From 657feacc538f796bdc312e6d6c3481d389dc32ae Mon Sep 17 00:00:00 2001 From: doria <93405247+dr-frmr@users.noreply.github.com> Date: Thu, 19 Jun 2025 11:41:39 -0400 Subject: [PATCH] Migrate to Peer Paradigm (#131) * Initial Model Changes * fix migration * update schemas * handle router changes * make name FK and corresponding crud changes * fix routers * comment metamessage references * add bulk peer session operations * update messages router * fix require_auth to make app runnable * remove peer from get messages * add new routes * implement new crud methods for session peers * alter keys router * add feature flags dict and token limit + fix SessionContext * fix: paginate get_session_peers and make tokens/summary query params in get_session_context * feat: add create_messages_for_peer, get_messages_for_peer * fix: make session_peers a Table * finalize upgrade * fix: working migration * fixes: schemas, crud, routes * add token count * fix migration errors discovered from db with data in it * fixes: unify with sdk * downgrade * feat: swap jwts to new paradigm * fix unit tests * fix tests pt 2 * fix: handle foreign key errors in create_messages * fix downgrade * downgrade queue changes * feat: add search to resources, make get_messages handle limits, add get_representation to peer * chore: beef up tests * fix: move chat and rep params to post body, add target to get_representation * fix get_user_protected_collection and embedding store * feat: add peer config to models, crud, schemas, routes * fix: update tests and fix list(tuple()) to dict() * add session peer left_at/joined_at and modify enqueue * [wip]: feat: refactor history to match new paradigm and implement get_context * fix messages enqueue and test it * chore: align deriver and new honcho paradigm * chore: update consumer * chore: get rid of is_user * feat: change queue tables to new key strat * fix: convert queue session_id to str properly * fix downgrade migration * feat: re-integrate old deriver * chore: coderabbit review, lots of small bug fixes * fix: fix batch migration of messages and token count * fix: mock ModelClient * CodeRabbit comments * CR comments 2 * fix: handle metadata and feature flags properly in get_or_creates * cr comments 3 * feature flag to configuration * feat: add real get crud * fix: remove reverse param from places it does not belong * add session.name constraint; narrow task type; disable deriver from configuration * get_or_add_peers_to_session + session peers limit * fix: add internal_metadata, fix agent * fix: move working rep into crud get/set, unstub get_working_representation * fix: don't payload metadata * peer protected collection -> global / local rep collections * fix: remove spurious mockery * feat: add english language search index * fix: remove spurious error * chore: 2.0.0 -- update readme, changelog, claude.md * chore: update readme for peer paradigm --------- Co-authored-by: Vineeth Voruganti <13438633+VVoruganti@users.noreply.github.com> Co-authored-by: Rajat Ahuja --- CHANGELOG.md | 39 + CLAUDE.md | 100 +- README.md | 136 +- migrations/utils.py | 29 + .../d429de0e5338_adopt_peer_paradigm.py | 1982 ++++++++++++++ pyproject.toml | 3 +- src/agent.py | 174 +- src/crud.py | 2223 ++++++++-------- src/db.py | 5 +- src/deriver/consumer.py | 241 +- src/deriver/queue.py | 204 +- src/deriver/tom/__init__.py | 12 +- src/deriver/tom/conversational.py | 22 +- src/deriver/tom/embeddings.py | 36 +- src/deriver/tom/long_term.py | 1 - src/deriver/tom/single_prompt.py | 2 - src/main.py | 18 +- src/models.py | 383 +-- src/routers/apps.py | 138 - src/routers/collections.py | 190 -- src/routers/documents.py | 212 -- src/routers/keys.py | 20 +- src/routers/messages.py | 381 +-- src/routers/metamessages.py | 154 -- src/routers/peers.py | 315 +++ src/routers/sessions.py | 487 ++-- src/routers/users.py | 162 -- src/routers/workspaces.py | 107 + src/schemas.py | 321 +-- src/security.py | 108 +- src/utils/history.py | 372 +-- tests/conftest.py | 131 +- tests/integration/test_enqueue.py | 599 +++++ tests/routes/test_apps.py | 151 -- tests/routes/test_collections.py | 185 -- tests/routes/test_documents.py | 261 -- tests/routes/test_keys.py | 18 +- tests/routes/test_messages.py | 695 ++++- tests/routes/test_metamessages.py | 440 --- tests/routes/test_peers.py | 525 ++++ tests/routes/test_scoped_api.py | 505 +--- tests/routes/test_sessions.py | 1038 +++++--- tests/routes/test_users.py | 88 - tests/routes/test_validation_api.py | 498 +--- tests/routes/test_workspaces.py | 248 ++ tests/test_schema_validations.py | 197 +- tests/utils/test_model_client.py | 8 +- uv.lock | 2351 +++++++++-------- 48 files changed, 9657 insertions(+), 6858 deletions(-) create mode 100644 migrations/versions/d429de0e5338_adopt_peer_paradigm.py delete mode 100644 src/routers/apps.py delete mode 100644 src/routers/collections.py delete mode 100644 src/routers/documents.py delete mode 100644 src/routers/metamessages.py create mode 100644 src/routers/peers.py delete mode 100644 src/routers/users.py create mode 100644 src/routers/workspaces.py create mode 100644 tests/integration/test_enqueue.py delete mode 100644 tests/routes/test_apps.py delete mode 100644 tests/routes/test_collections.py delete mode 100644 tests/routes/test_documents.py delete mode 100644 tests/routes/test_metamessages.py create mode 100644 tests/routes/test_peers.py delete mode 100644 tests/routes/test_users.py create mode 100644 tests/routes/test_workspaces.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 86688d17..2dc115fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,45 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [2.0.0] + +### Added + +- Ability to get a peer's working representation +- Metadata to all data primitives (Workspaces, Peers, Sessions, Messages) +- Internal metadata to store Honcho's state no longer exposed in API +- Batch message operations and enhanced message querying with token and message count limits +- Search and summary functionalities scoped by workspace, peer, and session +- Session context retrieval with summaries and token allocation + +### Changed + +- New architecture centered around the concept of a "peer" replaces the former + "app"/"user"/"session" paradigm +- Workspaces replace "apps" as top-level namespace +- Peers replace "users" +- Sessions no longer nested beneath peers and no longer limited to a single + user-assistant model. A session exists independently of any one peer and + peers can be added to and removed from sessions. +- Dialectic API is now part of the Peer, not the Session +- Dialectic API now allows queries to be scoped to a session or "targeted" + to a fellow peer +- Database schema migrated to adopt workspace/peer/session naming and structure +- Authentication and JWT scopes updated to workspace/peer/session hierarchy +- Queue processing now works on 'work units' instead of sessions +- Message token counting updated with tiktoken integration and fallback heuristic +- Queue and message processing updated to handle sender/target and task types for multi-peer scenarios + +### Fixed + +- Improved error handling and validation for batch message operations and metadata + +### Removed + +- Metamessages removed in favor of metadata +- Collections and Documents no longer exposed in the API, solely internal +- Obsolete tests for apps, users, collections, documents, and metamessages + ## [1.1.0] ### Added diff --git a/CLAUDE.md b/CLAUDE.md index dff1c953..6bb26342 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -7,9 +7,64 @@ Honcho is an infrastructure layer for building AI agents with social cognition a - Personalizing user experiences through understanding user psychology - Providing a Dialectic API that injects personal context just-in-time - Supporting development of LLM-powered applications that adapt to end users +- Enabling multi-peer sessions where multiple participants (users or agents) can interact Honcho leverages the inherent theory-of-mind capabilities of LLMs to build coherent models of user psychology over time, enabling more personalized and effective AI interactions. +## Core Concepts + +### Peer Paradigm +Honcho uses a peer-based model where both users and agents are represented as "peers". This unified approach enables: +- Multi-participant sessions with mixed human and AI agents +- Configurable observation settings (which peers observe which others) +- Flexible identity management for all participants + +### Key Primitives +- **Workspace** (formerly App): The root organizational unit containing all resources +- **Peer** (formerly User): Any participant in the system (human or AI) +- **Session**: A conversation context that can involve multiple peers +- **Message**: Data units that can represent communication between peers OR arbitrary data ingested by a peer to enhance its global representation +- **Collections & Documents**: Internal vector storage for theory-of-mind representations (not exposed via API) + +## Architecture Overview + +### API Structure +All API routes follow the pattern: `/v1/{resource}/{id}/{action}` +- **Workspaces**: Create, list, update, search +- **Peers**: Create, list, update, chat (dialectic), messages, representation +- **Sessions**: Create, list, update, delete, clone, manage peers, get context +- **Messages**: Create (batch up to 100), list, get, update +- **Keys**: Create scoped JWT tokens + +### Key Features + +#### Dialectic API (`/peers/{peer_id}/chat`) +- Provides theory-of-mind informed responses +- Integrates long-term facts from vector storage +- Supports streaming responses +- Configurable LLM providers + +#### Message Processing Pipeline +1. Messages created via API (batch or single) +2. Enqueued for background processing: + - `representation`: Update peer's theory of mind + - `summary`: Create session summaries +3. Session-based queue processing ensures order +4. Results stored internally in vector DB + +#### Theory of Mind System +- Multiple implementation methods (conversational, single_prompt, long_term) +- Facts extracted from messages and stored in collections +- Representations combine short-term inference with long-term facts +- Configurable via peer and session feature flags + +### Configuration +- Hierarchical config: config.toml + environment variables +- Database settings with connection pooling +- Multiple LLM provider support +- Background worker (deriver) settings +- Authentication can be toggled on/off + ## Development Guide ### Commands @@ -29,12 +84,51 @@ Honcho leverages the inherent theory-of-mind capabilities of LLMs to build coher - Docstrings: Use Google style docstrings ### Project Structure -- FastAPI routes in src/routers/ -- SQLAlchemy ORM models in src/models.py with proper type annotations -- Pydantic schemas in src/schemas.py for API validation +``` +src/ +├── main.py # FastAPI app setup with middleware and exception handlers +├── models.py # SQLAlchemy ORM models with proper type annotations +├── schemas.py # Pydantic validation schemas for API +├── crud.py # Database operations +├── dependencies.py # Dependency injection (DB sessions) +├── exceptions.py # Custom exception types +├── security.py # JWT authentication +├── agent.py # Dialectic API implementation +├── routers/ # API endpoints +│ ├── workspaces.py +│ ├── peers.py +│ ├── sessions.py +│ ├── messages.py +│ └── keys.py +├── deriver/ # Background processing system +│ ├── consumer.py # Message processing logic +│ ├── queue.py # Queue management +│ └── tom/ # Theory of Mind implementations +└── utils/ # Utilities + ├── history.py # Session history management + ├── cache.py # Caching utilities + └── model_client.py # LLM client abstraction +``` - Tests in pytest with fixtures in tests/conftest.py - Use environment variables via python-dotenv (.env) +### Database Design +- All tables use text IDs (nanoid format) as primary keys +- Composite foreign keys for multi-tenant relationships +- Feature flags on workspace, peer, and session levels +- Token counting on messages for usage tracking +- JSONB metadata fields for extensibility +- HNSW indexes for vector similarity search + +### Key Architectural Decisions +1. **Multi-Peer Sessions**: Sessions can have multiple participants with different observation settings +2. **Flexible Theory of Mind**: Pluggable ToM implementations (conversational, single_prompt, long_term) +3. **Background Processing**: Async queue system for expensive operations +4. **Provider Abstraction**: Model client supports multiple LLM providers +5. **Scoped Authentication**: JWT tokens can be scoped to workspace, peer, or session level +6. **Batch Operations**: Support for bulk message creation (up to 100 messages) +7. **Session History**: Two-tier summarization (short every 20 messages, long every 60) + ### Error Handling - Custom exceptions defined in src/exceptions.py - Use specific exception types (ResourceNotFoundException, ValidationException, etc.) diff --git a/README.md b/README.md index aee30c05..3f9f6272 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # 🫡 Honcho -![Static Badge](https://img.shields.io/badge/Version-1.1.0-blue) +![Static Badge](https://img.shields.io/badge/Version-2.0.0-blue) [![Discord](https://img.shields.io/discord/1016845111637839922?style=flat&logo=discord&logoColor=23ffffff&label=Plastic%20Labs&labelColor=235865F2)](https://discord.gg/plasticlabs) [![arXiv](https://img.shields.io/badge/arXiv-2310.06983-b31b1b.svg)](https://arxiv.org/abs/2310.06983) ![GitHub License](https://img.shields.io/github/license/plastic-labs/honcho) @@ -9,9 +9,7 @@ [![PyPI version](https://img.shields.io/pypi/v/honcho-ai.svg)](https://pypi.org/project/honcho-ai/) [![NPM version](https://img.shields.io/npm/v/honcho-ai.svg)](https://npmjs.org/package/honcho-ai) -Honcho is a platform for making AI agents and LLM powered applications that are personalized -to their end users. It leverages the inherent theory-of-mind capabilities of -LLMs to cohere to user psychology over time. +Honcho is an infrastructure layer for building AI agents with social cognition and theory-of-mind capabilities. It enables developers to create AI agents and LLM-powered applications that are personalized to their end users by leveraging the inherent theory-of-mind capabilities of LLMs to build coherent models of user psychology over time. Read about the project [here](https://blog.plasticlabs.ai/blog/A-Simple-Honcho-Primer). @@ -60,102 +58,136 @@ version of Honcho. The functionality of Honcho can be split into two different services: Storage and Insights. + +### Peer Paradigm + +Honcho uses a peer-based model where both users and agents are represented as "peers". This unified approach enables: + +- Multi-participant sessions with mixed human and AI agents +- Configurable observation settings (which peers observe which others) +- Flexible identity management for all participants +- Support for complex multi-agent interactions + +#### Key Features + +- **Theory-of-Mind System**: Multiple implementation methods that extract facts from interactions and build comprehensive models of peer psychology +- **Dialectic API**: Provides theory-of-mind informed responses that integrate long-term facts with current context +- **Background Processing**: Asynchronous processing pipeline for expensive operations like representation updates and session summarization +- **Multi-Provider Support**: Configurable LLM providers for different use cases + ### Storage Honcho contains several different primitives used for storing application and -user data. This data is used for managing conversations, modeling user +peer data. This data is used for managing conversations, modeling peer psychology, building RAG applications, and more. -The philosophy behind Honcho is to provide a platform that is user-centric and +The philosophy behind Honcho is to provide a platform that is peer-centric and easily scalable from a single user to a million. -Below is a mapping of the different primitives. +Below is a mapping of the different primitives and their relationships. ``` -Apps -└── Users - ├── Sessions - │ └── Messages - ├── Collections - │ └── Documents - └── Metamessages +Workspaces +├── Peers ←──────────────────┐ +│ ├── Sessions │ +│ ├── Collections │ +│ │ └── Documents │ +│ └── Messages (peer-level)│ +│ │ +└── Sessions ←───────────────┤ (many-to-many) + ├── Peers ───────────────┘ + └── Messages (session-level) ``` +**Relationship Details:** +- A **Workspace** contains multiple **Peers** +- **Peers** and **Sessions** have a many-to-many relationship (peers can participate in multiple sessions, sessions can have multiple peers) +- **Messages** can exist at two levels: + - **Session-level**: Communication between peers within a session + - **Peer-level**: Data ingested by a peer to enhance its global representation +- **Collections** belong to specific **Peers** +- **Documents** are stored within **Collections** + Users familiar with APIs such as the OpenAI Assistants API will be familiar with much of the mapping here. -#### Apps +#### Workspaces -This is the top level construct of Honcho. Developers can register different -`Apps` for different assistants, agents, AI enabled features, etc. It is a way to -isolation data between use cases. +This is the top level construct of Honcho (formerly called Apps). Developers can register different +`Workspaces` for different assistants, agents, AI enabled features, etc. It is a way to +isolate data between use cases and provide multi-tenant capabilities. -**Users** +#### Peers -Within an `App` everything revolves around a `User`. the `User` object -literally represent a user of an application. +Within a `Workspace` everything revolves around a `Peer`. The `Peer` object +represents any participant in the system - whether human users or AI agents. +This unified model enables complex multi-participant interactions. #### Sessions -The `Session` object represents a set of interactions a `User` has with an -`App`. Other application may refer to this as a thread or conversation. +The `Session` object represents a set of interactions between `Peers` within a +`Workspace`. Other applications may refer to this as a thread or conversation. +Sessions can involve multiple peers with configurable observation settings. -**Messages** +#### Messages -The `Message` represents an atomic interaction of a `User` in a `Session`. -`Message`s are labeled as either a `User` or AI message. +The `Message` represents an atomic data unit that can exist at two levels: + +- **Session-level Messages**: Communication between peers within a session context +- **Peer-level Messages**: Arbitrary data ingested by a peer to enhance its global representation (independent of any session) + +All messages are labeled by their source peer and can be processed asynchronously to update theory-of-mind models. This flexible design allows for both conversational interactions and broader data ingestion for personality modeling. #### Collections At a high level a `Collection` is a named group of `Documents`. Developers -familiar with RAG based applications will be familiar with these. `Collection`s +familiar with RAG based applications will be familiar with these. `Collections` store vector embedded data that developers and agents can retrieve against using functions like cosine similarity. -Developers can create multiple `Collection`s for a user for different purposes +Developers can create multiple `Collections` for a peer for different purposes such as modeling different personas, adding third-party data such as emails and -PDF files, and more. +PDF files, and more. Collections are also used internally by Honcho to store +theory-of-mind representations. #### Documents As stated before a `Document` is vector embedded data stored in a `Collection`. -#### Metamessages - -A `Metamessage` is similar to a `Message` with different use case. They are -meant to be used to store intermediate inference from AI assistants or other -derived information that is separate from the main `User` `App` interaction -loop. For complicated prompting architectures like [metacognitive prompting](https://arxiv.org/abs/2310.06983) -metamessages can store thought and reflection steps along with having developer -information such as logs. - -Each `Metamessage` is associated with a `User` with the ability to optionally -tie to a `Session` and a `Message`. - ### Insights The Insight functionality of Honcho is built on top of the Storage service. As -`Messages` and `Sessions` are created for a `User`, Honcho will asynchronously -reason about the `User`'s psychology to derive facts about them and store them -in a reserved `Collection`. +`Messages` and `Sessions` are created for `Peers`, Honcho will asynchronously +reason about peer psychology to derive facts about them and store them +in reserved `Collections`. + +The system uses a sophisticated message processing pipeline: + +1. Messages are created via API +2. Enqueued for background processing including: + - `representation`: Update peer's theory of mind + - `summary`: Create session summaries +3. Session-based queue processing ensures proper ordering +4. Results are stored internally in the vector database To read more about how this works read our [Research Paper](https://arxiv.org/abs/2310.06983) Developers can then leverage these insights in their application to better -server `User` needs. The primary interface for using these insights is through +serve peer needs. The primary interface for using these insights is through the [Dialectic Endpoint](https://blog.plasticlabs.ai/blog/Introducing-Honcho's-Dialectic-API). -This is a regular API endpoint that takes natural language requests to get data -about the `User`. This robust design let's us use this single endpoint for all -cases where extra personalization or information about the `User` is necessary. +This is a regular API endpoint (`/peers/{peer_id}/chat`) that takes natural language requests to get data +about the `Peer`. This robust design lets us use this single endpoint for all +cases where extra personalization or information about the `Peer` is necessary. -A developer's application can treat Honcho as an oracle to the `User` and +A developer's application can treat Honcho as an oracle to the `Peer` and consult it when necessary. Some examples of how to leverage the Dialectic API include: -- Asking Honcho for a theory-of-mind insight about the `User` -- Asking Honcho to hydrate a prompt with data about the `User`s behavior -- Asking Honcho for a 2nd opinion or approach about how to respond to the User +- Asking Honcho for a theory-of-mind insight about the `Peer` +- Asking Honcho to hydrate a prompt with data about the `Peer`s behavior +- Asking Honcho for a 2nd opinion or approach about how to respond to the Peer +- Getting personalized responses that incorporate long-term facts and context ## License diff --git a/migrations/utils.py b/migrations/utils.py index e7dbbafe..41f6d975 100644 --- a/migrations/utils.py +++ b/migrations/utils.py @@ -9,6 +9,14 @@ def get_schema() -> str: return getenv("DATABASE_SCHEMA", "public") +def table_exists(table_name: str, inspector: Optional[sa.Inspector] = None) -> bool: + """Check if a table exists in the database.""" + if inspector is None: + inspector = sa.inspect(op.get_bind()) + schema = get_schema() + return inspector.has_table(table_name, schema=schema) + + def column_exists( table_name: str, column_name: str, inspector: Optional[sa.Inspector] = None ) -> bool: @@ -43,3 +51,24 @@ def index_exists( indexes = inspector.get_indexes(table_name, schema=schema) return any(idx["name"] == index_name for idx in indexes) + +def constraint_exists( + table_name: str, + constraint_name: str, + type: str, + inspector: Optional[sa.Inspector] = None, +) -> bool: + """Check if a constraint exists in a table.""" + if inspector is None: + inspector = sa.inspect(op.get_bind()) + schema = get_schema() + if type == "check": + constraints = inspector.get_check_constraints(table_name, schema=schema) + elif type == "unique": + constraints = inspector.get_unique_constraints(table_name, schema=schema) + elif type == "primary": + constraint = inspector.get_pk_constraint(table_name, schema=schema) + return constraint["name"] == constraint_name + else: + raise ValueError(f"Invalid constraint type: {type}") + return any(constraint["name"] == constraint_name for constraint in constraints) diff --git a/migrations/versions/d429de0e5338_adopt_peer_paradigm.py b/migrations/versions/d429de0e5338_adopt_peer_paradigm.py new file mode 100644 index 00000000..04cca99c --- /dev/null +++ b/migrations/versions/d429de0e5338_adopt_peer_paradigm.py @@ -0,0 +1,1982 @@ +"""adopt peer paradigm + +Revision ID: d429de0e5338 +Revises: 66e63cf2cf77 +Create Date: 2025-06-09 15:16:38.164067 + +""" + +from collections.abc import Sequence +from contextlib import suppress +from os import getenv +from typing import Union + +import sqlalchemy as sa +import tiktoken +from alembic import op +from nanoid import generate as generate_nanoid +from sqlalchemy import text +from sqlalchemy.dialects import postgresql + +from migrations.utils import ( + column_exists, + constraint_exists, + fk_exists, + index_exists, + table_exists, +) + +# revision identifiers, used by Alembic. +revision: str = "d429de0e5338" +down_revision: Union[str, None] = "66e63cf2cf77" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + """Upgrade database schema to adopt peer paradigm.""" + schema = getenv("DATABASE_SCHEMA", "public") + inspector = sa.inspect(op.get_bind()) + + # Step 1: Rename tables + rename_tables(schema, inspector) + + # Step 2: Update workspaces table + update_workspaces_table(schema, inspector) + + # Step 3: Update peers table + update_peers_table(schema, inspector) + + # Step 4: Update queue and active_queue_sessions tables (moved before sessions update) + update_queue_and_active_queue_sessions_tables(schema, inspector) + + # Step 5: Update sessions table + update_sessions_table(schema, inspector) + + # Step 6: Create and populate session_peers table + create_and_populate_session_peers_table(schema, inspector) + + # Step 7: Update messages table + update_messages_table(schema, inspector) + + # Step 8: Update collections table + update_collections_table(schema, inspector) + + # Step 9: Update documents table + update_documents_table(schema, inspector) + + # Step 10: Drop metamessages table + if table_exists("metamessages", inspector): + op.drop_table("metamessages", schema=schema) + + # Step 11: Drop app_id, user_id from peers and sessions + if column_exists("sessions", "app_id", inspector): + op.drop_column("sessions", "app_id", schema=schema) + if column_exists("sessions", "user_id", inspector): + op.drop_column("sessions", "user_id", schema=schema) + + +def downgrade() -> None: + """Downgrade database schema to reverse peer paradigm adoption.""" + schema = getenv("DATABASE_SCHEMA", "public") + inspector = sa.inspect(op.get_bind()) + + # Step 1: Add back app_id, user_id to peers and sessions + restore_app_user_columns(schema, inspector) + + # Step 2: Restore documents table + restore_documents_table(schema, inspector) + + # Step 3: Restore collections table + restore_collections_table(schema, inspector) + + # Step 4: Restore messages table + restore_messages_table(schema, inspector) + + # Step 5: Drop session_peers table + if table_exists("session_peers", inspector): + op.drop_table("session_peers", schema=schema) + + # Step 6: Restore sessions table + restore_sessions_table(schema, inspector) + + # Step 7: Restore peers table + restore_peers_table(schema, inspector) + + # Step 8: Restore workspaces table + restore_workspaces_table(schema, inspector) + + # Step 9: Restore queue and active_queue_sessions tables + restore_queue_and_active_queue_sessions_tables(schema, inspector) + + # Step 10: Rename tables back + restore_table_names(schema, inspector) + + # Step 11: Restore foreign keys + restore_foreign_keys(schema) + + +def rename_tables(schema: str, inspector) -> None: + """Rename apps->workspaces and users->peers tables.""" + if inspector.has_table("apps", schema=schema): + op.rename_table("apps", "workspaces", schema=schema) + if inspector.has_table("users", schema=schema): + op.rename_table("users", "peers", schema=schema) + + +def update_workspaces_table(schema: str, inspector) -> None: + """Update workspaces table (formerly apps).""" + + # Add configuration column + op.add_column( + "workspaces", + sa.Column( + "configuration", + postgresql.JSONB(astext_type=sa.Text()), + nullable=False, + server_default="{}", + ), + schema=schema, + ) + # Add internal_metadata column + op.add_column( + "workspaces", + sa.Column( + "internal_metadata", + postgresql.JSONB(astext_type=sa.Text()), + nullable=False, + server_default="{}", + ), + schema=schema, + ) + + # Update constraint names + if constraint_exists("workspaces", "public_id_length", "check", inspector): + op.drop_constraint( + "public_id_length", "workspaces", type_="check", schema=schema + ) + if constraint_exists("workspaces", "public_id_format", "check", inspector): + op.drop_constraint( + "public_id_format", "workspaces", type_="check", schema=schema + ) + + # Rename public_id to id and make it the primary key + if constraint_exists("workspaces", "pk_apps", "primary", inspector): + op.drop_constraint("pk_apps", "workspaces", type_="primary", schema=schema) + if column_exists("workspaces", "id", inspector): + op.drop_column("workspaces", "id", schema=schema) + + op.alter_column("workspaces", "public_id", new_column_name="id", schema=schema) + op.create_primary_key("pk_workspaces", "workspaces", ["id"], schema=schema) + + op.create_check_constraint( + "id_length", "workspaces", "length(id) = 21", schema=schema + ) + op.create_check_constraint( + "id_format", "workspaces", "id ~ '^[A-Za-z0-9_-]+$'", schema=schema + ) + + +def update_peers_table(schema: str, inspector) -> None: + """Update peers table (formerly users).""" + + # Add configuration column + op.add_column( + "peers", + sa.Column( + "configuration", + postgresql.JSONB(astext_type=sa.Text()), + nullable=False, + server_default="{}", + ), + schema=schema, + ) + + # Add internal_metadata column + op.add_column( + "peers", + sa.Column( + "internal_metadata", + postgresql.JSONB(astext_type=sa.Text()), + nullable=False, + server_default="{}", + ), + schema=schema, + ) + + # Add workspace_name column and migrate data using app_id + op.add_column( + "peers", sa.Column("workspace_name", sa.TEXT(), nullable=True), schema=schema + ) + op.execute( + sa.text( + f"UPDATE {schema}.peers SET workspace_name = workspaces.name FROM {schema}.workspaces WHERE peers.app_id = workspaces.id" + ) + ) + + # Update constraints and indexes + if constraint_exists("peers", "public_id_length", "check", inspector): + op.drop_constraint("public_id_length", "peers", type_="check", schema=schema) + if constraint_exists("peers", "public_id_format", "check", inspector): + op.drop_constraint("public_id_format", "peers", type_="check", schema=schema) + + # Update primary key + if constraint_exists("peers", "pk_users", "primary", inspector): + op.drop_constraint("pk_users", "peers", type_="primary", schema=schema) + op.drop_column("peers", "id", schema=schema) + op.alter_column("peers", "public_id", new_column_name="id", schema=schema) + op.create_primary_key("pk_peers", "peers", ["id"], schema=schema) + + # Update foreign key + if fk_exists("peers", "fk_users_app_id_apps", inspector): + op.drop_constraint( + "fk_users_app_id_apps", "peers", type_="foreignkey", schema=schema + ) + if fk_exists("peers", "users_app_id_fkey", inspector): + op.drop_constraint( + "users_app_id_fkey", "peers", type_="foreignkey", schema=schema + ) + + op.create_foreign_key( + "fk_peers_workspace_name_workspaces", + "peers", + "workspaces", + ["workspace_name"], + ["name"], + referent_schema=schema, + ) + + # Update unique constraint + if constraint_exists("peers", "unique_name_app_user", "unique", inspector): + op.drop_constraint( + "unique_name_app_user", "peers", type_="unique", schema=schema + ) + op.create_unique_constraint( + "unique_name_workspace_peer", "peers", ["name", "workspace_name"], schema=schema + ) + + # Update indexes + op.drop_index("idx_users_app_lookup", "peers", schema=schema) + op.create_index( + "idx_peers_workspace_lookup", "peers", ["workspace_name", "name"], schema=schema + ) + + op.create_check_constraint("id_length", "peers", "length(id) = 21", schema=schema) + op.create_check_constraint( + "id_format", "peers", "id ~ '^[A-Za-z0-9_-]+$'", schema=schema + ) + + op.drop_column("peers", "app_id", schema=schema) + + +def update_sessions_table(schema: str, inspector) -> None: + """Update sessions table.""" + + # Add configuration column + op.add_column( + "sessions", + sa.Column( + "configuration", + postgresql.JSONB(astext_type=sa.Text()), + nullable=False, + server_default="{}", + ), + schema=schema, + ) + + # Add internal_metadata column + op.add_column( + "sessions", + sa.Column( + "internal_metadata", + postgresql.JSONB(astext_type=sa.Text()), + nullable=False, + server_default="{}", + ), + schema=schema, + ) + + # Add workspace_name column and migrate data using app_id + op.add_column( + "sessions", sa.Column("workspace_name", sa.TEXT(), nullable=True), schema=schema + ) + op.execute( + sa.text( + f"UPDATE {schema}.sessions SET workspace_name = workspaces.name FROM {schema}.workspaces WHERE sessions.app_id = workspaces.id" + ) + ) + + op.add_column( + "sessions", + sa.Column("name", sa.TEXT(), nullable=True), + schema=schema, + ) # Temporarily nullable + op.execute(sa.text(f"UPDATE {schema}.sessions SET name = public_id")) + op.alter_column("sessions", "name", nullable=False, schema=schema) + op.create_unique_constraint( + "unique_session_name", "sessions", ["name", "workspace_name"], schema=schema + ) + + # Update constraints and indexes + if constraint_exists("sessions", "public_id_length", "check", inspector): + op.drop_constraint("public_id_length", "sessions", type_="check", schema=schema) + if constraint_exists("sessions", "public_id_format", "check", inspector): + op.drop_constraint("public_id_format", "sessions", type_="check", schema=schema) + + # Update sessions table primary key and foreign keys + if constraint_exists("sessions", "pk_sessions", "primary", inspector): + op.drop_constraint("pk_sessions", "sessions", type_="primary", schema=schema) + + op.drop_column("sessions", "id", schema=schema) + op.alter_column("sessions", "public_id", new_column_name="id", schema=schema) + op.create_primary_key("pk_sessions", "sessions", ["id"], schema=schema) + + # Drop old foreign keys + if fk_exists("sessions", "fk_sessions_user_id_users", inspector): + op.drop_constraint( + "fk_sessions_user_id_users", "sessions", type_="foreignkey", schema=schema + ) + if fk_exists("sessions", "fk_sessions_app_id_apps", inspector): + op.drop_constraint( + "fk_sessions_app_id_apps", "sessions", type_="foreignkey", schema=schema + ) + if fk_exists("sessions", "sessions_app_id_fkey", inspector): + op.drop_constraint( + "sessions_app_id_fkey", "sessions", type_="foreignkey", schema=schema + ) + + # Create new foreign key + op.create_foreign_key( + "fk_sessions_workspace_name_workspaces", + "sessions", + "workspaces", + ["workspace_name"], + ["name"], + referent_schema=schema, + ) + + op.create_check_constraint( + "id_length", "sessions", "length(id) = 21", schema=schema + ) + op.create_check_constraint( + "id_format", "sessions", "id ~ '^[A-Za-z0-9_-]+$'", schema=schema + ) + op.create_check_constraint( + "name_length", "sessions", "length(name) <= 512", schema=schema + ) + + +def create_and_populate_session_peers_table(schema: str, inspector) -> None: + """Create and populate session_peers table.""" + + # Create session_peers table + if not table_exists("session_peers", inspector): + op.create_table( + "session_peers", + sa.Column("workspace_name", sa.TEXT(), nullable=False), + sa.Column("session_name", sa.TEXT(), nullable=False), + sa.Column("peer_name", sa.TEXT(), nullable=False), + sa.Column( + "configuration", + postgresql.JSONB(astext_type=sa.Text()), + nullable=False, + server_default="{}", + ), + sa.Column( + "internal_metadata", + postgresql.JSONB(astext_type=sa.Text()), + nullable=False, + server_default="{}", + ), + sa.Column( + "joined_at", + sa.DateTime(timezone=True), + nullable=False, + server_default=sa.func.now(), + ), + sa.Column( + "left_at", + sa.DateTime(timezone=True), + nullable=True, + ), + sa.ForeignKeyConstraint( + ["peer_name", "workspace_name"], + ["peers.name", "peers.workspace_name"], + ), + sa.ForeignKeyConstraint( + ["session_name", "workspace_name"], + ["sessions.name", "sessions.workspace_name"], + ), + sa.PrimaryKeyConstraint("workspace_name", "session_name", "peer_name"), + ) + + # Add agent peer for each workspace + conn = op.get_bind() + workspaces = conn.execute(sa.text(f"SELECT id, name FROM {schema}.workspaces")) + agent_peers_map = {} + for workspace in workspaces: + workspace_id, workspace_name = workspace + agent_peer_id = generate_nanoid() + agent_peers_map[workspace_name] = (agent_peer_id, workspace_id) + op.execute( + sa.text( + f"INSERT INTO {schema}.peers (id, name, workspace_name) VALUES (:id, :name, :workspace_name)" + ).bindparams( + id=agent_peer_id, name=agent_peer_id, workspace_name=workspace_name + ) + ) + + # Populate session_peers table + sessions = conn.execute( + sa.text(f""" + SELECT s.user_id, s.name, s.workspace_name, p.name as peer_name + FROM {schema}.sessions s + JOIN {schema}.peers p ON p.id = s.user_id AND p.workspace_name = s.workspace_name + """) + ).fetchall() + for session in sessions: + user_id, session_name, workspace_name, peer_name = session + op.execute( + sa.text( + "INSERT INTO session_peers (workspace_name, session_name, peer_name) VALUES (:workspace_name, :session_name, :peer_name)" + ).bindparams( + workspace_name=workspace_name, + session_name=session_name, + peer_name=peer_name, + ) + ) + agent_peer_id, _ = agent_peers_map[workspace_name] + op.execute( + sa.text( + f"INSERT INTO {schema}.session_peers (workspace_name, session_name, peer_name) VALUES (:workspace_name, :session_name, :peer_name)" + ).bindparams( + workspace_name=workspace_name, + session_name=session_name, + peer_name=agent_peer_id, + ) + ) + + +def update_messages_table(schema: str, inspector) -> None: + """Update messages table.""" + + # Add new columns + if not column_exists("messages", "peer_name", inspector): + op.add_column( + "messages", sa.Column("peer_name", sa.TEXT(), nullable=True), schema=schema + ) + if not column_exists("messages", "workspace_name", inspector): + op.add_column( + "messages", + sa.Column("workspace_name", sa.TEXT(), nullable=True), + schema=schema, + ) + if not column_exists("messages", "session_name", inspector): + op.add_column( + "messages", + sa.Column("session_name", sa.TEXT(), nullable=True), + schema=schema, + ) + + op.execute( + sa.text(f""" + UPDATE {schema}.messages SET + workspace_name = (SELECT name FROM {schema}.workspaces WHERE workspaces.id = messages.app_id) + """) + ) + + op.execute( + sa.text(f""" + UPDATE {schema}.messages SET + session_name = (SELECT name FROM {schema}.sessions WHERE sessions.id = messages.session_id) + """) + ) + + op.execute( + sa.text(f""" + UPDATE {schema}.messages SET + peer_name = CASE + WHEN is_user = true THEN ( + SELECT p.name + FROM {schema}.peers p + JOIN {schema}.sessions s ON s.user_id = p.id + WHERE s.name = messages.session_name + ) + ELSE ( + SELECT sp.peer_name + FROM {schema}.session_peers sp + WHERE sp.session_name = messages.session_name + AND sp.peer_name != ( + SELECT p.name + FROM {schema}.peers p + JOIN {schema}.sessions s ON s.user_id = p.id + WHERE s.name = messages.session_name + ) + ) + END + """) + ) + + # Make columns not nullable + op.alter_column("messages", "peer_name", nullable=False, schema=schema) + op.alter_column("messages", "workspace_name", nullable=False, schema=schema) + + # Drop old columns and constraints + if fk_exists("messages", "fk_messages_session_id_sessions", inspector): + op.drop_constraint( + "fk_messages_session_id_sessions", + "messages", + type_="foreignkey", + schema=schema, + ) + if fk_exists("messages", "messages_app_id_fkey", inspector): + op.drop_constraint( + "messages_app_id_fkey", "messages", type_="foreignkey", schema=schema + ) + if fk_exists("messages", "fk_messages_app_id_apps", inspector): + op.drop_constraint( + "fk_messages_app_id_apps", "messages", type_="foreignkey", schema=schema + ) + if fk_exists("messages", "messages_user_id_fkey", inspector): + op.drop_constraint( + "messages_user_id_fkey", "messages", type_="foreignkey", schema=schema + ) + + op.drop_column("messages", "is_user", schema=schema) + op.drop_column("messages", "user_id", schema=schema) + op.drop_column("messages", "app_id", schema=schema) + op.drop_column("messages", "session_id", schema=schema) + + # Add new foreign keys + op.create_foreign_key( + "fk_messages_session_name_sessions", + "messages", + "sessions", + ["session_name", "workspace_name"], + ["name", "workspace_name"], + referent_schema=schema, + ) + op.create_foreign_key( + "fk_messages_peer_name_peers", + "messages", + "peers", + ["peer_name", "workspace_name"], + ["name", "workspace_name"], + referent_schema=schema, + ) + op.create_foreign_key( + "fk_messages_workspace_name_workspaces", + "messages", + "workspaces", + ["workspace_name"], + ["name"], + referent_schema=schema, + ) + + # Update indexes + if index_exists("messages", "idx_messages_session_lookup", inspector): + op.drop_index( + "idx_messages_session_lookup", table_name="messages", schema=schema + ) + op.create_index( + "idx_messages_session_lookup", + "messages", + ["session_name", "id"], + postgresql_include=["id", "created_at"], + schema=schema, + ) + op.create_index("ix_messages_peer_name", "messages", ["peer_name"], schema=schema) + op.create_index( + "ix_messages_workspace_name", "messages", ["workspace_name"], schema=schema + ) + # Create full text search index on content column + op.create_index( + "idx_messages_content_gin", + "messages", + [sa.text("to_tsvector('english', content)")], + postgresql_using="gin", + schema=schema, + ) + + op.add_column( + "messages", + sa.Column("token_count", sa.Integer(), nullable=False, server_default="0"), + schema=schema, + ) + op.add_column( + "messages", + sa.Column( + "internal_metadata", + postgresql.JSONB(astext_type=sa.Text()), + nullable=False, + server_default="{}", + ), + schema=schema, + ) + + # Backfill token counts for existing messages + backfill_token_counts(schema) + + +def update_collections_table(schema: str, inspector) -> None: + """Update collections table.""" + + # Add new columns + if not column_exists("collections", "peer_name", inspector): + op.add_column( + "collections", + sa.Column("peer_name", sa.TEXT(), nullable=True), + schema=schema, + ) + if not column_exists("collections", "workspace_name", inspector): + op.add_column( + "collections", + sa.Column("workspace_name", sa.TEXT(), nullable=True), + schema=schema, + ) + if not column_exists("collections", "internal_metadata", inspector): + op.add_column( + "collections", + sa.Column( + "internal_metadata", + postgresql.JSONB(astext_type=sa.Text()), + nullable=False, + server_default="{}", + ), + schema=schema, + ) + + # Populate new columns from existing data + op.execute( + sa.text(f""" + UPDATE {schema}.collections SET + peer_name = (SELECT name FROM {schema}.peers WHERE peers.id = collections.user_id), + workspace_name = (SELECT name FROM {schema}.workspaces WHERE workspaces.id = collections.app_id) + """) + ) + + # Make columns not nullable + op.alter_column("collections", "peer_name", nullable=False, schema=schema) + op.alter_column("collections", "workspace_name", nullable=False, schema=schema) + + # Update primary key structure + if constraint_exists("collections", "pk_collections", "primary", inspector): + op.drop_constraint( + "pk_collections", "collections", type_="primary", schema=schema + ) + if column_exists("collections", "id", inspector): + op.drop_column("collections", "id", schema=schema) + op.alter_column("collections", "public_id", new_column_name="id", schema=schema) + op.create_primary_key("pk_collections", "collections", ["id"], schema=schema) + + # Drop old constraints and columns + if fk_exists("collections", "fk_collections_user_id_users", inspector): + op.drop_constraint( + "fk_collections_user_id_users", + "collections", + type_="foreignkey", + schema=schema, + ) + if fk_exists("collections", "fk_collections_app_id_apps", inspector): + op.drop_constraint( + "fk_collections_app_id_apps", + "collections", + type_="foreignkey", + schema=schema, + ) + if fk_exists("collections", "collections_app_id_fkey", inspector): + op.drop_constraint( + "collections_app_id_fkey", "collections", type_="foreignkey", schema=schema + ) + if fk_exists("collections", "collections_user_id_fkey", inspector): + op.drop_constraint( + "collections_user_id_fkey", "collections", type_="foreignkey", schema=schema + ) + + op.drop_constraint( + "unique_name_collection_user", "collections", type_="unique", schema=schema + ) + op.drop_column("collections", "user_id", schema=schema) + op.drop_column("collections", "app_id", schema=schema) + + # Add new constraints + op.create_unique_constraint( + "unique_name_collection_peer", + "collections", + ["name", "peer_name", "workspace_name"], + schema=schema, + ) + op.create_foreign_key( + "fk_collections_peer_name_peers", + "collections", + "peers", + ["peer_name", "workspace_name"], + ["name", "workspace_name"], + referent_schema=schema, + ) + op.create_foreign_key( + "fk_collections_workspace_name_workspaces", + "collections", + "workspaces", + ["workspace_name"], + ["name"], + referent_schema=schema, + ) + + # Update constraint names + if constraint_exists("collections", "public_id_length", "check", inspector): + op.drop_constraint( + "public_id_length", "collections", type_="check", schema=schema + ) + if constraint_exists("collections", "public_id_format", "check", inspector): + op.drop_constraint( + "public_id_format", "collections", type_="check", schema=schema + ) + + op.create_check_constraint( + "id_length", "collections", "length(id) = 21", schema=schema + ) + op.create_check_constraint( + "id_format", "collections", "id ~ '^[A-Za-z0-9_-]+$'", schema=schema + ) + + +def update_documents_table(schema: str, inspector) -> None: + """Update documents table.""" + + # Add new columns + if not column_exists("documents", "peer_name", inspector): + op.add_column( + "documents", sa.Column("peer_name", sa.TEXT(), nullable=True), schema=schema + ) + if not column_exists("documents", "workspace_name", inspector): + op.add_column( + "documents", + sa.Column("workspace_name", sa.TEXT(), nullable=True), + schema=schema, + ) + + # Populate new columns from existing data + op.execute( + sa.text(f""" + UPDATE {schema}.documents SET + peer_name = (SELECT name FROM {schema}.peers WHERE peers.id = documents.user_id), + workspace_name = (SELECT name FROM {schema}.workspaces WHERE workspaces.id = documents.app_id) + """) + ) + + # Drop old constraints and columns + if fk_exists("documents", "fk_documents_collection_id_collections", inspector): + op.drop_constraint( + "fk_documents_collection_id_collections", + "documents", + type_="foreignkey", + schema=schema, + ) + if fk_exists("documents", "fk_documents_user_id_users", inspector): + op.drop_constraint( + "fk_documents_user_id_users", "documents", type_="foreignkey", schema=schema + ) + if fk_exists("documents", "fk_documents_app_id_apps", inspector): + op.drop_constraint( + "fk_documents_app_id_apps", "documents", type_="foreignkey", schema=schema + ) + if fk_exists("documents", "documents_app_id_fkey", inspector): + op.drop_constraint( + "documents_app_id_fkey", "documents", type_="foreignkey", schema=schema + ) + if fk_exists("documents", "documents_user_id_fkey", inspector): + op.drop_constraint( + "documents_user_id_fkey", "documents", type_="foreignkey", schema=schema + ) + + # Now rename the column + if column_exists("documents", "collection_id", inspector): + op.alter_column( + "documents", + "collection_id", + new_column_name="collection_name", + schema=schema, + ) + + # rename metadata to internal_metadata + op.alter_column( + "documents", + "metadata", + new_column_name="internal_metadata", + schema=schema, + ) + + # Convert collection_id references to collection names + # (collection_id contains old collection IDs, we need to get the collection names) + op.execute( + sa.text(f""" + UPDATE {schema}.documents + SET collection_name = ( + SELECT c.name + FROM {schema}.collections c + WHERE c.id = documents.collection_name + AND c.peer_name = documents.peer_name + AND c.workspace_name = documents.workspace_name + ) + """) + ) + + # Make columns not nullable + op.alter_column("documents", "peer_name", nullable=False, schema=schema) + op.alter_column("documents", "workspace_name", nullable=False, schema=schema) + + # Update primary key structure + if constraint_exists("documents", "pk_documents", "primary", inspector): + op.drop_constraint("pk_documents", "documents", type_="primary", schema=schema) + if column_exists("documents", "id", inspector): + op.drop_column("documents", "id", schema=schema) + op.alter_column("documents", "public_id", new_column_name="id", schema=schema) + op.create_primary_key("pk_documents", "documents", ["id"], schema=schema) + + op.drop_column("documents", "user_id", schema=schema) + op.drop_column("documents", "app_id", schema=schema) + + # Add new foreign keys + op.create_foreign_key( + "fk_documents_collection_name_collections", + "documents", + "collections", + ["collection_name", "peer_name", "workspace_name"], + ["name", "peer_name", "workspace_name"], + referent_schema=schema, + ) + op.create_foreign_key( + "fk_documents_workspace_name_workspaces", + "documents", + "workspaces", + ["workspace_name"], + ["name"], + referent_schema=schema, + ) + + # Update constraint names + if constraint_exists("documents", "public_id_length", "check", inspector): + op.drop_constraint( + "public_id_length", "documents", type_="check", schema=schema + ) + if constraint_exists("documents", "public_id_format", "check", inspector): + op.drop_constraint( + "public_id_format", "documents", type_="check", schema=schema + ) + + op.create_check_constraint( + "id_length", "documents", "length(id) = 21", schema=schema + ) + op.create_check_constraint( + "id_format", "documents", "id ~ '^[A-Za-z0-9_-]+$'", schema=schema + ) + + +def update_queue_and_active_queue_sessions_tables(schema: str, inspector) -> None: + """Update queue and active_queue_sessions tables.""" + + # Drop foreign key constraints first, before changing column types + if table_exists("queue", inspector) and fk_exists( + "queue", "fk_queue_session_id_sessions", inspector + ): + op.drop_constraint( + "fk_queue_session_id_sessions", + "queue", + type_="foreignkey", + schema=schema, + ) + + if table_exists("active_queue_sessions", inspector) and fk_exists( + "active_queue_sessions", + "fk_active_queue_sessions_session_id_sessions", + inspector, + ): + op.drop_constraint( + "fk_active_queue_sessions_session_id_sessions", + "active_queue_sessions", + type_="foreignkey", + schema=schema, + ) + + connection = op.get_bind() + + # Get the mapping of old session.id (integer) to new session.id (text, which is public_id) + # At this point, sessions table still has both id (integer) and public_id (text) columns + session_id_mapping = {} + if table_exists("sessions", inspector): + sessions_mapping = connection.execute( + sa.text(f"SELECT id, public_id FROM {schema}.sessions") + ).fetchall() + + for old_id, new_id in sessions_mapping: + session_id_mapping[old_id] = new_id + + # Update queue table + if table_exists("queue", inspector): + # Get current session_id values in queue table + queue_session_ids = connection.execute( + sa.text( + f"SELECT DISTINCT session_id FROM {schema}.queue WHERE session_id IS NOT NULL" + ) + ).fetchall() + + op.alter_column( + "queue", + "session_id", + type_=sa.Text(), + existing_type=sa.BigInteger(), + postgresql_using="session_id::text", + nullable=True, + ) + # Convert session_id values in queue table + for (session_id,) in queue_session_ids: + if session_id in session_id_mapping: + new_id = session_id_mapping[session_id] + connection.execute( + sa.text( + f"UPDATE {schema}.queue SET session_id = :new_id WHERE session_id = :old_id" + ), + {"new_id": str(new_id), "old_id": str(session_id)}, + ) + + # Update active_queue_sessions table + if table_exists("active_queue_sessions", inspector): + active_queue_session_ids = connection.execute( + sa.text( + f"SELECT DISTINCT session_id FROM {schema}.active_queue_sessions WHERE session_id IS NOT NULL" + ) + ).fetchall() + + if constraint_exists( + "active_queue_sessions", "pk_active_queue_sessions", "primary", inspector + ): + op.drop_constraint( + "pk_active_queue_sessions", + "active_queue_sessions", + type_="primary", + schema=schema, + ) + + op.add_column( + "active_queue_sessions", + sa.Column( + "id", + sa.TEXT(), + nullable=True, + ), + schema=schema, + ) + + # Update existing rows with unique nanoids + connection = op.get_bind() + + # Get all rows that need IDs (using session_id as unique identifier) + rows_needing_ids = connection.execute( + sa.text( + f"SELECT session_id FROM {schema}.active_queue_sessions WHERE id IS NULL" + ) + ).fetchall() + + if rows_needing_ids: + # Generate nanoids for all rows upfront + updates = [ + {"session_id": row[0], "nanoid": generate_nanoid()} + for row in rows_needing_ids + ] + + # Batch update using individual queries (still better than while loop) + for update in updates: + connection.execute( + sa.text( + f"UPDATE {schema}.active_queue_sessions SET id = :nanoid WHERE session_id = :session_id AND id IS NULL" + ), + update, + ) + + # Make the column non-nullable after populating data + op.alter_column("active_queue_sessions", "id", nullable=False, schema=schema) + + op.create_primary_key( + "pk_active_queue_sessions", + "active_queue_sessions", + ["id"], + schema=schema, + ) + + op.alter_column( + "active_queue_sessions", + "session_id", + type_=sa.Text(), + nullable=True, + existing_type=sa.BigInteger(), + postgresql_using="session_id::text", + ) + # Convert session_id values in active_queue_sessions table + for (session_id,) in active_queue_session_ids: + if session_id in session_id_mapping: + new_id = session_id_mapping[session_id] + connection.execute( + sa.text( + f"UPDATE {schema}.active_queue_sessions SET session_id = :new_id WHERE session_id = :old_id" + ), + {"new_id": str(new_id), "old_id": str(session_id)}, + ) + op.add_column( + "active_queue_sessions", + sa.Column("sender_name", sa.TEXT(), nullable=True), + schema=schema, + ) + op.add_column( + "active_queue_sessions", + sa.Column("target_name", sa.TEXT(), nullable=True), + schema=schema, + ) + op.add_column( + "active_queue_sessions", + sa.Column("task_type", sa.TEXT(), nullable=False), + schema=schema, + ) + op.create_unique_constraint( + "unique_active_queue_session", + "active_queue_sessions", + ["session_id", "sender_name", "target_name", "task_type"], + schema=schema, + ) + + +def backfill_token_counts(schema: str) -> None: + """Backfill token counts for existing messages using batch updates.""" + connection = op.get_bind() + + # Initialize tokenizer once outside the loop for performance + tokenizer = None + with suppress(Exception): + tokenizer = tiktoken.get_encoding("cl100k_base") + + def _count_tokens(text: str) -> int: + """Count tokens in a text string using tiktoken.""" + if not text: + return 0 + if tokenizer: + with suppress(Exception): + return len(tokenizer.encode(text)) + # Fallback: rough estimation (4 chars per token) + return len(text) // 4 + + # Get all messages in batches to handle large datasets + batch_size = 1000 + offset = 0 + + while True: + result = connection.execute( + text( + f"SELECT id, content FROM {schema}.messages LIMIT :limit OFFSET :offset" + ), + {"limit": batch_size, "offset": offset}, + ) + messages = result.fetchall() + + if not messages: + break + + # Calculate token counts and update messages in batches + batch_updates = [] + for message_id, content in messages: + token_count = _count_tokens(content) + batch_updates.append((message_id, token_count)) + + # Process updates in smaller batches to stay under statement limits + update_batch_size = 200 + for i in range(0, len(batch_updates), update_batch_size): + batch_chunk = batch_updates[i : i + update_batch_size] + + # Use unnest arrays for clean batch update + ids = [item[0] for item in batch_chunk] + token_counts = [item[1] for item in batch_chunk] + + connection.execute( + text(f""" + UPDATE {schema}.messages + SET token_count = batch_data.token_count + FROM ( + SELECT UNNEST(:ids) as id, UNNEST(:token_counts) as token_count + ) AS batch_data + WHERE messages.id = batch_data.id + """), + {"ids": ids, "token_counts": token_counts}, + ) + + offset += batch_size + + +def restore_app_user_columns(schema: str, inspector) -> None: + """Restore app_id and user_id columns to peers and sessions.""" + # Add app_id back to peers + if not column_exists("peers", "app_id", inspector): + op.add_column( + "peers", + sa.Column("app_id", sa.TEXT(), nullable=True), + schema=schema, + ) + # Populate app_id from workspace_name + op.execute( + sa.text(f""" + UPDATE {schema}.peers SET app_id = ( + SELECT id FROM {schema}.workspaces WHERE name = peers.workspace_name + ) + """) + ) + op.alter_column("peers", "app_id", nullable=False, schema=schema) + + # Add app_id back to sessions + if not column_exists("sessions", "app_id", inspector): + op.add_column( + "sessions", + sa.Column("app_id", sa.TEXT(), nullable=True), + schema=schema, + ) + # Populate app_id from workspace_name + op.execute( + sa.text(f""" + UPDATE {schema}.sessions SET app_id = ( + SELECT id FROM {schema}.workspaces WHERE name = sessions.workspace_name + ) + """) + ) + op.alter_column("sessions", "app_id", nullable=False, schema=schema) + + # Add user_id back to sessions + if not column_exists("sessions", "user_id", inspector): + op.add_column( + "sessions", + sa.Column("user_id", sa.TEXT(), nullable=True), + schema=schema, + ) + # Populate user_id from session_peers + # The user peer is the one that existed before agent peers were created + # Agent peers have names that match their IDs (both are nanoids), user peers have different names and IDs + op.execute( + sa.text(f""" + UPDATE {schema}.sessions SET user_id = ( + SELECT p.id FROM {schema}.peers p + JOIN {schema}.session_peers sp ON p.name = sp.peer_name AND p.workspace_name = sp.workspace_name + WHERE sp.session_name = sessions.name + AND p.workspace_name = sessions.workspace_name + AND p.id != p.name + LIMIT 1 + ) + """) + ) + + op.alter_column("sessions", "user_id", nullable=False, schema=schema) + + +def restore_documents_table(schema: str, inspector) -> None: + """Restore documents table to pre-peer paradigm state.""" + # Add back id column as primary key + if not column_exists("documents", "temp_id", inspector): + op.add_column( + "documents", + sa.Column("temp_id", sa.BigInteger(), nullable=True), + schema=schema, + ) + + op.execute(f"CREATE SEQUENCE IF NOT EXISTS {schema}.documents_id_seq") + + op.execute(f""" + UPDATE {schema}.documents + SET temp_id = nextval('{schema}.documents_id_seq') + WHERE temp_id IS NULL + """) + + op.alter_column("documents", "temp_id", nullable=False, schema=schema) + + # Drop current primary key and rename columns + if constraint_exists("documents", "pk_documents", "primary", inspector): + op.drop_constraint("pk_documents", "documents", type_="primary", schema=schema) + + op.alter_column("documents", "id", new_column_name="public_id", schema=schema) + op.alter_column("documents", "temp_id", new_column_name="id", schema=schema) + op.alter_column( + "documents", "internal_metadata", new_column_name="metadata", schema=schema + ) + + op.create_primary_key("pk_documents", "documents", ["id"], schema=schema) + + op.alter_column( + "documents", + "id", + nullable=False, + server_default=sa.text(f"nextval('{schema}.documents_id_seq')"), + schema=schema, + ) + + op.execute( + f"ALTER SEQUENCE {schema}.documents_id_seq OWNED BY {schema}.documents.id" + ) + + # Add back user_id and app_id columns + if not column_exists("documents", "user_id", inspector): + op.add_column( + "documents", + sa.Column("user_id", sa.TEXT(), nullable=True), + schema=schema, + ) + # Populate from peer_name + op.execute( + sa.text(f""" + UPDATE {schema}.documents SET user_id = ( + SELECT id FROM {schema}.peers WHERE name = documents.peer_name AND workspace_name = documents.workspace_name + ) + """) + ) + op.alter_column("documents", "user_id", nullable=False, schema=schema) + + if not column_exists("documents", "app_id", inspector): + op.add_column( + "documents", + sa.Column("app_id", sa.TEXT(), nullable=True), + schema=schema, + ) + # Populate from workspace_name + op.execute( + sa.text(f""" + UPDATE {schema}.documents SET app_id = ( + SELECT id FROM {schema}.workspaces WHERE name = documents.workspace_name + ) + """) + ) + op.alter_column("documents", "app_id", nullable=False, schema=schema) + + # Drop new foreign keys + if fk_exists("documents", "fk_documents_collection_name_collections", inspector): + op.drop_constraint( + "fk_documents_collection_name_collections", + "documents", + type_="foreignkey", + schema=schema, + ) + if fk_exists("documents", "fk_documents_workspace_name_workspaces", inspector): + op.drop_constraint( + "fk_documents_workspace_name_workspaces", + "documents", + type_="foreignkey", + schema=schema, + ) + + op.execute( + sa.text(f""" + UPDATE {schema}.documents SET collection_name = ( + SELECT id FROM {schema}.collections WHERE name = documents.collection_name AND workspace_name = documents.workspace_name AND peer_name = documents.peer_name + ) + """) + ) + + # Restore collection_id from collection_name + op.alter_column( + "documents", "collection_name", new_column_name="collection_id", schema=schema + ) + + # Drop new columns + op.drop_column("documents", "peer_name", schema=schema) + op.drop_column("documents", "workspace_name", schema=schema) + + # Restore old foreign keys (only fk_ format) + op.create_foreign_key( + "fk_documents_collection_id_collections", + "documents", + "collections", + ["collection_id"], + ["id"], + referent_schema=schema, + ) + + # Restore old constraint names + if constraint_exists("documents", "id_length", "check", inspector): + op.drop_constraint("id_length", "documents", type_="check", schema=schema) + if constraint_exists("documents", "id_format", "check", inspector): + op.drop_constraint("id_format", "documents", type_="check", schema=schema) + + op.create_check_constraint( + "public_id_length", "documents", "length(public_id) = 21", schema=schema + ) + op.create_check_constraint( + "public_id_format", "documents", "public_id ~ '^[A-Za-z0-9_-]+$'", schema=schema + ) + + +def restore_collections_table(schema: str, inspector) -> None: + """Restore collections table to pre-peer paradigm state.""" + # Add back id column as primary key + if not column_exists("collections", "temp_id", inspector): + op.add_column( + "collections", + sa.Column("temp_id", sa.BigInteger()), + schema=schema, + ) + + op.execute(f"CREATE SEQUENCE IF NOT EXISTS {schema}.collections_id_seq") + + op.execute(f""" + UPDATE {schema}.collections + SET temp_id = nextval('{schema}.collections_id_seq') + WHERE temp_id IS NULL + """) + + op.alter_column("collections", "temp_id", nullable=False, schema=schema) + + # Drop current primary key and rename columns + if constraint_exists("collections", "pk_collections", "primary", inspector): + op.drop_constraint( + "pk_collections", "collections", type_="primary", schema=schema + ) + + op.alter_column("collections", "id", new_column_name="public_id", schema=schema) + op.alter_column("collections", "temp_id", new_column_name="id", schema=schema) + + op.create_primary_key("pk_collections", "collections", ["id"], schema=schema) + + op.alter_column( + "collections", + "id", + nullable=False, + server_default=sa.text(f"nextval('{schema}.collections_id_seq')"), + schema=schema, + ) + + op.execute( + f"ALTER SEQUENCE {schema}.collections_id_seq OWNED BY {schema}.collections.id" + ) + + # Add back user_id and app_id columns + if not column_exists("collections", "user_id", inspector): + op.add_column( + "collections", + sa.Column("user_id", sa.TEXT(), nullable=True), + schema=schema, + ) + # Populate from peer_name + op.execute( + sa.text(f""" + UPDATE {schema}.collections SET user_id = ( + SELECT id FROM {schema}.peers WHERE name = collections.peer_name AND workspace_name = collections.workspace_name + ) + """) + ) + op.alter_column("collections", "user_id", nullable=False, schema=schema) + + if not column_exists("collections", "app_id", inspector): + op.add_column( + "collections", + sa.Column("app_id", sa.TEXT(), nullable=True), + schema=schema, + ) + # Populate from workspace_name + op.execute( + sa.text(f""" + UPDATE {schema}.collections SET app_id = ( + SELECT id FROM {schema}.workspaces WHERE name = collections.workspace_name + ) + """) + ) + op.alter_column("collections", "app_id", nullable=False, schema=schema) + + # Drop new foreign keys + if fk_exists("collections", "fk_collections_peer_name_peers", inspector): + op.drop_constraint( + "fk_collections_peer_name_peers", + "collections", + type_="foreignkey", + schema=schema, + ) + if fk_exists("collections", "fk_collections_workspace_name_workspaces", inspector): + op.drop_constraint( + "fk_collections_workspace_name_workspaces", + "collections", + type_="foreignkey", + schema=schema, + ) + + # Drop new unique constraint + op.drop_constraint( + "unique_name_collection_peer", "collections", type_="unique", schema=schema + ) + + # Drop new columns + op.drop_column("collections", "peer_name", schema=schema) + op.drop_column("collections", "workspace_name", schema=schema) + op.drop_column("collections", "internal_metadata", schema=schema) + + # Restore old unique constraint + op.create_unique_constraint( + "unique_name_collection_user", + "collections", + ["name", "user_id"], + schema=schema, + ) + + # Restore old constraint names + if constraint_exists("collections", "id_length", "check", inspector): + op.drop_constraint("id_length", "collections", type_="check", schema=schema) + if constraint_exists("collections", "id_format", "check", inspector): + op.drop_constraint("id_format", "collections", type_="check", schema=schema) + + op.create_check_constraint( + "public_id_length", "collections", "length(public_id) = 21", schema=schema + ) + op.create_check_constraint( + "public_id_format", + "collections", + "public_id ~ '^[A-Za-z0-9_-]+$'", + schema=schema, + ) + + +def restore_messages_table(schema: str, inspector) -> None: + """Restore messages table to pre-peer paradigm state.""" + # Add back old columns + if not column_exists("messages", "session_id", inspector): + op.add_column( + "messages", + sa.Column("session_id", sa.Text(), nullable=True), + schema=schema, + ) + op.execute( + sa.text(f""" + UPDATE {schema}.messages SET session_id = ( + SELECT id FROM {schema}.sessions s WHERE s.name = messages.session_name + ) + """) + ) + op.alter_column("messages", "session_id", nullable=False, schema=schema) + + if not column_exists("messages", "user_id", inspector): + op.add_column( + "messages", + sa.Column("user_id", sa.TEXT(), nullable=True), + schema=schema, + ) + # Populate from peer_name + op.execute( + sa.text(f""" + UPDATE {schema}.messages SET user_id = ( + SELECT id FROM {schema}.peers WHERE name = messages.peer_name AND workspace_name = messages.workspace_name + ) + """) + ) + op.alter_column("messages", "user_id", nullable=False, schema=schema) + + if not column_exists("messages", "app_id", inspector): + op.add_column( + "messages", + sa.Column("app_id", sa.TEXT(), nullable=True), + schema=schema, + ) + # Populate from workspace_name + op.execute( + sa.text(f""" + UPDATE {schema}.messages SET app_id = ( + SELECT id FROM {schema}.workspaces WHERE name = messages.workspace_name + ) + """) + ) + op.alter_column("messages", "app_id", nullable=False, schema=schema) + + if not column_exists("messages", "is_user", inspector): + op.add_column( + "messages", + sa.Column("is_user", sa.Boolean(), nullable=True), + schema=schema, + ) + # Determine is_user based on whether peer_name matches session's user + op.execute( + sa.text(f""" + UPDATE {schema}.messages SET is_user = ( + SELECT CASE + WHEN s.user_id = messages.peer_name THEN true + ELSE false + END + FROM {schema}.sessions s + WHERE s.name = messages.session_name + ) + """) + ) + op.alter_column("messages", "is_user", nullable=False, schema=schema) + + # Drop new foreign keys + if fk_exists("messages", "fk_messages_session_name_sessions", inspector): + op.drop_constraint( + "fk_messages_session_name_sessions", + "messages", + type_="foreignkey", + schema=schema, + ) + if fk_exists("messages", "fk_messages_peer_name_peers", inspector): + op.drop_constraint( + "fk_messages_peer_name_peers", "messages", type_="foreignkey", schema=schema + ) + if fk_exists("messages", "fk_messages_workspace_name_workspaces", inspector): + op.drop_constraint( + "fk_messages_workspace_name_workspaces", + "messages", + type_="foreignkey", + schema=schema, + ) + + # Drop new indexes + if index_exists("messages", "ix_messages_peer_name", inspector): + op.drop_index("ix_messages_peer_name", table_name="messages", schema=schema) + if index_exists("messages", "ix_messages_workspace_name", inspector): + op.drop_index( + "ix_messages_workspace_name", table_name="messages", schema=schema + ) + # Drop full text search index + if index_exists("messages", "idx_messages_content_gin", inspector): + op.drop_index("idx_messages_content_gin", table_name="messages", schema=schema) + + # Drop new columns + op.drop_column("messages", "peer_name", schema=schema) + op.drop_column("messages", "workspace_name", schema=schema) + op.drop_column("messages", "session_name", schema=schema) + op.drop_column("messages", "token_count", schema=schema) + op.drop_column("messages", "internal_metadata", schema=schema) + + # Restore old foreign keys (only fk_ format) + op.create_foreign_key( + "fk_messages_session_id_sessions", + "messages", + "sessions", + ["session_id"], + ["id"], + referent_schema=schema, + ) + + +def restore_sessions_table(schema: str, inspector) -> None: + """Restore sessions table to pre-peer paradigm state.""" + # Add back id column as BigInteger primary key + if not column_exists("sessions", "temp_id", inspector): + op.add_column( + "sessions", + sa.Column("temp_id", sa.BigInteger()), + schema=schema, + ) + + op.execute(f"CREATE SEQUENCE IF NOT EXISTS {schema}.sessions_id_seq") + + op.execute(f""" + UPDATE {schema}.sessions + SET temp_id = nextval('{schema}.sessions_id_seq') + WHERE temp_id IS NULL + """) + + # Drop current primary key and rename columns + if constraint_exists("sessions", "pk_sessions", "primary", inspector): + op.drop_constraint("pk_sessions", "sessions", type_="primary", schema=schema) + + op.alter_column("sessions", "id", new_column_name="public_id", schema=schema) + op.alter_column("sessions", "temp_id", new_column_name="id", schema=schema) + + op.create_primary_key("pk_sessions", "sessions", ["id"], schema=schema) + + op.alter_column( + "sessions", + "id", + nullable=False, + server_default=sa.text(f"nextval('{schema}.sessions_id_seq')"), + schema=schema, + ) + + op.execute(f"ALTER SEQUENCE {schema}.sessions_id_seq OWNED BY {schema}.sessions.id") + + # Drop new foreign keys + if fk_exists("sessions", "fk_sessions_workspace_name_workspaces", inspector): + op.drop_constraint( + "fk_sessions_workspace_name_workspaces", + "sessions", + type_="foreignkey", + schema=schema, + ) + + # Drop new unique constraint + op.drop_constraint("unique_session_name", "sessions", type_="unique", schema=schema) + + # Drop new columns + op.drop_column("sessions", "name", schema=schema) + op.drop_column("sessions", "workspace_name", schema=schema) + op.drop_column("sessions", "configuration", schema=schema) + op.drop_column("sessions", "internal_metadata", schema=schema) + + # Restore old constraint names + if constraint_exists("sessions", "id_length", "check", inspector): + op.drop_constraint("id_length", "sessions", type_="check", schema=schema) + if constraint_exists("sessions", "id_format", "check", inspector): + op.drop_constraint("id_format", "sessions", type_="check", schema=schema) + if constraint_exists("sessions", "name_length", "check", inspector): + op.drop_constraint("name_length", "sessions", type_="check", schema=schema) + + op.create_check_constraint( + "public_id_length", "sessions", "length(public_id) = 21", schema=schema + ) + op.create_check_constraint( + "public_id_format", "sessions", "public_id ~ '^[A-Za-z0-9_-]+$'", schema=schema + ) + + +def restore_peers_table(schema: str, inspector) -> None: + """Restore peers table to pre-user paradigm state.""" + # Add back id column as BigInteger primary key + if not column_exists("peers", "temp_id", inspector): + op.add_column( + "peers", + sa.Column("temp_id", sa.BigInteger()), + schema=schema, + ) + + op.execute(f"CREATE SEQUENCE IF NOT EXISTS {schema}.peers_id_seq") + + op.execute(f""" + UPDATE {schema}.peers + SET temp_id = nextval('{schema}.peers_id_seq') + WHERE temp_id IS NULL + """) + + # Drop current primary key and rename columns + if constraint_exists("peers", "pk_peers", "primary", inspector): + op.drop_constraint("pk_peers", "peers", type_="primary", schema=schema) + + op.alter_column("peers", "id", new_column_name="public_id", schema=schema) + op.alter_column("peers", "temp_id", new_column_name="id", schema=schema) + + op.alter_column( + "peers", + "id", + nullable=False, + server_default=sa.text(f"nextval('{schema}.peers_id_seq')"), + schema=schema, + ) + + op.execute(f"ALTER SEQUENCE {schema}.peers_id_seq OWNED BY {schema}.peers.id") + op.create_primary_key("pk_users", "peers", ["id"], schema=schema) + + # Drop new foreign keys + if fk_exists("peers", "fk_peers_workspace_name_workspaces", inspector): + op.drop_constraint( + "fk_peers_workspace_name_workspaces", + "peers", + type_="foreignkey", + schema=schema, + ) + + # Drop new unique constraint and index + if constraint_exists("peers", "unique_name_workspace_peer", "unique", inspector): + op.drop_constraint( + "unique_name_workspace_peer", "peers", type_="unique", schema=schema + ) + if index_exists("peers", "idx_peers_workspace_lookup", inspector): + op.drop_index("idx_peers_workspace_lookup", table_name="peers", schema=schema) + + # Restore old unique constraint and index + op.create_unique_constraint( + "unique_name_app_user", "peers", ["name", "app_id"], schema=schema + ) + op.create_index("idx_users_app_lookup", "peers", ["app_id", "name"], schema=schema) + + # Drop new columns + op.drop_column("peers", "workspace_name", schema=schema) + op.drop_column("peers", "configuration", schema=schema) + op.drop_column("peers", "internal_metadata", schema=schema) + + # Restore old constraint names + if constraint_exists("peers", "id_length", "check", inspector): + op.drop_constraint("id_length", "peers", type_="check", schema=schema) + if constraint_exists("peers", "id_format", "check", inspector): + op.drop_constraint("id_format", "peers", type_="check", schema=schema) + + op.create_check_constraint( + "public_id_length", "peers", "length(public_id) = 21", schema=schema + ) + op.create_check_constraint( + "public_id_format", "peers", "public_id ~ '^[A-Za-z0-9_-]+$'", schema=schema + ) + + +def restore_workspaces_table(schema: str, inspector) -> None: + """Restore workspaces table to pre-peer paradigm state (apps).""" + # Add back id column as BigInteger primary key + if not column_exists("workspaces", "temp_id", inspector): + op.add_column( + "workspaces", + sa.Column("temp_id", sa.BigInteger()), + schema=schema, + ) + + op.execute(f"CREATE SEQUENCE IF NOT EXISTS {schema}.workspaces_id_seq") + + op.execute(f""" + UPDATE {schema}.workspaces + SET temp_id = nextval('{schema}.workspaces_id_seq') + WHERE temp_id IS NULL + """) + + # Drop current primary key and rename columns + if constraint_exists("workspaces", "pk_workspaces", "primary", inspector): + op.drop_constraint( + "pk_workspaces", "workspaces", type_="primary", schema=schema + ) + + op.alter_column("workspaces", "id", new_column_name="public_id", schema=schema) + op.alter_column("workspaces", "temp_id", new_column_name="id", schema=schema) + + op.create_primary_key("pk_apps", "workspaces", ["id"], schema=schema) + + op.alter_column( + "workspaces", + "id", + nullable=False, + server_default=sa.text(f"nextval('{schema}.workspaces_id_seq')"), + schema=schema, + ) + op.execute( + f"ALTER SEQUENCE {schema}.workspaces_id_seq OWNED BY {schema}.workspaces.id" + ) + + # Drop new columns + op.drop_column("workspaces", "configuration", schema=schema) + op.drop_column("workspaces", "internal_metadata", schema=schema) + + # Restore old constraint names + if constraint_exists("workspaces", "id_length", "check", inspector): + op.drop_constraint("id_length", "workspaces", type_="check", schema=schema) + if constraint_exists("workspaces", "id_format", "check", inspector): + op.drop_constraint("id_format", "workspaces", type_="check", schema=schema) + + op.create_check_constraint( + "public_id_length", "workspaces", "length(public_id) = 21", schema=schema + ) + op.create_check_constraint( + "public_id_format", + "workspaces", + "public_id ~ '^[A-Za-z0-9_-]+$'", + schema=schema, + ) + + +def restore_queue_and_active_queue_sessions_tables(schema: str, inspector) -> None: + """Restore queue and active_queue_sessions tables to pre-peer paradigm state.""" + + connection = op.get_bind() + + # Create reverse mapping from session.public_id (text) back to session.id (BigInteger) + # At this point in downgrade, sessions table still has both id (BigInteger) and public_id (text) + session_id_reverse_mapping = {} + if table_exists("sessions", inspector): + sessions_mapping = connection.execute( + sa.text(f"SELECT id, public_id FROM {schema}.sessions") + ).fetchall() + + for big_int_id, text_id in sessions_mapping: + session_id_reverse_mapping[text_id] = big_int_id + + # Update queue table + if table_exists("queue", inspector) and session_id_reverse_mapping: + # Get current session_id values in queue table (they are text now) + queue_session_ids = connection.execute( + sa.text( + f"SELECT DISTINCT session_id FROM {schema}.queue WHERE session_id IS NOT NULL" + ) + ).fetchall() + + # Convert session_id values back to BigInteger + for (session_id,) in queue_session_ids: + if session_id in session_id_reverse_mapping: + old_id = session_id_reverse_mapping[session_id] + connection.execute( + sa.text( + f"UPDATE {schema}.queue SET session_id = :old_id WHERE session_id = :new_id" + ), + {"old_id": str(old_id), "new_id": str(session_id)}, + ) + + # Change column type back to BigInteger + op.alter_column( + "queue", + "session_id", + type_=sa.BigInteger(), + existing_type=sa.Text(), + postgresql_using="session_id::bigint", + ) + + # Update active_queue_sessions table + if table_exists("active_queue_sessions", inspector) and session_id_reverse_mapping: + # Get current session_id values in active_queue_sessions table (they are text now) + active_queue_session_ids = connection.execute( + sa.text( + f"SELECT DISTINCT session_id FROM {schema}.active_queue_sessions WHERE session_id IS NOT NULL" + ) + ).fetchall() + + if constraint_exists( + "active_queue_sessions", "pk_active_queue_sessions", "primary", inspector + ): + op.drop_constraint( + "pk_active_queue_sessions", + "active_queue_sessions", + type_="primary", + schema=schema, + ) + + # Convert session_id values back to BigInteger + for (session_id,) in active_queue_session_ids: + if session_id in session_id_reverse_mapping: + old_id = session_id_reverse_mapping[session_id] + connection.execute( + sa.text( + f"UPDATE {schema}.active_queue_sessions SET session_id = :old_id WHERE session_id = :new_id" + ), + {"old_id": str(old_id), "new_id": str(session_id)}, + ) + + # Change column type back to BigInteger + op.alter_column( + "active_queue_sessions", + "session_id", + type_=sa.BigInteger(), + existing_type=sa.Text(), + postgresql_using="session_id::bigint", + ) + + op.create_primary_key( + "pk_active_queue_sessions", + "active_queue_sessions", + ["session_id"], + schema=schema, + ) + + if constraint_exists( + "active_queue_sessions", + "unique_active_queue_session", + "unique", + inspector, + ): + op.drop_constraint( + "unique_active_queue_session", + "active_queue_sessions", + type_="unique", + schema=schema, + ) + + if column_exists("active_queue_sessions", "id", inspector): + op.drop_column("active_queue_sessions", "id", schema=schema) + if column_exists("active_queue_sessions", "sender_name", inspector): + op.drop_column("active_queue_sessions", "sender_name", schema=schema) + if column_exists("active_queue_sessions", "target_name", inspector): + op.drop_column("active_queue_sessions", "target_name", schema=schema) + if column_exists("active_queue_sessions", "task_type", inspector): + op.drop_column("active_queue_sessions", "task_type", schema=schema) + + # Restore foreign key constraints + if table_exists("queue", inspector): + op.create_foreign_key( + "fk_queue_session_id_sessions", + "queue", + "sessions", + ["session_id"], + ["id"], + referent_schema=schema, + ) + + if table_exists("active_queue_sessions", inspector): + op.create_foreign_key( + "fk_active_queue_sessions_session_id_sessions", + "active_queue_sessions", + "sessions", + ["session_id"], + ["id"], + referent_schema=schema, + ) + + +def restore_table_names(schema: str, inspector) -> None: + """Restore table names: workspaces->apps and peers->users.""" + if inspector.has_table("workspaces", schema=schema): + op.rename_table("workspaces", "apps", schema=schema) + if inspector.has_table("peers", schema=schema): + op.rename_table("peers", "users", schema=schema) + + +def restore_foreign_keys(schema: str) -> None: + op.create_foreign_key( + "fk_documents_user_id_users", + "documents", + "users", + ["user_id"], + ["public_id"], + referent_schema=schema, + ) + + op.create_foreign_key( + "fk_documents_app_id_apps", + "documents", + "apps", + ["app_id"], + ["public_id"], + referent_schema=schema, + ) + op.create_foreign_key( + "fk_collections_user_id_users", + "collections", + "users", + ["user_id"], + ["public_id"], + referent_schema=schema, + ) + op.create_foreign_key( + "fk_collections_app_id_apps", + "collections", + "apps", + ["app_id"], + ["public_id"], + referent_schema=schema, + ) + op.create_foreign_key( + "fk_messages_app_id_apps", + "messages", + "apps", + ["app_id"], + ["public_id"], + referent_schema=schema, + ) + op.create_foreign_key( + "fk_messages_user_id_users", + "messages", + "users", + ["user_id"], + ["public_id"], + referent_schema=schema, + ) + op.create_foreign_key( + "fk_sessions_app_id_apps", + "sessions", + "apps", + ["app_id"], + ["public_id"], + referent_schema=schema, + ) + op.create_foreign_key( + "fk_sessions_user_id_users", + "sessions", + "users", + ["user_id"], + ["public_id"], + referent_schema=schema, + ) + op.create_foreign_key( + "fk_users_app_id_apps", + "users", + "apps", + ["app_id"], + ["public_id"], + referent_schema=schema, + ) diff --git a/pyproject.toml b/pyproject.toml index cf446e52..f0b5b00b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "honcho" -version = "1.1.0" +version = "2.0.0" description = "Honcho Server" authors = [ {name = "Plastic Labs", email = "hello@plasticlabs.ai"}, @@ -25,6 +25,7 @@ dependencies = [ "langfuse>=2.57.1", "pyjwt>=2.10.0", "google-genai>=1.10.0", + "tiktoken>=0.9.0", ] [tool.uv] dev-dependencies = [ diff --git a/src/agent.py b/src/agent.py index bb53aa90..0ddb5e8c 100644 --- a/src/agent.py +++ b/src/agent.py @@ -2,7 +2,6 @@ import asyncio import json import logging import os -from collections.abc import Iterable from typing import Any, Optional import sentry_sdk @@ -24,8 +23,6 @@ from src.utils.model_client import ModelClient, ModelProvider # Configure logging logger = logging.getLogger(__name__) -USER_REPRESENTATION_METAMESSAGE_TYPE = "honcho_user_representation" - DEF_DIALECTIC_PROVIDER = ModelProvider.ANTHROPIC DEF_DIALECTIC_MODEL = "claude-3-7-sonnet-20250219" @@ -131,9 +128,9 @@ class Dialectic: @observe() async def chat( - app_id: str, - user_id: str, - session_id: str, + workspace_name: str, + peer_name: str, + session_name: str | None, queries: str | list[str], stream: bool = False, ) -> schemas.DialecticResponse | MessageStreamManager: @@ -148,58 +145,77 @@ async def chat( 3. Combines both into a fresh user representation 4. Uses this representation to answer the query 5. Saves the representation for future use + + Args: + workspace_name: The workspace name + peer_name: The peer name + session_name: The session name. If None, this queries the global representation. + queries: The queries to ask the Dialectic API + stream: Whether to stream the response + + Returns: + Either a string or a stream of messages from the LLM provider, depending on the stream flag """ # Format the query string questions = [queries] if isinstance(queries, str) else queries final_query = "\n".join(questions) if len(questions) > 1 else questions[0] - logger.debug(f"Received query: {final_query} for session {session_id}") + logger.debug(f"Received query: {final_query} for session {session_name}") logger.debug("Starting on-demand user representation generation") start_time = asyncio.get_event_loop().time() # Setup phase - create resources we'll need for all operations - # 1. Fetch latest user message & chat history + # 1. Fetch latest peer message & chat history async with tracked_db("chat.load_history") as db_history: stmt = ( select(models.Message) - .where(models.Message.app_id == app_id) - .where(models.Message.user_id == user_id) - .where(models.Message.session_id == session_id) - .where(models.Message.is_user) + .where(models.Message.workspace_name == workspace_name) + .where(models.Message.peer_name == peer_name) .order_by(models.Message.id.desc()) .limit(1) ) + if session_name: + stmt = stmt.where(models.Message.session_name == session_name) latest_messages = await db_history.execute(stmt) latest_message = latest_messages.scalar_one_or_none() latest_message_id = latest_message.public_id if latest_message else None - logger.debug(f"Latest user message ID: {latest_message_id}") - - chat_history, _, _ = await history.get_summarized_history( - db_history, session_id, summary_type=history.SummaryType.SHORT - ) - if not chat_history: - logger.warning(f"No chat history found for session {session_id}") - chat_history = f"someone asked this about the user's message: {final_query}" - logger.debug(f"IDs: {app_id}, {user_id}, {session_id}") - message_count = len(chat_history.split("\n")) - logger.debug(f"Retrieved chat history: {message_count} messages") + if session_name: + chat_history = await history.get_summarized_history( + db_history, + workspace_name, + session_name, + peer_name, + summary_type=history.SummaryType.SHORT, + ) + if not chat_history: + logger.warning(f"No chat history found for session {session_name}") + chat_history = ( + f"someone asked this about the user's message: {final_query}" + ) + logger.debug( + f"Workspace: {workspace_name}, Peer: {peer_name}, Session: {session_name}" + ) + else: + chat_history = "" + logger.debug("Retrieved chat history: %s lines", len(chat_history.split("\n"))) # Run short-term inference and long-term facts in parallel async def fetch_long_term(): async with tracked_db("chat.get_collection") as db_embed: - collection = await crud.get_or_create_user_protected_collection( - db_embed, app_id, user_id + name = "global_representation" if session_name is None else "" + collection = await crud.get_or_create_collection( + db_embed, workspace_name, peer_name, collection_name=name ) - collection_id = ( - collection.public_id - ) # Extract the ID while session is active - facts = await get_long_term_facts(final_query, app_id, user_id, collection_id) + collection_name = collection.name # Extract the ID while session is active + facts = await get_long_term_facts( + final_query, workspace_name, peer_name, collection_name + ) return facts long_term_task = asyncio.create_task(fetch_long_term()) - short_term_task = asyncio.create_task(run_tom_inference(chat_history, session_id)) + short_term_task = asyncio.create_task(run_tom_inference(chat_history)) facts, tom_inference = await asyncio.gather(long_term_task, short_term_task) logger.debug(f"Retrieved {len(facts)} facts from long-term memory") @@ -209,9 +225,9 @@ async def chat( logger.debug("Generating user representation") async with tracked_db("chat.generate_user_representation") as db_rep: user_representation = await generate_user_representation( - app_id=app_id, - user_id=user_id, - session_id=session_id, + workspace_name, + peer_name, + session_name, chat_history=chat_history, tom_inference=tom_inference, facts=facts, @@ -234,8 +250,8 @@ async def chat( logger.debug(f"User representation generation completed in {generation_time:.2f}s") langfuse_context.update_current_trace( - session_id=session_id, - user_id=user_id, + session_id=session_name, + user_id=peer_name, release=os.getenv("SENTRY_RELEASE"), metadata={"environment": os.getenv("SENTRY_ENVIRONMENT")}, ) @@ -260,14 +276,19 @@ async def chat( async def get_long_term_facts( - query: str, app_id: str, user_id: str, collection_id: str + query: str, + workspace_name: str, + peer_name: str, + collection_name: str, ) -> list[str]: """ Generate queries based on the dialectic query and retrieve relevant facts. Args: query: The user query - embedding_store: The embedding store to search + workspace_name: The workspace name + peer_name: The peer name + collection_name: The collection name Returns: List of retrieved facts @@ -285,9 +306,9 @@ async def get_long_term_facts( logger.debug(f"Starting query {i + 1}/{len(search_queries)}: {search_query}") query_start = asyncio.get_event_loop().time() query_embedding_store = CollectionEmbeddingStore( - app_id=app_id, - user_id=user_id, - collection_id=collection_id, + workspace_name=workspace_name, + peer_name=peer_name, + collection_name=collection_name, ) facts = await query_embedding_store.get_relevant_facts( search_query, top_k=10, max_distance=0.85 @@ -314,24 +335,23 @@ async def get_long_term_facts( return list(retrieved_facts) -async def run_tom_inference(chat_history: str, session_id: str) -> str: +async def run_tom_inference(chat_history: str) -> str: """ Run ToM inference on chat history. Args: chat_history: The chat history - session_id: The session ID Returns: The ToM inference """ # Run ToM inference - logger.debug(f"Running ToM inference for session {session_id}") + logger.debug("Running ToM inference") tom_start_time = asyncio.get_event_loop().time() # Get chat history length to determine if this is a new conversation tom_inference_response = await get_tom_inference( - chat_history, session_id, method="single_prompt", user_representation="" + chat_history, user_representation="", method="single_prompt" ) # Extract the prediction from the response @@ -407,9 +427,9 @@ async def generate_semantic_queries(query: str) -> list[str]: async def generate_user_representation( - app_id: str, - user_id: str, - session_id: str, + workspace_name: str, + peer_name: str, + session_name: str | None, chat_history: str, tom_inference: str, facts: list[str], @@ -419,8 +439,9 @@ async def generate_user_representation( ) -> str: """ Generate a user representation by combining long-term facts and short-term context. - Optionally save it as a metamessage if message_id is provided. - Only uses existing representations from the same session for continuity. + Save it to peer metadata if no session is provided (global-level), or save it to + session-peers table metadata if a session is provided (local-level). + If session-level, uses existing representations from the same session for continuity. Returns: The generated user representation. @@ -429,24 +450,10 @@ async def generate_user_representation( rep_start_time = asyncio.get_event_loop().time() if with_inference: - # Fetch the latest user representation from the same session - logger.debug(f"Fetching latest representation for session {session_id}") - latest_representation_stmt = ( - select(models.Metamessage) - .where( - models.Metamessage.session_id == session_id - ) # only from the same session - .where(models.Metamessage.label == USER_REPRESENTATION_METAMESSAGE_TYPE) - .order_by(models.Metamessage.id.desc()) - .limit(1) - ) - result = await db.execute(latest_representation_stmt) - latest_representation_obj = result.scalar_one_or_none() - latest_representation = ( - latest_representation_obj.content - if latest_representation_obj - else "No user representation available." + latest_representation = await crud.get_working_representation( + db, workspace_name, peer_name, session_name ) + logger.debug( f"Found previous representation: {len(latest_representation)} characters" ) @@ -457,7 +464,6 @@ async def generate_user_representation( gen_start_time = asyncio.get_event_loop().time() user_representation_response = await get_user_representation_long_term( chat_history=chat_history, - session_id=session_id, facts=facts, user_representation=latest_representation, tom_inference=tom_inference, @@ -479,36 +485,24 @@ RELEVANT LONG-TERM FACTS ABOUT THE USER: {facts} """ logger.debug(f"Representation: {representation}") - # If message_id is provided, save the representation as a metamessage + # If message_id is provided, save the representation as metadata if not representation: logger.debug("Empty representation, skipping save") + elif not message_id: + logger.debug("No message_id, skipping save") else: logger.debug(f"Saving representation to message_id: {message_id}") save_start = asyncio.get_event_loop().time() try: - # First check if message exists - message_check_stmt = select(models.Message).where( - models.Message.public_id == message_id + await crud.set_working_representation( + db, + representation, + workspace_name, + peer_name, + session_name, ) - message_check = await db.execute(message_check_stmt) - message_exists = message_check.scalar_one_or_none() is not None - - if not message_exists: - message_id = None - else: - metamessage = models.Metamessage( - app_id=app_id, - user_id=user_id, - session_id=session_id, - message_id=message_id if message_id else None, - label=USER_REPRESENTATION_METAMESSAGE_TYPE, - content=representation, - h_metadata={}, - ) - db.add(metamessage) - await db.commit() - save_time = asyncio.get_event_loop().time() - save_start - logger.debug(f"Representation saved in {save_time:.2f}s") + save_time = asyncio.get_event_loop().time() - save_start + logger.debug(f"Representation saved in {save_time:.2f}s") except Exception as e: logger.error(f"Error during save DB operation: {str(e)}") await db.rollback() diff --git a/src/crud.py b/src/crud.py index f14107cf..d696b86a 100644 --- a/src/crud.py +++ b/src/crud.py @@ -1,21 +1,18 @@ +import os from collections.abc import Sequence from logging import getLogger from typing import Optional from dotenv import load_dotenv +from nanoid import generate as generate_nanoid from openai import AsyncOpenAI -from sqlalchemy import Select, cast, insert, select -from sqlalchemy.exc import IntegrityError +from sqlalchemy import Select, cast, func, insert, select, update +from sqlalchemy.dialects.postgresql import insert as pg_insert from sqlalchemy.ext.asyncio import AsyncSession -from sqlalchemy.sql import func from sqlalchemy.types import BigInteger from . import models, schemas -from .exceptions import ( - ConflictException, - ResourceNotFoundException, - ValidationException, -) +from .exceptions import ResourceNotFoundException load_dotenv(override=True) @@ -23,297 +20,295 @@ openai_client = AsyncOpenAI() logger = getLogger(__name__) -DEF_PROTECTED_COLLECTION_NAME = "honcho" +USER_REPRESENTATION_METADATA_KEY = "user_representation" + +SESSION_PEERS_LIMIT = int(os.getenv("SESSION_PEERS_LIMIT", 10)) ######################################################## -# app methods +# workspace methods ######################################################## -async def get_app(db: AsyncSession, app_id: str) -> models.App: +async def get_or_create_workspace( + db: AsyncSession, workspace: schemas.WorkspaceCreate +) -> models.Workspace: """ - Get an app by its ID. + Get an existing workspace or create a new one if it doesn't exist. Args: db: Database session - app_id: Public ID of the app + workspace: Workspace creation schema Returns: - The app if found + The workspace if found or created Raises: - ResourceNotFoundException: If the app does not exist + ConflictException: If there's an integrity error when creating the workspace """ - stmt = select(models.App).where(models.App.public_id == app_id) + # Try to get the existing workspace + stmt = select(models.Workspace).where(models.Workspace.name == workspace.name) result = await db.execute(stmt) - app = result.scalar_one_or_none() - if app is None: - logger.warning(f"App with ID {app_id} not found") - raise ResourceNotFoundException(f"App with ID {app_id} not found") - return app + existing_workspace = result.scalar_one_or_none() + + if existing_workspace is not None: + # Workspace already exists + logger.debug(f"Found existing workspace: {workspace.name}") + return existing_workspace + + # Workspace doesn't exist, create a new one + honcho_workspace = models.Workspace( + name=workspace.name, + h_metadata=workspace.metadata, + configuration=workspace.configuration, + ) + db.add(honcho_workspace) + await db.commit() + logger.info(f"Workspace created successfully: {workspace.name}") + return honcho_workspace -async def get_all_apps( - reverse: Optional[bool] = False, +async def get_all_workspaces( filter: Optional[dict] = None, ) -> Select: """ - Get all apps. + Get all workspaces. Args: db: Database session - reverse: Whether to reverse the order of the apps - filter: Filter the apps by a dictionary of metadata + filter: Filter the workspaces by a dictionary of metadata """ - stmt = select(models.App) - if reverse: - stmt = stmt.order_by(models.App.id.desc()) - else: - stmt = stmt.order_by(models.App.id) + stmt = select(models.Workspace) if filter is not None: - stmt = stmt.where(models.App.h_metadata.contains(filter)) + stmt = stmt.where(models.Workspace.h_metadata.contains(filter)) + stmt = stmt.order_by(models.Workspace.created_at) return stmt -async def get_app_by_name(db: AsyncSession, name: str) -> models.App: +async def update_workspace( + db: AsyncSession, workspace_name: str, workspace: schemas.WorkspaceUpdate +) -> models.Workspace: """ - Get an app by its name. + Update a workspace. Args: db: Database session - name: Name of the app + workspace_name: Name of the workspace + workspace: Workspace update schema Returns: - The app if found - - Raises: - ResourceNotFoundException: If the app does not exist + The updated workspace """ - stmt = select(models.App).where(models.App.name == name) + honcho_workspace = await get_or_create_workspace( + db, + schemas.WorkspaceCreate( + name=workspace_name, + metadata=workspace.metadata or {}, # Provide empty dict if metadata is None + ), + ) + + if workspace.metadata is not None: + honcho_workspace.h_metadata = workspace.metadata + + if workspace.configuration is not None: + honcho_workspace.configuration = workspace.configuration + + await db.commit() + logger.info(f"Workspace with id {honcho_workspace.id} updated successfully") + return honcho_workspace + + +######################################################## +# peer methods +######################################################## + + +async def get_or_create_peers( + db: AsyncSession, + workspace_name: str, + peers: list[schemas.PeerCreate], +) -> list[models.Peer]: + """ + Get an existing list of peers or create new peers if they don't exist. + Updates existing peers with metadata and configuration if provided. + + Args: + db: Database session + workspace_name: Name of the workspace + peers: List of peer creation schemas + + Returns: + List of peers if found or created + """ + peer_names = [p.name for p in peers] + stmt = ( + select(models.Peer) + .where(models.Peer.workspace_name == workspace_name) + .where(models.Peer.name.in_(peer_names)) + ) result = await db.execute(stmt) - app = result.scalar_one_or_none() - if app is None: - logger.warning(f"App with name '{name}' not found") - raise ResourceNotFoundException(f"App with name '{name}' not found") - return app + existing_peers = list(result.scalars().all()) + # Create a mapping of peer names to peer schemas for easy lookup + peer_schema_map = {p.name: p for p in peers} -# def get_apps(db: AsyncSession) -> Sequence[models.App]: -# return db.query(models.App).all() + # Update existing peers with metadata and configuration if provided + for existing_peer in existing_peers: + peer_schema = peer_schema_map[existing_peer.name] + # Update with metadata and configuration if provided + if peer_schema.metadata is not None: + existing_peer.h_metadata = peer_schema.metadata -async def create_app(db: AsyncSession, app: schemas.AppCreate) -> models.App: - """ - Create a new app. + if peer_schema.configuration is not None: + existing_peer.configuration = peer_schema.configuration - Args: - db: Database session - app: App creation schema + # Find which peers need to be created + existing_names = {p.name for p in existing_peers} + peers_to_create = [p for p in peers if p.name not in existing_names] - Returns: - The created app - - Raises: - ConflictException: If an app with the same name already exists - """ - try: - honcho_app = models.App(name=app.name, h_metadata=app.metadata) - db.add(honcho_app) - await db.commit() - logger.info(f"App created successfully: {app.name}") - return honcho_app - except IntegrityError as e: - await db.rollback() - logger.warning(f"IntegrityError creating app with name '{app.name}': {str(e)}") - raise ConflictException(f"App with name '{app.name}' already exists") from e - - -async def update_app( - db: AsyncSession, app_id: str, app: schemas.AppUpdate -) -> models.App: - """ - Update an app. - - Args: - db: Database session - app_id: Public ID of the app - app: App update schema - - Returns: - The updated app - - Raises: - ResourceNotFoundException: If the app does not exist - """ - try: - honcho_app = await get_app(db, app_id) - - if app.name is not None: - honcho_app.name = app.name - if app.metadata is not None: - honcho_app.h_metadata = app.metadata - - await db.commit() - logger.info(f"App with ID {app_id} updated successfully") - return honcho_app - except IntegrityError as e: - await db.rollback() - logger.warning(f"IntegrityError updating app {app_id}: {str(e)}") - raise ConflictException( - "App update failed - unique constraint violation" - ) from e - - -######################################################## -# user methods -######################################################## - - -async def create_user( - db: AsyncSession, app_id: str, user: schemas.UserCreate -) -> models.User: - """ - Create a new user. - - Args: - db: Database session - app_id: Public ID of the app - user: User creation schema - - Returns: - The created user - - Raises: - ConflictException: If a user with the same name already exists in this app - """ - try: - honcho_user = models.User( - app_id=app_id, - name=user.name, - h_metadata=user.metadata, + # Create new peers + new_peers = [ + models.Peer( + workspace_name=workspace_name, + name=p.name, + h_metadata=p.metadata or {}, + configuration=p.configuration or {}, ) - db.add(honcho_user) - await db.commit() - logger.info(f"User created successfully: {user.name} for app {app_id}") - return honcho_user - except IntegrityError as e: - await db.rollback() - logger.warning(f"Failed to create user - integrity error: {str(e)}") - raise ConflictException("User with this name already exists") from e + for p in peers_to_create + ] + db.add_all(new_peers) + + await db.commit() + + # Return combined list of existing and new peers + return existing_peers + new_peers -async def get_user(db: AsyncSession, app_id: str, user_id: str) -> models.User: +async def get_peer( + db: AsyncSession, + workspace_name: str, + peer: schemas.PeerCreate, +) -> models.Peer: """ - Get a user by app ID and user ID. + Get an existing peer. Args: db: Database session - app_id: Public ID of the app - user_id: Public ID of the user + workspace_name: Name of the workspace + peer: Peer creation schema Returns: - The user if found + The peer if found or created Raises: - ResourceNotFoundException: If the user does not exist + ResourceNotFoundException: If the peer does not exist """ + # Try to get the existing peer stmt = ( - select(models.User) - .where(models.User.app_id == app_id) - .where(models.User.public_id == user_id) + select(models.Peer) + .where(models.Peer.workspace_name == workspace_name) + .where(models.Peer.name == peer.name) ) result = await db.execute(stmt) - user = result.scalar_one_or_none() - if user is None: - logger.warning(f"User with ID '{user_id}' not found in app {app_id}") - raise ResourceNotFoundException(f"User with ID '{user_id}' not found") - return user + existing_peer = result.scalar_one_or_none() + if existing_peer is not None: + return existing_peer -async def get_user_by_name(db: AsyncSession, app_id: str, name: str) -> models.User: - """ - Get a user by app ID and name. - - Args: - db: Database session - app_id: Public ID of the app - name: Name of the user - - Returns: - The user if found - - Raises: - ResourceNotFoundException: If the user does not exist - """ - stmt = ( - select(models.User) - .where(models.User.app_id == app_id) - .where(models.User.name == name) + raise ResourceNotFoundException( + f"Peer {peer.name} not found in workspace {workspace_name}" ) - result = await db.execute(stmt) - user = result.scalar_one_or_none() - if user is None: - logger.warning(f"User with name '{name}' not found in app {app_id}") - raise ResourceNotFoundException(f"User with name '{name}' not found") - return user -async def get_users( - app_id: str, - reverse: bool = False, +async def get_peers( + workspace_name: str, filter: Optional[dict] = None, ) -> Select: - stmt = select(models.User).where(models.User.app_id == app_id) + stmt = select(models.Peer).where(models.Peer.workspace_name == workspace_name) if filter is not None: - stmt = stmt.where(models.User.h_metadata.contains(filter)) + stmt = stmt.where(models.Peer.h_metadata.contains(filter)) - if reverse: - stmt = stmt.order_by(models.User.id.desc()) - else: - stmt = stmt.order_by(models.User.id) + stmt = stmt.order_by(models.Peer.created_at) return stmt -async def update_user( - db: AsyncSession, app_id: str, user_id: str, user: schemas.UserUpdate -) -> models.User: +async def update_peer( + db: AsyncSession, workspace_name: str, peer_name: str, peer: schemas.PeerUpdate +) -> models.Peer: """ - Update a user. + Update a peer. Args: db: Database session - app_id: Public ID of the app - user_id: Public ID of the user - user: User update schema + workspace_name: Name of the workspace + peer_name: Name of the peer + peer: Peer update schema Returns: - The updated user + The updated peer Raises: - ResourceNotFoundException: If the user does not exist + ResourceNotFoundException: If the peer does not exist ValidationException: If the update data is invalid ConflictException: If the update violates a unique constraint """ - try: - # get_user will raise ResourceNotFoundException if not found - honcho_user = await get_user(db, app_id, user_id) + honcho_peer = ( + await get_or_create_peers( + db, workspace_name, [schemas.PeerCreate(name=peer_name)] + ) + )[0] - if user.name is not None: - honcho_user.name = user.name - if user.metadata is not None: - honcho_user.h_metadata = user.metadata + if peer.metadata is not None: + honcho_peer.h_metadata = peer.metadata - await db.commit() - logger.info(f"User {user_id} updated successfully") - return honcho_user - except IntegrityError as e: - await db.rollback() - logger.warning(f"User update failed due to integrity error: {str(e)}") - raise ConflictException( - "User update failed - unique constraint violation" - ) from e + if peer.configuration is not None: + honcho_peer.configuration = peer.configuration + + await db.commit() + logger.info(f"Peer {peer_name} updated successfully") + return honcho_peer + + +async def get_sessions_for_peer( + workspace_name: str, + peer_name: str, + is_active: Optional[bool] = None, + filter: Optional[dict] = None, +) -> Select: + """ + Get all sessions for a peer through the session_peers relationship. + + Args: + workspace_name: Name of the workspace + peer_name: Name of the peer + is_active: Filter by active status (True/False/None for all) + filter: Filter sessions by metadata + + Returns: + SQLAlchemy Select statement + """ + stmt = ( + select(models.Session) + .join( + models.SessionPeer, + (models.Session.name == models.SessionPeer.session_name) + & (models.Session.workspace_name == models.SessionPeer.workspace_name), + ) + .where(models.SessionPeer.peer_name == peer_name) + .where(models.Session.workspace_name == workspace_name) + ) + + if is_active is not None: + stmt = stmt.where(models.Session.is_active == is_active) + + if filter is not None: + stmt = stmt.where(models.Session.h_metadata.contains(filter)) + + stmt = stmt.order_by(models.Session.created_at) + + return stmt ######################################################## @@ -321,54 +316,15 @@ async def update_user( ######################################################## -async def get_session( - db: AsyncSession, - app_id: str, - session_id: str, - user_id: Optional[str] = None, -) -> models.Session: - """ - Get a session by ID for a specific user and app. - - Args: - db: Database session - app_id: Public ID of the app - session_id: Public ID of the session - user_id: Optional public ID of the user - - Returns: - The session if found - - Raises: - ResourceNotFoundException: If the session does not exist or doesn't belong to the user - """ - stmt = ( - select(models.Session) - .where(models.Session.app_id == app_id) - .where(models.Session.public_id == session_id) - ) - if user_id is not None: - stmt = stmt.where(models.Session.user_id == user_id) - result = await db.execute(stmt) - session = result.scalar_one_or_none() - if session is None: - logger.warning(f"Session with ID '{session_id}' not found for user {user_id}") - raise ResourceNotFoundException("Session not found or does not belong to user") - return session - - async def get_sessions( - app_id: str, - user_id: str, - reverse: Optional[bool] = False, + workspace_name: str, is_active: Optional[bool] = False, filter: Optional[dict] = None, ) -> Select: - stmt = ( - select(models.Session) - .where(models.Session.app_id == app_id) - .where(models.Session.user_id == user_id) - ) + """ + Get all sessions in a workspace. + """ + stmt = select(models.Session).where(models.Session.workspace_name == workspace_name) if is_active: stmt = stmt.where(models.Session.is_active.is_(True)) @@ -376,60 +332,133 @@ async def get_sessions( if filter is not None: stmt = stmt.where(models.Session.h_metadata.contains(filter)) - if reverse: - stmt = stmt.order_by(models.Session.id.desc()) - else: - stmt = stmt.order_by(models.Session.id) + stmt = stmt.order_by(models.Session.created_at) return stmt -async def create_session( +async def get_or_create_session( db: AsyncSession, session: schemas.SessionCreate, - app_id: str, - user_id: str, + workspace_name: str, ) -> models.Session: """ - Create a new session for a user. + Get or create a session in a workspace with specified peers. + If the session already exists, the peers are added to the session. Args: db: Database session session: Session creation schema - app_id: ID of the app - user_id: ID of the user + workspace_name: Name of the workspace + peer_names: List of peer names to add to the session Returns: The created session Raises: - ResourceNotFoundException: If the user does not exist + ResourceNotFoundException: If the session does not exist and create is false """ - try: - # This will raise ResourceNotFoundException if user not found - await get_user(db, app_id=app_id, user_id=user_id) + + stmt = ( + select(models.Session) + .where(models.Session.workspace_name == workspace_name) + .where(models.Session.name == session.name) + ) + + result = await db.execute(stmt) + + honcho_session = result.scalar_one_or_none() + + # Check if session already exists + if honcho_session is None: + if session.peer_names and len(session.peer_names) > SESSION_PEERS_LIMIT: + raise ValueError( + f"Cannot create session {session.name} with {len(session.peer_names)} peers. Maximum allowed is {SESSION_PEERS_LIMIT} peers per session." + ) + + # Create honcho session honcho_session = models.Session( - user_id=user_id, - app_id=app_id, - h_metadata=session.metadata, + workspace_name=workspace_name, + name=session.name, + h_metadata=session.metadata or {}, + configuration=session.configuration or {}, ) db.add(honcho_session) - await db.commit() - logger.info(f"Session created successfully for user {user_id}") - return honcho_session - except Exception as e: - await db.rollback() - logger.error(f"Error creating session for user {user_id}: {str(e)}") - raise + # Flush to ensure session exists in DB before adding peers + await db.flush() + else: + # Update existing session with metadata and feature flags if provided + if session.metadata is not None: + honcho_session.h_metadata = session.metadata + if session.configuration is not None: + honcho_session.configuration = session.configuration + + # Add all peers to session + if session.peer_names: + await get_or_create_peers( + db, + workspace_name=workspace_name, + peers=[ + schemas.PeerCreate(name=peer_name) for peer_name in session.peer_names + ], + ) + await _get_or_add_peers_to_session( + db, + workspace_name=workspace_name, + session_name=session.name, + peer_names=session.peer_names, + ) + + await db.commit() + logger.info( + f"Session {session.name} updated successfully in workspace {workspace_name} with {len(session.peer_names or [])} peers" + ) + return honcho_session + + +async def get_session( + db: AsyncSession, + session_name: str, + workspace_name: str, +) -> models.Session: + """ + Get a session in a workspace. + + Args: + db: Database session + session_name: Name of the session + workspace_name: Name of the workspace + + Returns: + The session + + Raises: + ResourceNotFoundException: If the session does not exist + """ + stmt = ( + select(models.Session) + .where(models.Session.workspace_name == workspace_name) + .where(models.Session.name == session_name) + ) + + result = await db.execute(stmt) + + honcho_session = result.scalar_one_or_none() + + if honcho_session is None: + raise ResourceNotFoundException( + f"Session {session_name} not found in workspace {workspace_name}" + ) + + return honcho_session async def update_session( db: AsyncSession, session: schemas.SessionUpdate, - app_id: str, - user_id: str, - session_id: str, + workspace_name: str, + session_name: str, ) -> models.Session: """ Update a session. @@ -437,77 +466,72 @@ async def update_session( Args: db: Database session session: Session update schema - app_id: ID of the app - user_id: ID of the user - session_id: ID of the session + workspace_name: Name of the workspace + session_name: Name of the session Returns: The updated session Raises: - ResourceNotFoundException: If the session does not exist or doesn't belong to the user + ResourceNotFoundException: If the session does not exist or peer is not in session """ - honcho_session = await get_session( - db, app_id=app_id, session_id=session_id, user_id=user_id + honcho_session = await get_or_create_session( + db, schemas.SessionCreate(name=session_name), workspace_name=workspace_name ) - if honcho_session is None: - logger.warning(f"Session {session_id} not found for user {user_id}") - raise ResourceNotFoundException("Session not found or does not belong to user") - if ( - session.metadata is not None - ): # Need to explicitly be there won't make it empty by default + if session.metadata is not None: honcho_session.h_metadata = session.metadata + if session.configuration is not None: + honcho_session.configuration = session.configuration + await db.commit() - logger.info(f"Session {session_id} updated successfully") + logger.info(f"Session {session_name} updated successfully") return honcho_session async def delete_session( - db: AsyncSession, app_id: str, user_id: str, session_id: str + db: AsyncSession, workspace_name: str, session_name: str ) -> bool: """ Mark a session as inactive (soft delete). Args: db: Database session - app_id: ID of the app - user_id: ID of the user - session_id: ID of the session + workspace_name: Name of the workspace + session_name: Name of the session Returns: True if the session was deleted successfully Raises: - ResourceNotFoundException: If the session does not exist or doesn't belong to the user + ResourceNotFoundException: If the session does not exist """ stmt = ( select(models.Session) - .where(models.Session.public_id == session_id) - .where(models.Session.user_id == user_id) - .where(models.Session.app_id == app_id) + .where(models.Session.workspace_name == workspace_name) + .where(models.Session.name == session_name) ) result = await db.execute(stmt) honcho_session = result.scalar_one_or_none() if honcho_session is None: - logger.warning(f"Session {session_id} not found for user {user_id}") - raise ResourceNotFoundException("Session not found or does not belong to user") + logger.warning( + f"Session {session_name} not found in workspace {workspace_name}" + ) + raise ResourceNotFoundException("Session not found") honcho_session.is_active = False await db.commit() - logger.info(f"Session {session_id} marked as inactive") + logger.info(f"Session {session_name} marked as inactive") return True async def clone_session( db: AsyncSession, - app_id: str, - user_id: str, - original_session_id: str, + workspace_name: str, + original_session_name: str, cutoff_message_id: Optional[str] = None, - deep_copy: bool = True, ) -> models.Session: """ Clone a session and its messages. If cutoff_message_id is provided, @@ -515,9 +539,8 @@ async def clone_session( Args: db: SQLAlchemy session - app_id: ID of the app the target session is in - user_id: ID of the user the target session belongs to - original_session_id: ID of the session to clone + workspace_name: Name of the workspace the target session is in + original_session_name: Name of the session to clone cutoff_message_id: Optional ID of the last message to include in the clone Returns: @@ -526,20 +549,20 @@ async def clone_session( # Get the original session stmt = ( select(models.Session) - .where(models.Session.app_id == app_id) - .where(models.Session.user_id == user_id) - .where(models.Session.public_id == original_session_id) + .where(models.Session.workspace_name == workspace_name) + .where(models.Session.name == original_session_name) ) - original_session = await db.scalar(stmt) - if not original_session: - raise ValueError("Original session not found") + result = await db.execute(stmt) + original_session = result.scalar_one_or_none() + if original_session is None: + raise ResourceNotFoundException("Original session not found") # If cutoff_message_id is provided, verify it belongs to the session cutoff_message = None if cutoff_message_id is not None: stmt = select(models.Message).where( models.Message.public_id == cutoff_message_id, - models.Message.session_id == original_session_id, + models.Message.session_name == original_session_name, ) cutoff_message = await db.scalar(stmt) if not cutoff_message: @@ -549,8 +572,8 @@ async def clone_session( # Create new session new_session = models.Session( - user_id=original_session.user_id, - app_id=original_session.app_id, + workspace_name=workspace_name, + name=generate_nanoid(), h_metadata=original_session.h_metadata, ) db.add(new_session) @@ -558,7 +581,7 @@ async def clone_session( # Build query for messages to clone stmt = select(models.Message).where( - models.Message.session_id == original_session_id + models.Message.session_name == original_session_name ) if cutoff_message_id is not None and cutoff_message is not None: stmt = stmt.where(models.Message.id <= cast(cutoff_message.id, BigInteger)) @@ -574,156 +597,792 @@ async def clone_session( # Prepare bulk insert data new_messages = [ { - "session_id": new_session.public_id, + "session_name": new_session.name, "content": message.content, - "is_user": message.is_user, "h_metadata": message.h_metadata, - "app_id": original_session.app_id, - "user_id": original_session.user_id, + "workspace_name": workspace_name, + "peer_name": message.peer_name, } for message in messages_to_clone ] - stmt = insert(models.Message).returning(models.Message.public_id) - result = await db.execute(stmt, new_messages) - new_message_ids = result.scalars().all() + insert_stmt = insert(models.Message).returning(models.Message) + result = await db.execute(insert_stmt, new_messages) - # Create mapping of old to new message IDs - message_id_map = dict( - zip([message.public_id for message in messages_to_clone], new_message_ids) + # Clone peers from original session to new session + stmt = select(models.SessionPeer).where( + models.SessionPeer.session_name == original_session_name ) - - # Handle metamessages if deep copy is requested - if deep_copy: - # Fetch all metamessages tied to the session in a single query - stmt = ( - select(models.Metamessage) - .where(models.Metamessage.session_id == original_session_id) - .order_by(models.Metamessage.id) # Explicit ordering by id + result = await db.execute(stmt) + session_peers = result.scalars().all() + for session_peer in session_peers: + new_session_peer = models.SessionPeer( + session_name=new_session.name, + peer_name=session_peer.peer_name, + workspace_name=workspace_name, ) - if cutoff_message_id is not None and cutoff_message is not None: - # Only get metamessages related to messages we're cloning - message_ids = [message.public_id for message in messages_to_clone] - stmt = stmt.where( - (models.Metamessage.message_id.is_(None)) - | (models.Metamessage.message_id.in_(message_ids)) - ) - - metamessages_result = await db.scalars(stmt) - metamessages = metamessages_result.all() - - if metamessages: - # Prepare bulk insert data for metamessages - new_metamessages = [] - - for meta in metamessages: - # Base metamessage data - meta_data = { - "user_id": meta.user_id, # Preserve original user - "session_id": new_session.public_id, - "label": meta.label, - "content": meta.content, - "h_metadata": meta.h_metadata, - "app_id": original_session.app_id, - } - - # If the metamessage was tied to a message, tie it to the corresponding new message - if meta.message_id is not None and meta.message_id in message_id_map: - meta_data["message_id"] = message_id_map[meta.message_id] - - new_metamessages.append(meta_data) - - # Bulk insert metamessages using modern insert syntax - if new_metamessages: - stmt = insert(models.Metamessage) - await db.execute(stmt, new_metamessages) + db.add(new_session_peer) await db.commit() - + logger.info(f"Session {original_session_name} cloned successfully") return new_session +async def remove_peers_from_session( + db: AsyncSession, + workspace_name: str, + session_name: str, + peer_names: set[str], +) -> bool: + """ + Remove specified peers from a session. + + Args: + db: Database session + workspace_name: Name of the workspace + session_name: Name of the session + peer_names: Set of peer names to remove from the session + + Returns: + True if peers were removed successfully + + Raises: + ResourceNotFoundException: If the session does not exist + """ + # Verify session exists + stmt = ( + select(models.Session) + .where(models.Session.workspace_name == workspace_name) + .where(models.Session.name == session_name) + ) + result = await db.execute(stmt) + session = result.scalar_one_or_none() + + if session is None: + raise ResourceNotFoundException( + f"Session {session_name} not found in workspace {workspace_name}" + ) + + # Soft delete specified session peers by setting left_at timestamp + update_stmt = ( + update(models.SessionPeer) + .where( + models.SessionPeer.session_name == session_name, + models.SessionPeer.workspace_name == workspace_name, + models.SessionPeer.peer_name.in_(peer_names), + models.SessionPeer.left_at.is_(None), # Only update active peers + ) + .values(left_at=func.now()) + ) + result = await db.execute(update_stmt) + + await db.commit() + return True + + +async def get_peers_from_session( + workspace_name: str, + session_name: str, +) -> Select: + """ + Get all peers from a session. + + Args: + db: Database session + workspace_name: Name of the workspace + session_name: Name of the session + + Returns: + Paginated list of Peer objects in the session + """ + # Get all active peers in the session (where left_at is NULL) + stmt = ( + select(models.Peer) + .join(models.SessionPeer, models.Peer.name == models.SessionPeer.peer_name) + .where(models.SessionPeer.session_name == session_name) + .where(models.Peer.workspace_name == workspace_name) + .where(models.SessionPeer.left_at.is_(None)) # Only active peers + ) + + return stmt + + +async def get_session_peer_configuration( + workspace_name: str, + session_name: str, +) -> Select: + """ + Get configuration from both SessionPeer and Peer tables for active peers in a session. + + Args: + workspace_name: Name of the workspace + session_name: Name of the session + + Returns: + Select statement returning peer_name, peer_configuration, and session_peer_configuration + """ + stmt = ( + select( + models.Peer.name.label("peer_name"), + models.Peer.configuration.label("peer_configuration"), + models.SessionPeer.configuration.label("session_peer_configuration"), + ) + .join(models.SessionPeer, models.Peer.name == models.SessionPeer.peer_name) + .where(models.SessionPeer.session_name == session_name) + .where(models.Peer.workspace_name == workspace_name) + .where(models.SessionPeer.workspace_name == workspace_name) + .where(models.SessionPeer.left_at.is_(None)) # Only active peers + ) + + return stmt + + +async def set_peers_for_session( + db: AsyncSession, + workspace_name: str, + session_name: str, + peer_names: dict[str, schemas.SessionPeerConfig], +) -> list[models.SessionPeer]: + """ + Set peers for a session, overwriting any existing peers. + If peers don't exist, they will be created. + + Args: + db: Database session + workspace_name: Name of the workspace + session_name: Name of the session + peer_names: Set of peer names to set for the session + + Returns: + List of SessionPeer objects for all peers in the session + + Raises: + ResourceNotFoundException: If the session does not exist + """ + # Validate peer limit before making any changes + if len(peer_names) > SESSION_PEERS_LIMIT: + raise ValueError( + f"Cannot set {len(peer_names)} peers for session {session_name}. Maximum allowed is {SESSION_PEERS_LIMIT} peers per session." + ) + + # Verify session exists + stmt = ( + select(models.Session) + .where(models.Session.workspace_name == workspace_name) + .where(models.Session.name == session_name) + ) + result = await db.execute(stmt) + session = result.scalar_one_or_none() + + if session is None: + raise ResourceNotFoundException( + f"Session {session_name} not found in workspace {workspace_name}" + ) + + # Soft delete specified session peers by setting left_at timestamp + update_stmt = ( + update(models.SessionPeer) + .where( + models.SessionPeer.session_name == session_name, + models.SessionPeer.workspace_name == workspace_name, + models.SessionPeer.left_at.is_(None), # Only update active peers + ) + .values(left_at=func.now()) + ) + result = await db.execute(update_stmt) + + # Get or create peers + await get_or_create_peers( + db, + workspace_name=workspace_name, + peers=[schemas.PeerCreate(name=peer_name) for peer_name in peer_names], + ) + + # Add new peers to session + peers = await _get_or_add_peers_to_session( + db, + workspace_name=workspace_name, + session_name=session_name, + peer_names=peer_names, + ) + + await db.commit() + return peers + + +async def _get_or_add_peers_to_session( + db: AsyncSession, + workspace_name: str, + session_name: str, + peer_names: dict[str, schemas.SessionPeerConfig], +) -> list[models.SessionPeer]: + """ + Add multiple peers to an existing session. If a peer already exists in the session, + it will be skipped gracefully. + + Args: + db: Database session + session_name: Name of the session + peer_names: Set of peer names to add to the session + + Returns: + List of all SessionPeer objects (both existing and newly created) + + Raises: + ValueError: If adding peers would exceed the maximum limit + """ + # If no peers to add, skip the insert and just return existing active session peers + if not peer_names: + select_stmt = select(models.SessionPeer).where( + models.SessionPeer.session_name == session_name, + models.SessionPeer.workspace_name == workspace_name, + models.SessionPeer.left_at.is_(None), # Only active peers + ) + result = await db.execute(select_stmt) + return list(result.scalars().all()) + + # Check current number of active peers and validate limit before upsert + current_peers_stmt = select(models.SessionPeer.peer_name).where( + models.SessionPeer.session_name == session_name, + models.SessionPeer.workspace_name == workspace_name, + models.SessionPeer.left_at.is_(None), # Only active peers + ) + result = await db.execute(current_peers_stmt) + existing_peer_names = result.scalars().all() + + new_peers = [name for name in peer_names if name not in existing_peer_names] + if len(new_peers) + len(existing_peer_names) > SESSION_PEERS_LIMIT: + raise ValueError( + f"Cannot add {len(new_peers)} peer(s). Session already has {len(existing_peer_names)} peer(s) with {SESSION_PEERS_LIMIT} peers per session." + ) + + # Use upsert to handle both new peers and rejoining peers + stmt = pg_insert(models.SessionPeer).values( + [ + { + "session_name": session_name, + "peer_name": peer_name, + "workspace_name": workspace_name, + "joined_at": func.now(), + "left_at": None, + "configuration": configuration.model_dump(), + } + for peer_name, configuration in peer_names.items() + ] + ) + + # On conflict, update joined_at and clear left_at (rejoin scenario) + stmt = stmt.on_conflict_do_update( + index_elements=["session_name", "peer_name", "workspace_name"], + set_={ + "joined_at": func.now(), + "left_at": None, + }, + ) + await db.execute(stmt) + + select_stmt = select(models.SessionPeer).where( + models.SessionPeer.session_name == session_name, + models.SessionPeer.workspace_name == workspace_name, + models.SessionPeer.left_at.is_(None), # Only active peers + ) + result = await db.execute(select_stmt) + return list(result.scalars().all()) + + +async def get_peer_config( + db: AsyncSession, + workspace_name: str, + session_name: str, + peer_id: str, +) -> schemas.SessionPeerConfig: + """ + Get the configuration for a peer in a session. + + Args: + db: Database session + workspace_name: Name of the workspace + session_name: Name of the session + peer_id: Name of the peer + + Returns: + Configuration for the peer + + Raises: + ResourceNotFoundException: If the session or peer does not exist + """ + # Get row from session_peer table + stmt = select(models.SessionPeer).where( + models.SessionPeer.workspace_name == workspace_name, + models.SessionPeer.session_name == session_name, + models.SessionPeer.peer_name == peer_id, + ) + result = await db.execute(stmt) + session_peer = result.scalar_one_or_none() + + if session_peer is None: + raise ResourceNotFoundException( + f"Session peer {peer_id} not found in session {session_name} in workspace {workspace_name}" + ) + + return schemas.SessionPeerConfig(**session_peer.configuration) + + +async def set_peer_config( + db: AsyncSession, + workspace_name: str, + session_name: str, + peer_id: str, + config: schemas.SessionPeerConfig, +) -> None: + """ + Set the configuration for a peer in a session. + + Args: + db: Database session + workspace_name: Name of the workspace + session_name: Name of the session + peer_id: Name of the peer + config: Configuration for the peer + + Returns: + True if the peer config was set successfully + + Raises: + ResourceNotFoundException: If the session or peer does not exist + """ + # Get row from session_peer table + stmt = select(models.SessionPeer).where( + models.SessionPeer.workspace_name == workspace_name, + models.SessionPeer.session_name == session_name, + models.SessionPeer.peer_name == peer_id, + ) + result = await db.execute(stmt) + session_peer = result.scalar_one_or_none() + + if session_peer is None: + raise ResourceNotFoundException( + f"Session peer {peer_id} not found in session {session_name} in workspace {workspace_name}" + ) + + # Update peer config + session_peer.configuration["observe_others"] = config.observe_others + session_peer.configuration["observe_me"] = config.observe_me + + await db.commit() + return + + +async def search( + query: str, + *, + workspace_name: str, + session_name: Optional[str] = None, + peer_name: Optional[str] = None, +) -> Select: + """ + Search across message content using a hybrid approach: + - Uses PostgreSQL full text search for natural language queries + - Falls back to exact string matching for queries with special characters + + If a session or peer is provided, the search will be scoped to that + session or peer. Otherwise, it will search across all messages in the workspace. + + Args: + query: Search query to match against message content + workspace_name: Name of the workspace + session_name: Optional name of the session + peer_name: Optional name of the peer + + Returns: + List of messages that match the search query, ordered by relevance + """ + import re + + from sqlalchemy import func, or_ + + # Check if query contains special characters that FTS might not handle well + has_special_chars = bool( + re.search(r'[~`!@#$%^&*()_+=\[\]{};\':"\\|,.<>/?-]', query) + ) + + # Base query conditions + base_conditions = [models.Message.workspace_name == workspace_name] + + if has_special_chars: + # For queries with special characters, use exact string matching (ILIKE) + # This ensures we can find exact matches like "~special-uuid~" + search_condition = models.Message.content.ilike(f"%{query}%") + + base_query = ( + select(models.Message) + .where(*base_conditions, search_condition) + .order_by(models.Message.created_at.desc()) + ) + else: + # For natural language queries, use full text search with ranking + fts_condition = func.to_tsvector("english", models.Message.content).op("@@")( + func.plainto_tsquery("english", query) + ) + + # Combine FTS with ILIKE as fallback for better coverage + combined_condition = or_( + fts_condition, models.Message.content.ilike(f"%{query}%") + ) + + base_query = ( + select(models.Message) + .where(*base_conditions, combined_condition) + .order_by( + # Order by FTS relevance first, then by creation time + func.coalesce( + func.ts_rank( + func.to_tsvector("english", models.Message.content), + func.plainto_tsquery("english", query), + ), + 0, + ).desc(), + models.Message.created_at.desc(), + ) + ) + + # Add additional filters based on parameters + if session_name is not None: + stmt = base_query.where(models.Message.session_name == session_name) + elif peer_name is not None: + stmt = base_query.where(models.Message.peer_name == peer_name) + else: + stmt = base_query + + return stmt + + +async def get_working_representation( + db: AsyncSession, + workspace_name: str, + peer_name: str, + session_name: str | None = None, +) -> str: + if session_name: + # Fetch the latest user representation from the same session + logger.debug(f"Fetching latest representation for session {session_name}") + latest_representation_stmt = ( + select(models.SessionPeer) + .where(models.SessionPeer.workspace_name == workspace_name) + .where(models.SessionPeer.peer_name == peer_name) + .where(models.SessionPeer.session_name == session_name) + .limit(1) + ) + result = await db.execute(latest_representation_stmt) + latest_representation_obj = result.scalar_one_or_none() + latest_representation = ( + latest_representation_obj.internal_metadata.get( + USER_REPRESENTATION_METADATA_KEY, "No user representation available." + ) + if latest_representation_obj + else "No user representation available." + ) + else: + # Fetch the latest global level user representation + logger.debug("Fetching latest global level user representation") + latest_representation_stmt = ( + select(models.Peer) + .where(models.Peer.workspace_name == workspace_name) + .where(models.Peer.name == peer_name) + ) + result = await db.execute(latest_representation_stmt) + latest_representation_obj = result.scalar_one_or_none() + latest_representation = ( + latest_representation_obj.internal_metadata.get( + USER_REPRESENTATION_METADATA_KEY, "No user representation available." + ) + if latest_representation_obj + else "No user representation available." + ) + + return latest_representation + + +async def set_working_representation( + db: AsyncSession, + representation: str, + workspace_name: str, + peer_name: str, + session_name: str | None = None, +) -> None: + if session_name: + # Get session peer and update its metadata with the representation + stmt = ( + update(models.SessionPeer) + .where(models.SessionPeer.workspace_name == workspace_name) + .where(models.SessionPeer.peer_name == peer_name) + .where(models.SessionPeer.session_name == session_name) + .values( + internal_metadata=models.SessionPeer.internal_metadata.op("||")( + {USER_REPRESENTATION_METADATA_KEY: representation} + ) + ) + ) + else: + # Get peer and update its metadata with the representation + stmt = ( + update(models.Peer) + .where(models.Peer.workspace_name == workspace_name) + .where(models.Peer.name == peer_name) + .values( + internal_metadata=models.Peer.internal_metadata.op("||")( + {USER_REPRESENTATION_METADATA_KEY: representation} + ) + ) + ) + await db.execute(stmt) + await db.commit() + + ######################################################## # Message Methods ######################################################## -async def create_message( - db: AsyncSession, - message: schemas.MessageCreate, - app_id: str, - user_id: str, - session_id: str, -) -> models.Message: - honcho_session = await get_session( - db, app_id=app_id, session_id=session_id, user_id=user_id - ) - if honcho_session is None: - raise ValueError("Session not found or does not belong to user") - - honcho_message = models.Message( - session_id=session_id, - is_user=message.is_user, - content=message.content, - h_metadata=message.metadata, - user_id=user_id, - app_id=app_id, - ) - db.add(honcho_message) - await db.commit() - # await db.refresh(honcho_message, attribute_names=["id", "content", "h_metadata"]) - # await db.refresh(honcho_message) - return honcho_message - - async def create_messages( db: AsyncSession, messages: list[schemas.MessageCreate], - app_id: str, - user_id: str, - session_id: str, + workspace_name: str, + session_name: str, ) -> list[models.Message]: - """Bulk create messages for a session while maintaining order""" - # Verify session exists and belongs to user - honcho_session = await get_session( - db, app_id=app_id, session_id=session_id, user_id=user_id - ) - if honcho_session is None: - raise ValueError("Session not found or does not belong to user") + """ + Bulk create messages for a session while maintaining order. - # Create list of message records - message_records = [ - { - "session_id": session_id, - "is_user": message.is_user, - "content": message.content, - "h_metadata": message.metadata, - "user_id": user_id, - "app_id": app_id, - } + Args: + db: Database session + messages: List of messages to create + workspace_name: Name of the workspace + session_name: Name of the session to create messages in + + Returns: + List of created message objects + """ + # Get or create session with peers in messages list + peers = {message.peer_name: schemas.SessionPeerConfig() for message in messages} + await get_or_create_session( + db, + session=schemas.SessionCreate( + name=session_name, + peers=peers, + ), + workspace_name=workspace_name, + ) + + # Create list of message objects (this will trigger the before_insert event) + message_objects = [ + models.Message( + session_name=session_name, + peer_name=message.peer_name, + content=message.content, + h_metadata=message.metadata or {}, + workspace_name=workspace_name, + ) for message in messages ] - # Bulk insert messages and return them in order - stmt = insert(models.Message).returning(models.Message) - result = await db.execute(stmt, message_records) + # Add all messages and commit + db.add_all(message_objects) await db.commit() - return list(result.scalars().all()) + return message_objects + + +async def create_messages_for_peer( + db: AsyncSession, + messages: list[schemas.MessageCreate], + workspace_name: str, + peer_name: str, +) -> list[models.Message]: + """ + Bulk create messages for a peer while maintaining order. + Note that session_name for messages created this way will be None + and peer_name will be the provided peer_name for each message, + regardless of the peer_name in the individual message(s). + + Args: + db: Database session + messages: List of messages to create + workspace_name: Name of the workspace + peer_name: Name of the peer to create messages for + + Returns: + List of created message objects + """ + await get_or_create_peers( + db, workspace_name=workspace_name, peers=[schemas.PeerCreate(name=peer_name)] + ) + # Create list of message objects (this will trigger the before_insert event) + message_objects = [ + models.Message( + session_name=None, + peer_name=peer_name, + content=message.content, + h_metadata=message.metadata or {}, + workspace_name=workspace_name, + ) + for message in messages + ] + + # Add all messages and commit + db.add_all(message_objects) + await db.commit() + + return message_objects async def get_messages( - app_id: str, - user_id: str, - session_id: str, + workspace_name: str, + session_name: str, + reverse: Optional[bool] = False, + filter: Optional[dict] = None, + token_limit: Optional[int] = None, + message_count_limit: Optional[int] = None, +) -> Select: + """ + Get messages from a session. If token_limit is provided, the n most recent messages + with token count adding up to the limit will be returned. If message_count_limit is provided, + the n most recent messages will be returned. If both are provided, message_count_limit will be + used. + + Args: + workspace_name: Name of the workspace + session_name: Name of the session + reverse: Whether to reverse the order of messages + filter: Filter to apply to the messages + token_limit: Maximum number of tokens to include in the messages + message_count_limit: Maximum number of messages to include + + Returns: + Select statement for the messages + """ + # Base query with workspace and session filters + base_conditions = [ + models.Message.workspace_name == workspace_name, + models.Message.session_name == session_name, + ] + + # Add metadata filter if provided + if filter is not None: + base_conditions.append(models.Message.h_metadata.contains(filter)) + + # Apply message count limit first (takes precedence over token limit) + if message_count_limit is not None: + stmt = select(models.Message).where(*base_conditions) + # For message count limit, we want the most recent N messages + # So we order by id desc to get most recent, then apply limit + stmt = stmt.order_by(models.Message.id.desc()).limit(message_count_limit) + + # Apply final ordering based on reverse parameter + if reverse: + stmt = stmt.order_by(models.Message.id.desc()) + else: + stmt = stmt.order_by(models.Message.id.asc()) + + elif token_limit is not None: + # Apply token limit logic + # Create a subquery that calculates running sum of tokens for most recent messages + token_subquery = ( + select( + models.Message.id, + func.sum(models.Message.token_count) + .over(order_by=models.Message.id.desc()) + .label("running_token_sum"), + ) + .where(*base_conditions) + .subquery() + ) + + # Select Message objects where running sum doesn't exceed token_limit + stmt = ( + select(models.Message) + .join(token_subquery, models.Message.id == token_subquery.c.id) + .where(token_subquery.c.running_token_sum <= token_limit) + ) + + # Apply final ordering based on reverse parameter + if reverse: + stmt = stmt.order_by(models.Message.id.desc()) + else: + stmt = stmt.order_by(models.Message.id.asc()) + + else: + # Default case - no limits applied + stmt = select(models.Message).where(*base_conditions) + if reverse: + stmt = stmt.order_by(models.Message.id.desc()) + else: + stmt = stmt.order_by(models.Message.id.asc()) + + return stmt + + +async def get_messages_id_range( + db: AsyncSession, + workspace_name: str, + session_name: str | None, + peer_name: str | None, + start_id: int = 0, + end_id: Optional[int] = None, +) -> list[models.Message]: + """ + Get messages from a session or peer by primary key ID range. + If end_id is not provided, all messages after and including start_id will be returned. + If start_id is not provided, start will be beginning of session. + + Note: list is exclusive of the end_id message. + + Args: + db: Database session + workspace_name: Name of the workspace + session_name: Name of the session + peer_name: Name of the peer + start_id: Primary key ID of the first message to return + end_id: Primary key ID of the last message (exclusive) + + Returns: + List of messages + + Raises: + ValueError: If both session_name and peer_name are not provided + """ + if start_id < 0 or (end_id is not None and (start_id >= end_id or end_id <= 1)): + return [] + stmt = select(models.Message).where( + models.Message.workspace_name == workspace_name, + ) + if end_id: + stmt = stmt.where(models.Message.id.between(start_id, end_id - 1)) + else: + stmt = stmt.where(models.Message.id >= start_id) + + if session_name: + stmt = stmt.where(models.Message.session_name == session_name) + elif peer_name: + stmt = stmt.where(models.Message.peer_name == peer_name).where( + models.Message.session_name.is_(None) + ) + else: + raise ValueError("Either session_name or peer_name must be provided") + result = await db.execute(stmt) + return list(result.scalars().all()) + + +async def get_messages_for_peer( + workspace_name: str, + peer_name: str, reverse: Optional[bool] = False, filter: Optional[dict] = None, ) -> Select: stmt = ( select(models.Message) - .where(models.Message.app_id == app_id) - .where(models.Message.user_id == user_id) - .where(models.Message.session_id == session_id) + .where(models.Message.workspace_name == workspace_name) + .where(models.Message.peer_name == peer_name) + .where(models.Message.session_name.is_(None)) ) if filter is not None: @@ -739,16 +1398,14 @@ async def get_messages( async def get_message( db: AsyncSession, - app_id: str, - user_id: str, - session_id: str, + workspace_name: str, + session_name: str, message_id: str, ) -> Optional[models.Message]: stmt = ( select(models.Message) - .where(models.Message.app_id == app_id) - .where(models.Message.user_id == user_id) - .where(models.Message.session_id == session_id) + .where(models.Message.workspace_name == workspace_name) + .where(models.Message.session_name == session_name) .where(models.Message.public_id == message_id) ) result = await db.execute(stmt) @@ -758,13 +1415,15 @@ async def get_message( async def update_message( db: AsyncSession, message: schemas.MessageUpdate, - app_id: str, - user_id: str, - session_id: str, + workspace_name: str, + session_name: str, message_id: str, ) -> bool: honcho_message = await get_message( - db, app_id=app_id, session_id=session_id, user_id=user_id, message_id=message_id + db, + workspace_name=workspace_name, + session_name=session_name, + message_id=message_id, ) if honcho_message is None: raise ValueError("Message not found or does not belong to user") @@ -777,192 +1436,6 @@ async def update_message( return honcho_message -######################################################## -# metamessage methods -######################################################## - - -async def create_metamessage( - db: AsyncSession, - user_id: str, - metamessage: schemas.MetamessageCreate, - app_id: str, -): - # Validate user exists - user = await get_user(db, app_id=app_id, user_id=user_id) - if user is None: - raise ResourceNotFoundException(f"User with ID '{user_id}' not found") - - # Initialize metamessage data - metamessage_data = { - "user_id": user_id, - "app_id": app_id, - "label": metamessage.label, - "content": metamessage.content, - "h_metadata": metamessage.metadata, - } - - # Validate session_id if provided - if metamessage.session_id is not None: - session = await get_session( - db, - app_id=app_id, - user_id=user_id, - session_id=metamessage.session_id, - ) - if session is None: - raise ResourceNotFoundException( - "Session not found or does not belong to user" - ) - metamessage_data["session_id"] = metamessage.session_id - - # Validate message_id if provided - if metamessage.message_id is not None: - message = await get_message( - db, - app_id=app_id, - session_id=metamessage.session_id, - user_id=user_id, - message_id=metamessage.message_id, - ) - if message is None: - raise ResourceNotFoundException( - "Message not found or does not belong to session" - ) - metamessage_data["message_id"] = metamessage.message_id - elif metamessage.message_id is not None: - # If message_id provided but no session_id, that's an error - raise ValidationException("Cannot specify message_id without session_id") - - # Create metamessage - honcho_metamessage = models.Metamessage(**metamessage_data) - db.add(honcho_metamessage) - await db.commit() - return honcho_metamessage - - -async def get_metamessages( - app_id: str, - user_id: str, - session_id: Optional[str] = None, - message_id: Optional[str] = None, - label: Optional[str] = None, - filter: Optional[dict] = None, - reverse: Optional[bool] = False, -) -> Select: - # Base query starts with metamessage and user relationship - stmt = ( - select(models.Metamessage) - .where(models.Metamessage.app_id == app_id) - .where(models.Metamessage.user_id == user_id) - ) - - # If session_id is provided, filter by it - if session_id is not None: - stmt = stmt.where(models.Metamessage.session_id == session_id) - - # If message_id is provided, filter by it - if message_id is not None: - stmt = stmt.where(models.Metamessage.message_id == message_id) - - # Filter by label if provided - if label is not None: - stmt = stmt.where(models.Metamessage.label == label) - - # Apply metadata filter if provided - if filter is not None: - stmt = stmt.where(models.Metamessage.h_metadata.contains(filter)) - - # Apply sort order - if reverse: - stmt = stmt.order_by(models.Metamessage.id.desc()) - else: - stmt = stmt.order_by(models.Metamessage.id) - - return stmt - - -async def get_metamessage( - db: AsyncSession, - app_id: str, - user_id: str, - metamessage_id: str, - session_id: Optional[str] = None, - message_id: Optional[str] = None, -) -> Optional[models.Metamessage]: - # Base query for metamessage by ID - stmt = ( - select(models.Metamessage) - .where(models.Metamessage.app_id == app_id) - .where(models.Metamessage.user_id == user_id) - .where(models.Metamessage.public_id == metamessage_id) - ) - - # Add optional filters - if session_id is not None: - stmt = stmt.where(models.Metamessage.session_id == session_id) - if message_id is not None: - stmt = stmt.where(models.Metamessage.message_id == message_id) - - result = await db.execute(stmt) - return result.scalar_one_or_none() - - -async def update_metamessage( - db: AsyncSession, - metamessage: schemas.MetamessageUpdate, - app_id: str, - user_id: str, - metamessage_id: str, -) -> bool: - # First retrieve the metamessage - metamessage_obj = await get_metamessage( - db, app_id=app_id, user_id=user_id, metamessage_id=metamessage_id - ) - if metamessage_obj is None: - raise ResourceNotFoundException( - f"Metamessage with ID {metamessage_id} not found" - ) - - # Validate the consistency of relationships if they're being changed - # If we're setting message_id, we must have a session_id - if metamessage.message_id is not None and metamessage.session_id is None: - # If updating message_id but not session_id, use the existing session_id - metamessage.session_id = metamessage_obj.session_id - if metamessage.session_id is None: - raise ValidationException("Cannot specify message_id without session_id") - - # If we're updating session_id and message_id, validate they belong together - if metamessage.session_id is not None and metamessage.message_id is not None: - message = await get_message( - db, - app_id=app_id, - session_id=metamessage.session_id, - user_id=user_id, - message_id=metamessage.message_id, - ) - if message is None: - raise ResourceNotFoundException( - "Message not found or doesn't belong to session" - ) - - # Update fields - if metamessage.session_id is not None: - metamessage_obj.session_id = metamessage.session_id - - if metamessage.message_id is not None: - metamessage_obj.message_id = metamessage.message_id - - if metamessage.metadata is not None: - metamessage_obj.h_metadata = metamessage.metadata - - if metamessage.label is not None: - metamessage_obj.label = metamessage.label - - await db.commit() - return metamessage_obj - - ######################################################## # collection methods ######################################################## @@ -970,41 +1443,17 @@ async def update_metamessage( # Should be very similar to the session methods -async def get_collections( - app_id: str, - user_id: str, - reverse: Optional[bool] = False, - filter: Optional[dict] = None, -) -> Select: - """Get a distinct list of the names of collections associated with a user""" - stmt = ( - select(models.Collection) - .where(models.Collection.app_id == app_id) - .where(models.Collection.user_id == user_id) - ) - - if filter is not None: - stmt = stmt.where(models.Collection.h_metadata.contains(filter)) - - if reverse: - stmt = stmt.order_by(models.Collection.id.desc()) - else: - stmt = stmt.order_by(models.Collection.id) - - return stmt - - -async def get_collection_by_id( - db: AsyncSession, app_id: str, user_id: str, collection_id: str +async def get_collection( + db: AsyncSession, workspace_name: str, peer_name: str, collection_name: str ) -> models.Collection: """ - Get a collection by ID for a specific user and app. + Get a collection by name for a specific peer and workspace. Args: db: Database session - app_id: Public ID of the app - user_id: Public ID of the user - collection_id: Public ID of the collection + workspace_name: Name of the workspace + peer_name: Name of the peer + collection_name: Name of the collection Returns: The collection if found @@ -1014,332 +1463,51 @@ async def get_collection_by_id( """ stmt = ( select(models.Collection) - .where(models.Collection.app_id == app_id) - .where(models.Collection.user_id == user_id) - .where(models.Collection.public_id == collection_id) + .where(models.Collection.workspace_name == workspace_name) + .where(models.Collection.peer_name == peer_name) + .where(models.Collection.name == collection_name) ) result = await db.execute(stmt) collection = result.scalar_one_or_none() if collection is None: - logger.warning( - f"Collection with ID '{collection_id}' not found for user {user_id}" - ) raise ResourceNotFoundException( - "Collection not found or does not belong to user" + "Collection not found or does not belong to peer" ) return collection -async def get_collection_by_name( - db: AsyncSession, app_id: str, user_id: str, name: str -) -> models.Collection: - """ - Get a collection by name for a specific user and app. - - Args: - db: Database session - app_id: Public ID of the app - user_id: Public ID of the user - name: Name of the collection - - Returns: - The collection if found - - Raises: - ResourceNotFoundException: If the collection does not exist - """ - stmt = ( - select(models.Collection) - .where(models.Collection.app_id == app_id) - .where(models.Collection.user_id == user_id) - .where(models.Collection.name == name) - ) - result = await db.execute(stmt) - collection = result.scalar_one_or_none() - if collection is None: - logger.warning(f"Collection with name '{name}' not found for user {user_id}") - raise ResourceNotFoundException(f"Collection with name '{name}' not found") - return collection - - -async def create_collection( +async def get_or_create_collection( db: AsyncSession, - collection: schemas.CollectionCreate, - app_id: str, - user_id: str, -) -> models.Collection: - """ - Create a new collection for a user. - - Args: - db: Database session - collection: Collection creation schema - app_id: ID of the app - user_id: ID of the user - - Returns: - The created collection - - Raises: - ConflictException: If a collection with the same name already exists for this user - ValidationException: If the collection configuration is invalid - ResourceNotFoundException: If the user does not exist - """ - try: - # This will raise ResourceNotFoundException if user not found - await get_user(db, app_id=app_id, user_id=user_id) - - # Check for reserved names - if collection.name == "honcho": - logger.warning( - f"Attempted to create collection with reserved name 'honcho' for user {user_id}" - ) - raise ValidationException( - "Invalid collection configuration - 'honcho' is a reserved name" - ) - - honcho_collection = models.Collection( - user_id=user_id, - app_id=app_id, - name=collection.name, - h_metadata=collection.metadata, - ) - db.add(honcho_collection) - await db.commit() - logger.info( - f"Collection '{collection.name}' created successfully for user {user_id}" - ) - return honcho_collection - except IntegrityError as e: - await db.rollback() - logger.warning(f"Failed to create collection - integrity error: {str(e)}") - raise ConflictException( - f"Collection with name '{collection.name}' already exists" - ) from e - - -async def create_user_protected_collection( - db: AsyncSession, - app_id: str, - user_id: str, -) -> models.Collection: - honcho_collection = models.Collection( - user_id=user_id, - app_id=app_id, - name=DEF_PROTECTED_COLLECTION_NAME, - ) - try: - # This will raise ResourceNotFoundException if user not found - await get_user(db, app_id=app_id, user_id=user_id) - - db.add(honcho_collection) - await db.commit() - except IntegrityError: - await db.rollback() - raise ValueError("Collection already exists") from None - return honcho_collection - - -async def get_or_create_user_protected_collection( - db: AsyncSession, - app_id: str, - user_id: str, + workspace_name: str, + peer_name: str, + collection_name: str, ) -> models.Collection: try: - honcho_collection = await get_collection_by_name( - db, app_id, user_id, DEF_PROTECTED_COLLECTION_NAME + honcho_collection = await get_collection( + db, workspace_name, peer_name, collection_name ) return honcho_collection except ResourceNotFoundException: - honcho_collection = await create_user_protected_collection(db, app_id, user_id) - return honcho_collection - - -async def update_collection( - db: AsyncSession, - collection: schemas.CollectionUpdate, - app_id: str, - user_id: str, - collection_id: str, -) -> models.Collection: - """ - Update a collection. - - Args: - db: Database session - collection: Collection update schema - app_id: ID of the app - user_id: ID of the user - collection_id: ID of the collection - - Returns: - The updated collection - - Raises: - ResourceNotFoundException: If the collection does not exist - ValidationException: If the update data is invalid - ConflictException: If the update violates a unique constraint - """ - try: - # Validate input - if collection.name is None and collection.metadata is None: - logger.warning( - f"Collection update attempted with no fields provided for collection {collection_id}" - ) - raise ValidationException( - "Invalid collection configuration - at least one field must be provided" - ) - - # This will raise ResourceNotFoundException if not found - honcho_collection = await get_collection_by_id( - db, app_id=app_id, user_id=user_id, collection_id=collection_id + honcho_collection = models.Collection( + workspace_name=workspace_name, + peer_name=peer_name, + name=collection_name, ) - - # Check for reserved names if name is being updated - if collection.name == "honcho": - logger.warning( - f"Attempted to rename collection to reserved name 'honcho' for user {user_id}" - ) - raise ValidationException( - "Invalid collection configuration - 'honcho' is a reserved name" - ) - - if collection.metadata is not None: - honcho_collection.h_metadata = collection.metadata - - if collection.name is not None: - honcho_collection.name = collection.name - + db.add(honcho_collection) await db.commit() - logger.info(f"Collection {collection_id} updated successfully") return honcho_collection - except IntegrityError as e: - await db.rollback() - logger.warning(f"Collection update failed due to integrity error: {str(e)}") - raise ConflictException("Collection update failed - name already in use") from e - - -async def delete_collection( - db: AsyncSession, app_id: str, user_id: str, collection_id: str -) -> bool: - """ - Delete a Collection and all documents associated with it. Takes advantage of - the orm cascade feature. - - Args: - db: Database session - app_id: ID of the app - user_id: ID of the user - collection_id: ID of the collection - - Returns: - True if the collection was deleted successfully - - Raises: - ResourceNotFoundException: If the collection does not exist - """ - try: - # This will raise ResourceNotFoundException if not found - honcho_collection = await get_collection_by_id( - db, app_id=app_id, user_id=user_id, collection_id=collection_id - ) - - if honcho_collection.name == "honcho": - logger.warning( - f"Attempted to delete collection with reserved name 'honcho' for user {user_id}" - ) - raise ValidationException( - "Invalid collection configuration - 'honcho' is a reserved name" - ) - - await db.delete(honcho_collection) - await db.commit() - logger.info(f"Collection {collection_id} deleted successfully") - return True - except Exception as e: - await db.rollback() - logger.error(f"Error deleting collection {collection_id}: {str(e)}") - raise ######################################################## # document methods ######################################################## -# Should be similar to the messages methods outside of query - - -async def get_documents( - app_id: str, - user_id: str, - collection_id: str, - reverse: Optional[bool] = False, - filter: Optional[dict] = None, -) -> Select: - stmt = ( - select(models.Document) - .where(models.Document.app_id == app_id) - .where(models.Document.user_id == user_id) - .where(models.Document.collection_id == collection_id) - ) - - if filter is not None: - stmt = stmt.where(models.Document.h_metadata.contains(filter)) - - if reverse: - stmt = stmt.order_by(models.Document.id.desc()) - else: - stmt = stmt.order_by(models.Document.id) - - return stmt - - -async def get_document( - db: AsyncSession, - app_id: str, - user_id: str, - collection_id: str, - document_id: str, -) -> models.Document: - """ - Get a document by ID. - - Args: - db: Database session - app_id: Public ID of the app - user_id: Public ID of the user - collection_id: Public ID of the collection - document_id: Public ID of the document - - Returns: - The document if found - - Raises: - ResourceNotFoundException: If the document does not exist - """ - stmt = ( - select(models.Document) - .where(models.Document.app_id == app_id) - .where(models.Document.user_id == user_id) - .where(models.Document.collection_id == collection_id) - .where(models.Document.public_id == document_id) - ) - - result = await db.execute(stmt) - document = result.scalar_one_or_none() - if document is None: - logger.warning( - f"Document with ID '{document_id}' not found in collection {collection_id}" - ) - raise ResourceNotFoundException(f"Document with ID '{document_id}' not found") - return document - async def query_documents( db: AsyncSession, - app_id: str, - user_id: str, - collection_id: str, + workspace_name: str, + peer_name: str, + collection_name: str, query: str, filter: Optional[dict] = None, max_distance: Optional[float] = None, @@ -1352,9 +1520,9 @@ async def query_documents( embedding_query = response.data[0].embedding stmt = ( select(models.Document) - .where(models.Document.app_id == app_id) - .where(models.Document.user_id == user_id) - .where(models.Document.collection_id == collection_id) + .where(models.Document.workspace_name == workspace_name) + .where(models.Document.peer_name == peer_name) + .where(models.Document.collection_name == collection_name) # .limit(top_k) ) if max_distance is not None: @@ -1373,9 +1541,9 @@ async def query_documents( async def create_document( db: AsyncSession, document: schemas.DocumentCreate, - app_id: str, - user_id: str, - collection_id: str, + workspace_name: str, + peer_name: str, + collection_name: str, duplicate_threshold: Optional[float] = None, ) -> models.Document: """ @@ -1384,9 +1552,9 @@ async def create_document( Args: db: Database session document: Document creation schema - app_id: ID of the app - user_id: ID of the user - collection_id: ID of the collection + workspace_name: Name of the workspace + peer_name: Name of the peer + collection_name: Name of the collection Returns: The created document @@ -1397,8 +1565,11 @@ async def create_document( """ # This will raise ResourceNotFoundException if collection not found - await get_collection_by_id( - db, app_id=app_id, collection_id=collection_id, user_id=user_id + await get_collection( + db, + workspace_name=workspace_name, + peer_name=peer_name, + collection_name=collection_name, ) # Using async client with await @@ -1412,7 +1583,7 @@ async def create_document( # Check if there are duplicates within the threshold stmt = ( select(models.Document) - .where(models.Document.collection_id == collection_id) + .where(models.Document.collection_name == collection_name) .where( models.Document.embedding.cosine_distance(embedding) < duplicate_threshold @@ -1427,11 +1598,11 @@ async def create_document( return duplicate honcho_document = models.Document( - app_id=app_id, - user_id=user_id, - collection_id=collection_id, + workspace_name=workspace_name, + peer_name=peer_name, + collection_name=collection_name, content=document.content, - h_metadata=document.metadata, + internal_metadata=document.metadata, embedding=embedding, ) db.add(honcho_document) @@ -1440,79 +1611,11 @@ async def create_document( return honcho_document -async def update_document( - db: AsyncSession, - document: schemas.DocumentUpdate, - app_id: str, - user_id: str, - collection_id: str, - document_id: str, -) -> bool: - honcho_document = await get_document( - db, - app_id=app_id, - collection_id=collection_id, - user_id=user_id, - document_id=document_id, - ) - if honcho_document is None: - raise ValueError("Session not found or does not belong to user") - if document.content is not None: - honcho_document.content = document.content - # Using async client with await - response = await openai_client.embeddings.create( - input=document.content, model="text-embedding-3-small" - ) - embedding = response.data[0].embedding - honcho_document.embedding = embedding - honcho_document.created_at = func.now() - - if document.metadata is not None: - honcho_document.h_metadata = document.metadata - await db.commit() - return honcho_document - - -async def delete_document( - db: AsyncSession, - app_id: str, - user_id: str, - collection_id: str, - document_id: str, -) -> bool: - honcho_collection = await get_collection_by_id( - db, app_id=app_id, collection_id=collection_id, user_id=user_id - ) - if honcho_collection is None: - raise ResourceNotFoundException("Collection or Document not found") - if honcho_collection.name == "honcho": - logger.warning( - f"Attempted to delete collection with reserved name 'honcho' for user {user_id}" - ) - raise ValidationException( - "Cannot delete collection with reserved name 'honcho'" - ) - stmt = ( - select(models.Document) - .where(models.Document.app_id == app_id) - .where(models.Document.user_id == user_id) - .where(models.Document.collection_id == collection_id) - .where(models.Document.public_id == document_id) - ) - result = await db.execute(stmt) - document = result.scalar_one_or_none() - if document is None: - return False - await db.delete(document) - await db.commit() - return True - - async def get_duplicate_documents( db: AsyncSession, - app_id: str, - user_id: str, - collection_id: str, + workspace_name: str, + peer_name: str, + collection_name: str, content: str, similarity_threshold: float = 0.85, ) -> list[models.Document]: @@ -1520,9 +1623,9 @@ async def get_duplicate_documents( Args: db: Database session - app_id: Application ID - user_id: User ID - collection_id: Collection ID + workspace_name: Name of the workspace + peer_name: Name of the peer + collection_name: Name of the collection content: Document content to check for duplicates similarity_threshold: Similarity threshold (0-1) for considering documents as duplicates @@ -1539,9 +1642,9 @@ async def get_duplicate_documents( # Find documents with similar embeddings stmt = ( select(models.Document) - .where(models.Document.app_id == app_id) - .where(models.Document.user_id == user_id) - .where(models.Document.collection_id == collection_id) + .where(models.Document.workspace_name == workspace_name) + .where(models.Document.peer_name == peer_name) + .where(models.Document.collection_name == collection_name) .where( models.Document.embedding.cosine_distance(embedding) < (1 - similarity_threshold) @@ -1551,3 +1654,7 @@ async def get_duplicate_documents( result = await db.execute(stmt) return list(result.scalars().all()) # Convert to list to match the return type + + +def construct_collection_name(peer_name: str, target_name: str) -> str: + return f"{peer_name}_{target_name}" diff --git a/src/db.py b/src/db.py index ee6d544e..95482c0e 100644 --- a/src/db.py +++ b/src/db.py @@ -1,5 +1,6 @@ import contextvars import os +from typing import Optional from dotenv import load_dotenv from sqlalchemy import MetaData, create_engine, text @@ -11,7 +12,9 @@ load_dotenv() connect_args = {"prepare_threshold": None} # Context variable to store request context -request_context = contextvars.ContextVar("request_context", default=None) +request_context: contextvars.ContextVar[Optional[str]] = contextvars.ContextVar( + "request_context", default=None +) engine = create_async_engine( os.environ["CONNECTION_URI"], diff --git a/src/deriver/consumer.py b/src/deriver/consumer.py index cc6525d0..92b351f9 100644 --- a/src/deriver/consumer.py +++ b/src/deriver/consumer.py @@ -1,8 +1,10 @@ import logging import os +from typing import Literal import sentry_sdk from langfuse.decorators import observe +from pydantic import BaseModel, ValidationError from rich.console import Console from sqlalchemy.ext.asyncio import AsyncSession @@ -20,70 +22,82 @@ TOM_METHOD = os.getenv("TOM_METHOD", "single_prompt") USER_REPRESENTATION_METHOD = os.getenv("USER_REPRESENTATION_METHOD", "long_term") -# FIXME see if this is SAFE -# async def add_metamessage(db, message_id, metamessage_type, content): -# metamessage = models.Metamessage( -# message_id=message_id, -# metamessage_type=metamessage_type, -# content=content, -# h_metadata={}, -# ) -# db.add(metamessage) +class PayloadSchema(BaseModel): + """ + Schema for validating payload data in process_item function. + Ensures all required fields are present with correct types and prevents injection risks. + """ + + content: str + workspace_name: str + sender_name: str + target_name: str + session_name: str | None + message_id: int + task_type: Literal["representation", "summary"] + + class Config: + # Forbid extra fields to prevent injection of unexpected data + extra = "forbid" async def process_item(db: AsyncSession, payload: dict): + # Validate payload structure and types before processing + try: + validated_payload = PayloadSchema(**payload) + except ValidationError as e: + logger.error("Invalid payload received: %s. Payload: %s", str(e), payload) + raise ValueError(f"Invalid payload structure: {str(e)}") from e + logger.debug( - f"process_item received payload: {payload['message_id']} is_user={payload['is_user']}" + "process_item received payload for message %s in session %s", + validated_payload.message_id, + validated_payload.session_name, ) + processing_args = [ - payload["content"], - payload["app_id"], - payload["user_id"], - payload["session_id"], - payload["message_id"], + validated_payload.content, + validated_payload.workspace_name, + validated_payload.sender_name, + validated_payload.target_name, + validated_payload.session_name, + validated_payload.message_id, db, ] - if payload["is_user"]: - logger.debug(f"Processing user message: {payload['message_id']}") - await process_user_message(*processing_args) - else: - logger.debug(f"Processing AI message: {payload['message_id']}") - await process_ai_message(*processing_args) - logger.debug(f"Finished processing message: {payload['message_id']}") + if validated_payload.task_type == "representation": + logger.debug( + "Processing message %s in %s", + validated_payload.message_id, + validated_payload.session_name, + ) + await process_message(*processing_args) + logger.debug( + "Finished processing message %s in %s %s", + validated_payload.message_id, + "session" if validated_payload.session_name else "peer", + validated_payload.session_name + if validated_payload.session_name + else validated_payload.sender_name, + ) await summarize_if_needed( db, - payload["app_id"], - payload["session_id"], - payload["user_id"], - payload["message_id"], + validated_payload.workspace_name, + validated_payload.session_name, + validated_payload.sender_name, + validated_payload.message_id, ) return -@sentry_sdk.trace -# @observe() -async def process_ai_message( - content: str, - app_id: str, - user_id: str, - session_id: str, - message_id: str, - db: AsyncSession, -): - """ - Process an AI message. Make a prediction about what the user is going to say to it. - """ - console.print(f"Processing AI message: {content}", style="bright_magenta") - - @sentry_sdk.trace @observe() -async def process_user_message( +async def process_message( content: str, - app_id: str, - user_id: str, - session_id: str, - message_id: str, + workspace_name: str, + peer_name: str, + target_name: str, + session_name: str | None, + message_id: int, db: AsyncSession, ): """ @@ -92,18 +106,32 @@ async def process_user_message( """ console.print(f"Processing User Message: {content}", style="orange1") process_start = os.times()[4] # Get current CPU time - logger.debug(f"Starting fact extraction for user message: {message_id}") - - # Get chat history and append current message - logger.debug(f"Retrieving chat history for session: {session_id}") - ( - short_history_text, - short_history_messages, - latest_short_summary, - ) = await history.get_summarized_history( - db, session_id, summary_type=history.SummaryType.SHORT + logger.debug( + "Starting fact extraction for user message %s in %s %s", + message_id, + "session" if session_name else "peer", + session_name if session_name else peer_name, ) - chat_history_str = f"{short_history_text}\nhuman: {content}" + + if session_name: + # Get chat history and append current message + logger.debug( + "Retrieving chat history for %s %s", + "session" if session_name else "peer", + session_name if session_name else peer_name, + ) + short_history_text = await history.get_summarized_history( + db, + workspace_name, + session_name, + peer_name, + cutoff=message_id, + summary_type=history.SummaryType.SHORT, + ) + + chat_history_str = f"{short_history_text}\nuser: {content}" + else: + chat_history_str = f"user: {content}" # Extract facts from chat history logger.debug("Extracting facts from chat history") @@ -114,14 +142,21 @@ async def process_user_message( logger.debug(f"Extracted {len(facts)} facts in {extract_time:.2f}s") # Save the facts to the collection - logger.debug(f"Setting up embedding store for app: {app_id}, user: {user_id}") - collection = await crud.get_or_create_user_protected_collection( - db=db, app_id=app_id, user_id=user_id + logger.debug( + f"Setting up embedding store for workspace: {workspace_name}, peer: {peer_name}" + ) + collection_name = ( + crud.construct_collection_name(peer_name, target_name) + if peer_name != target_name + else "global_representation" + ) + collection = await crud.get_or_create_collection( + db, workspace_name, peer_name, collection_name ) embedding_store = CollectionEmbeddingStore( - app_id=app_id, - user_id=user_id, - collection_id=collection.public_id, # type: ignore + workspace_name=workspace_name, + peer_name=peer_name, + collection_name=collection.name, ) # Filter out facts that are duplicates of existing facts in the vector store @@ -150,18 +185,30 @@ async def process_user_message( async def summarize_if_needed( - db: AsyncSession, app_id: str, session_id: str, user_id: str, message_id: str + db: AsyncSession, + workspace_name: str, + session_name: str | None, + peer_name: str, + message_id: int, ): + if not session_name: + return + summary_start = os.times()[4] - logger.debug("Checking if summaries should be created") + logger.debug("Checking if summaries should be created for session %s", session_name) # STEP 1: First check if we need a short summary (every 10 messages) ( should_create_short, short_messages, - latest_short_summary, + _, ) = await history.should_create_summary( - db, session_id, summary_type=history.SummaryType.SHORT + db, + workspace_name, + session_name, + peer_name, + message_id, + summary_type=history.SummaryType.SHORT, ) if should_create_short: @@ -173,7 +220,12 @@ async def summarize_if_needed( long_messages, latest_long_summary, ) = await history.should_create_summary( - db, session_id, summary_type=history.SummaryType.LONG + db, + workspace_name, + session_name, + peer_name, + message_id, + summary_type=history.SummaryType.LONG, ) # STEP 3: If we need a long summary, create it first before creating the short summary @@ -183,26 +235,23 @@ async def summarize_if_needed( ) try: # Get previous long summary context if available - previous_long_summary = ( - latest_long_summary.content if latest_long_summary else None + previous_long_summary_text = ( + latest_long_summary["content"] if latest_long_summary else None ) # Create a new long summary - long_summary_text = await history.create_summary( + new_long_summary = await history.create_summary( messages=long_messages, - previous_summary=previous_long_summary, + previous_summary_text=previous_long_summary_text, summary_type=history.SummaryType.LONG, ) - # Save the long summary as a metamessage and capture the returned object - latest_long_summary = await history.save_summary_metamessage( - db=db, - app_id=app_id, - user_id=user_id, - session_id=session_id, - message_id=message_id, - summary_content=long_summary_text, - message_count=len(long_messages), - summary_type=history.SummaryType.LONG, + + # Save the long summary + await history.save_summary( + db, + new_long_summary, + workspace_name, + session_name, ) logger.debug("Long summary created and saved successfully") except Exception as e: @@ -217,25 +266,23 @@ async def summarize_if_needed( f"Creating new short summary covering {len(short_messages)} messages" ) try: - previous_summary = ( - latest_long_summary.content if latest_long_summary else None + previous_long_summary_text = ( + latest_long_summary["content"] if latest_long_summary else None ) + # Create a new short summary - short_summary_text = await history.create_summary( + new_short_summary = await history.create_summary( messages=short_messages, - previous_summary=previous_summary, + previous_summary_text=previous_long_summary_text, summary_type=history.SummaryType.SHORT, ) - # Save the short summary as a metamessage - await history.save_summary_metamessage( - db=db, - app_id=app_id, - user_id=user_id, - session_id=session_id, - message_id=message_id, - summary_content=short_summary_text, - message_count=len(short_messages), - summary_type=history.SummaryType.SHORT, + + # Save the short summary + await history.save_summary( + db, + new_short_summary, + workspace_name, + session_name, ) logger.debug("Short summary created and saved successfully") except Exception as e: diff --git a/src/deriver/queue.py b/src/deriver/queue.py index c784482f..29145dfe 100644 --- a/src/deriver/queue.py +++ b/src/deriver/queue.py @@ -1,7 +1,9 @@ import asyncio import os import signal -from datetime import datetime, timedelta +from collections.abc import Sequence +from dataclasses import dataclass +from datetime import UTC, datetime, timedelta from logging import getLogger import sentry_sdk @@ -21,11 +23,30 @@ logger = getLogger(__name__) load_dotenv() +@dataclass(frozen=True) +class WorkUnit: + """ + Represents a unit of work in the queue system. + + A work unit is uniquely identified by the combination of session_id, + sender_name, target_name, and task_type. This allows multiple workers + to process different work units from the same session in parallel. + """ + + session_id: str + sender_name: str + target_name: str + task_type: str + + def __str__(self) -> str: + return f"({self.session_id}, {self.sender_name}, {self.target_name}, {self.task_type})" + + class QueueManager: def __init__(self): self.shutdown_event = asyncio.Event() self.active_tasks: set[asyncio.Task] = set() - self.owned_sessions: set[int] = set() + self.owned_work_units: set[WorkUnit] = set() self.queue_empty_flag = asyncio.Event() # Initialize from environment @@ -47,13 +68,13 @@ class QueueManager: self.active_tasks.add(task) task.add_done_callback(self.active_tasks.discard) - def track_session(self, session_id: int): - """Track a new session owned by this process""" - self.owned_sessions.add(session_id) + def track_work_unit(self, work_unit: WorkUnit): + """Track a new work unit owned by this process""" + self.owned_work_units.add(work_unit) - def untrack_session(self, session_id: int): - """Remove a session from tracking""" - self.owned_sessions.discard(session_id) + def untrack_work_unit(self, work_unit: WorkUnit): + """Remove a work unit from tracking""" + self.owned_work_units.discard(work_unit) async def initialize(self): """Setup signal handlers and start the main polling loop""" @@ -87,19 +108,25 @@ class QueueManager: await asyncio.gather(*self.active_tasks, return_exceptions=True) async def cleanup(self): - """Clean up owned sessions""" - if self.owned_sessions: - logger.info(f"Cleaning up {len(self.owned_sessions)} owned sessions...") + """Clean up owned work units""" + if self.owned_work_units: + logger.info(f"Cleaning up {len(self.owned_work_units)} owned work units...") try: # Use the tracked_db dependency for transaction safety async with tracked_db("queue_cleanup") as db: - await db.execute( - delete(models.ActiveQueueSession).where( - models.ActiveQueueSession.session_id.in_( - self.owned_sessions + for work_unit in self.owned_work_units: + await db.execute( + delete(models.ActiveQueueSession).where( + models.ActiveQueueSession.session_id + == work_unit.session_id, + models.ActiveQueueSession.sender_name + == work_unit.sender_name, + models.ActiveQueueSession.target_name + == work_unit.target_name, + models.ActiveQueueSession.task_type + == work_unit.task_type, ) ) - ) await db.commit() logger.info("Cleanup completed successfully") except Exception as e: @@ -111,34 +138,66 @@ class QueueManager: # Polling and Scheduling # ########################## - async def get_available_sessions(self, db: AsyncSession): - """Get available sessions that aren't being processed""" - # Clean up stale sessions - five_minutes_ago = datetime.utcnow() - timedelta(minutes=5) + async def get_available_work_units(self, db: AsyncSession) -> Sequence[WorkUnit]: + """ + Get available work units that aren't being processed. + Returns a list of WorkUnit objects. + """ + # Clean up stale work units + five_minutes_ago = datetime.now(UTC) - timedelta(minutes=5) await db.execute( delete(models.ActiveQueueSession).where( models.ActiveQueueSession.last_updated < five_minutes_ago ) ) - # Get available sessions + # Create the JSON path expressions once to ensure they're identical in SELECT and GROUP BY + sender_name_expr = models.QueueItem.payload["sender_name"].astext + target_name_expr = models.QueueItem.payload["target_name"].astext + task_type_expr = models.QueueItem.payload["task_type"].astext + + # Get available work units by extracting sender_name, target_name, task_type from payload + # We need to join with ActiveQueueSession to find units that aren't already being processed result = await db.execute( - select(models.QueueItem.session_id) + select( + models.QueueItem.session_id, + sender_name_expr.label("sender_name"), + target_name_expr.label("target_name"), + task_type_expr.label("task_type"), + ) .outerjoin( models.ActiveQueueSession, - models.QueueItem.session_id == models.ActiveQueueSession.session_id, + (models.QueueItem.session_id == models.ActiveQueueSession.session_id) + & (sender_name_expr == models.ActiveQueueSession.sender_name) + & (target_name_expr == models.ActiveQueueSession.target_name) + & (task_type_expr == models.ActiveQueueSession.task_type), ) - .where(models.QueueItem.processed == False) + .where(~models.QueueItem.processed) .where( - models.ActiveQueueSession.session_id == None - ) # Only sessions not in active_sessions - .group_by(models.QueueItem.session_id) - .limit(1) + models.ActiveQueueSession.id == None # noqa: E711 + ) # Only work units not in active_queue_sessions + .group_by( + models.QueueItem.session_id, + sender_name_expr, + target_name_expr, + task_type_expr, + ) + .limit(self.workers) # Process multiple work units in parallel ) - return result.scalars().all() + + rows = result.fetchall() + return [ + WorkUnit( + session_id=row.session_id, + sender_name=row.sender_name, + target_name=row.target_name, + task_type=row.task_type, + ) + for row in rows + ] async def polling_loop(self): - """Main polling loop to find and process new sessions""" + """Main polling loop to find and process new work units""" logger.debug("Starting polling loop") try: while not self.shutdown_event.is_set(): @@ -157,35 +216,38 @@ class QueueManager: # Use the dependency for transaction safety async with tracked_db("queue_polling_loop") as db: try: - new_sessions = await self.get_available_sessions(db) + new_work_units = await self.get_available_work_units(db) - if new_sessions and not self.shutdown_event.is_set(): - for session_id in new_sessions: + if new_work_units and not self.shutdown_event.is_set(): + for work_unit in new_work_units: try: - # Try to claim the session + # Try to claim the work unit await db.execute( insert(models.ActiveQueueSession).values( - session_id=session_id, + session_id=work_unit.session_id, + sender_name=work_unit.sender_name, + target_name=work_unit.target_name, + task_type=work_unit.task_type, ) ) await db.commit() - # Track this session - self.track_session(session_id) + # Track this work unit + self.track_work_unit(work_unit) logger.debug( - f"Claimed session {session_id} for processing" + f"Claimed work unit {work_unit} for processing" ) - # Create a new task for processing this session + # Create a new task for processing this work unit if not self.shutdown_event.is_set(): task = asyncio.create_task( - self.process_session(session_id) + self.process_work_unit(work_unit) ) self.add_task(task) except IntegrityError: # Note: rollback is handled by tracked_db dependency logger.debug( - f"Failed to claim session {session_id}, already owned" + f"Failed to claim work unit {work_unit}, already owned" ) else: self.queue_empty_flag.set() @@ -204,29 +266,29 @@ class QueueManager: ###################### @sentry_sdk.trace - async def process_session(self, session_id: int): - """Process all messages for a session""" - logger.debug(f"Starting to process session {session_id}") + async def process_work_unit(self, work_unit: WorkUnit): + """Process all messages for a specific work unit""" + logger.debug(f"Starting to process work unit {work_unit}") # Use the tracked_db dependency for transaction safety async with ( self.semaphore, - tracked_db("queue_process_session") as db, - ): # Hold the semaphore for the entire session duration + tracked_db("queue_process_work_unit") as db, + ): # Hold the semaphore for the entire work unit duration try: message_count = 0 while not self.shutdown_event.is_set(): - message = await self.get_next_message(db, session_id) + message = await self.get_next_message(db, work_unit) if not message: - logger.debug(f"No more messages for session {session_id}") + logger.debug(f"No more messages for work unit {work_unit}") break message_count += 1 logger.debug( - f"Processing message {message.id} for session {session_id} (message {message_count})" + f"Processing message {message.id} for work unit {work_unit} (message {message_count})" ) try: logger.info( - f"Processing message {message.id} from session {session_id}" + f"Processing message {message.id} from work unit {work_unit}" ) await process_item(db, payload=message.payload) logger.debug(f"Successfully processed message {message.id}") @@ -245,39 +307,57 @@ class QueueManager: if self.shutdown_event.is_set(): logger.debug( - f"Shutdown requested, stopping processing for session {session_id}" + f"Shutdown requested, stopping processing for work unit {work_unit}" ) break - # Update last_updated timestamp to show this session is still being processed + # Update last_updated timestamp to show this work unit is still being processed await db.execute( update(models.ActiveQueueSession) - .where(models.ActiveQueueSession.session_id == session_id) + .where( + models.ActiveQueueSession.session_id + == work_unit.session_id, + models.ActiveQueueSession.sender_name + == work_unit.sender_name, + models.ActiveQueueSession.target_name + == work_unit.target_name, + models.ActiveQueueSession.task_type == work_unit.task_type, + ) .values(last_updated=func.now()) ) await db.commit() logger.debug( - f"Completed processing session {session_id}, processed {message_count} messages" + f"Completed processing work unit {work_unit}, processed {message_count} messages" ) finally: - # Remove session from active_sessions when done - logger.debug(f"Removing session {session_id} from active sessions") + # Remove work unit from active_queue_sessions when done + logger.debug(f"Removing work unit {work_unit} from active sessions") await db.execute( delete(models.ActiveQueueSession).where( - models.ActiveQueueSession.session_id == session_id + models.ActiveQueueSession.session_id == work_unit.session_id, + models.ActiveQueueSession.sender_name == work_unit.sender_name, + models.ActiveQueueSession.target_name == work_unit.target_name, + models.ActiveQueueSession.task_type == work_unit.task_type, ) ) await db.commit() - self.untrack_session(session_id) + self.untrack_work_unit(work_unit) @sentry_sdk.trace - async def get_next_message(self, db: AsyncSession, session_id: int): - """Get the next unprocessed message for a session""" + async def get_next_message(self, db: AsyncSession, work_unit: WorkUnit): + """Get the next unprocessed message for a specific work unit""" result = await db.execute( select(models.QueueItem) - .where(models.QueueItem.session_id == session_id) - .where(models.QueueItem.processed == False) + .where(models.QueueItem.session_id == work_unit.session_id) + .where( + models.QueueItem.payload["sender_name"].astext == work_unit.sender_name + ) + .where( + models.QueueItem.payload["target_name"].astext == work_unit.target_name + ) + .where(models.QueueItem.payload["task_type"].astext == work_unit.task_type) + .where(~models.QueueItem.processed) .order_by(models.QueueItem.id) .with_for_update(skip_locked=True) .limit(1) diff --git a/src/deriver/tom/__init__.py b/src/deriver/tom/__init__.py index 5d6a6452..b8901e41 100644 --- a/src/deriver/tom/__init__.py +++ b/src/deriver/tom/__init__.py @@ -11,18 +11,17 @@ from .single_prompt import ( async def get_tom_inference( chat_history: str, - session_id: str, user_representation: str = "None", method: str = "conversational", **kwargs, ) -> str: if method == "conversational": return await get_tom_inference_conversational( - chat_history, session_id, user_representation, **kwargs + chat_history, user_representation, **kwargs ) elif method == "single_prompt": return await get_tom_inference_single_prompt( - chat_history, session_id, user_representation, **kwargs + chat_history, user_representation, **kwargs ) else: raise ValueError(f"Invalid method: {method}") @@ -30,7 +29,6 @@ async def get_tom_inference( async def get_user_representation( chat_history: str, - session_id: str, user_representation: str = "None", tom_inference: str = "None", method: str = "conversational", @@ -38,15 +36,15 @@ async def get_user_representation( ) -> str: if method == "conversational": return await get_user_representation_conversational( - chat_history, session_id, user_representation, tom_inference, **kwargs + chat_history, user_representation, tom_inference, **kwargs ) elif method == "single_prompt": return await get_user_representation_single_prompt( - chat_history, session_id, user_representation, tom_inference, **kwargs + chat_history, user_representation, tom_inference, **kwargs ) elif method == "long_term": return await get_user_representation_long_term( - chat_history, session_id, user_representation, tom_inference, **kwargs + chat_history, user_representation, tom_inference, **kwargs ) else: raise ValueError(f"Invalid method: {method}") diff --git a/src/deriver/tom/conversational.py b/src/deriver/tom/conversational.py index 98b91bfe..636c7eb8 100644 --- a/src/deriver/tom/conversational.py +++ b/src/deriver/tom/conversational.py @@ -2,11 +2,10 @@ import os import sentry_sdk from anthropic import Anthropic +from anthropic.types import MessageParam, TextBlock from langfuse.decorators import langfuse_context, observe from sentry_sdk.ai.monitoring import ai_track -# Place the code below at the beginning of your application to initialize the tracer - # Initialize the Anthropic client anthropic = Anthropic( api_key=os.getenv("ANTHROPIC_API_KEY"), @@ -17,10 +16,10 @@ anthropic = Anthropic( @ai_track("Tom Inference") @observe() async def get_tom_inference_conversational( - chat_history: str, session_id: str, user_representation: str = "None" + chat_history: str, user_representation: str = "None" ) -> str: with sentry_sdk.start_transaction(op="tom-inference", name="ToM Inference"): - messages = [ + messages: list[MessageParam] = [ { "role": "user", "content": [ @@ -77,21 +76,24 @@ async def get_tom_inference_conversational( temperature=0, messages=messages, ) - return message.content[0].text + # skip blocks that are not text and return the first text block + for block in message.content: + if isinstance(block, TextBlock): + return block.text + raise RuntimeError("No text block returned by LLM") @ai_track("User Representation") @observe() async def get_user_representation_conversational( chat_history: str, - session_id: str, user_representation: str = "None", tom_inference: str = "None", ) -> str: with sentry_sdk.start_transaction( op="user-representation-inference", name="User Representation" ): - messages = [ + messages: list[MessageParam] = [ { "role": "user", "content": [ @@ -148,4 +150,8 @@ async def get_user_representation_conversational( temperature=0, messages=messages, ) - return message.content[0].text + # skip blocks that are not text and return the first text block + for block in message.content: + if isinstance(block, TextBlock): + return block.text + raise RuntimeError("No text block returned by LLM") diff --git a/src/deriver/tom/embeddings.py b/src/deriver/tom/embeddings.py index 059ea791..b98ebf28 100644 --- a/src/deriver/tom/embeddings.py +++ b/src/deriver/tom/embeddings.py @@ -1,7 +1,5 @@ import logging -from sqlalchemy.ext.asyncio import AsyncSession - from ... import crud, schemas from ...dependencies import tracked_db @@ -9,17 +7,17 @@ logger = logging.getLogger(__name__) class CollectionEmbeddingStore: - def __init__(self, app_id: str, user_id: str, collection_id: str): - self.app_id = app_id - self.user_id = user_id - self.collection_id = collection_id + def __init__(self, workspace_name: str, peer_name: str, collection_name: str): + self.workspace_name = workspace_name + self.peer_name = peer_name + self.collection_name = collection_name async def save_facts( self, facts: list[str], replace_duplicates: bool = True, similarity_threshold: float = 0.85, - message_id: str = None, + message_id: int | None = None, ) -> None: """Save facts to the collection. @@ -33,14 +31,16 @@ class CollectionEmbeddingStore: # Create document with duplicate checking try: metadata = {} - if message_id: + if message_id is not None: metadata["message_id"] = message_id await crud.create_document( db, - document=schemas.DocumentCreate(content=fact, metadata=metadata), - app_id=self.app_id, - user_id=self.user_id, - collection_id=self.collection_id, + document=schemas.DocumentCreate( + content=fact, metadata=metadata + ), + workspace_name=self.workspace_name, + peer_name=self.peer_name, + collection_name=self.collection_name, duplicate_threshold=1 - similarity_threshold, # Convert similarity to distance ) @@ -64,9 +64,9 @@ class CollectionEmbeddingStore: async with tracked_db("embedding_store.get_relevant_facts") as db: documents = await crud.query_documents( db, - app_id=self.app_id, - user_id=self.user_id, - collection_id=self.collection_id, + workspace_name=self.workspace_name, + peer_name=self.peer_name, + collection_name=self.collection_name, query=query, max_distance=max_distance, top_k=top_k, @@ -94,9 +94,9 @@ class CollectionEmbeddingStore: # Check for duplicates using the crud function duplicates = await crud.get_duplicate_documents( db, - app_id=self.app_id, - user_id=self.user_id, - collection_id=self.collection_id, + workspace_name=self.workspace_name, + peer_name=self.peer_name, + collection_name=self.collection_name, content=fact, similarity_threshold=similarity_threshold, ) diff --git a/src/deriver/tom/long_term.py b/src/deriver/tom/long_term.py index a9a7f2c5..7b8bf264 100644 --- a/src/deriver/tom/long_term.py +++ b/src/deriver/tom/long_term.py @@ -26,7 +26,6 @@ MAX_FACT_DISTANCE = 0.85 @observe() async def get_user_representation_long_term( chat_history: str, - session_id: str, user_representation: str = "None", tom_inference: str = "None", facts: Optional[list[str]] = None, diff --git a/src/deriver/tom/single_prompt.py b/src/deriver/tom/single_prompt.py index 3696eb5e..71ea552e 100644 --- a/src/deriver/tom/single_prompt.py +++ b/src/deriver/tom/single_prompt.py @@ -105,7 +105,6 @@ UPDATES: @observe() async def get_tom_inference_single_prompt( chat_history: str, - session_id: str, user_representation: Optional[str] = None, **kwargs, ) -> str: @@ -153,7 +152,6 @@ async def get_tom_inference_single_prompt( @observe() async def get_user_representation_single_prompt( chat_history: str, - session_id: str, user_representation: Optional[str] = None, tom_inference: Optional[str] = None, **kwargs, diff --git a/src/main.py b/src/main.py index 5405b644..4a36b3b4 100644 --- a/src/main.py +++ b/src/main.py @@ -15,14 +15,11 @@ from sentry_sdk.integrations.starlette import StarletteIntegration from src.db import engine, request_context from src.exceptions import HonchoException from src.routers import ( - apps, - collections, - documents, keys, messages, - metamessages, + peers, sessions, - users, + workspaces, ) from src.security import create_admin_jwt @@ -82,7 +79,7 @@ if SENTRY_ENABLED: # Sentry SDK's default behavior: # - Captures INFO+ level logs as breadcrumbs # - Captures ERROR+ level logs as Sentry events - # + # # For custom log levels, use the LoggingIntegration class: # sentry_sdk.init(..., integrations=[LoggingIntegration(level=logging.INFO, event_level=logging.ERROR)]) sentry_sdk.init( @@ -117,7 +114,7 @@ app = FastAPI( title="Honcho API", summary="The Identity Layer for the Agentic World", description="""Honcho is a platform for giving agents user-centric memory and social cognition""", - version="1.1.0", + version="2.0.0", contact={ "name": "Plastic Labs", "url": "https://honcho.dev", @@ -144,13 +141,10 @@ router = APIRouter(prefix="/apps/{app_id}/users/{user_id}") add_pagination(app) -app.include_router(apps.router, prefix="/v1") -app.include_router(users.router, prefix="/v1") +app.include_router(workspaces.router, prefix="/v1") +app.include_router(peers.router, prefix="/v1") app.include_router(sessions.router, prefix="/v1") app.include_router(messages.router, prefix="/v1") -app.include_router(metamessages.router, prefix="/v1") -app.include_router(collections.router, prefix="/v1") -app.include_router(documents.router, prefix="/v1") app.include_router(keys.router, prefix="/v1") diff --git a/src/models.py b/src/models.py index 71587653..077956c1 100644 --- a/src/models.py +++ b/src/models.py @@ -1,5 +1,7 @@ import datetime +from logging import getLogger +import tiktoken from dotenv import load_dotenv from nanoid import generate as generate_nanoid from pgvector.sqlalchemy import Vector @@ -7,11 +9,16 @@ from sqlalchemy import ( BigInteger, Boolean, CheckConstraint, + Column, DateTime, ForeignKey, + ForeignKeyConstraint, Identity, Index, + Integer, + Table, UniqueConstraint, + event, text, ) from sqlalchemy.dialects.postgresql import JSONB, TEXT @@ -22,265 +29,299 @@ from .db import Base load_dotenv() +logger = getLogger(__name__) -class App(Base): - __tablename__ = "apps" - id: Mapped[int] = mapped_column( - BigInteger, Identity(), primary_key=True, index=True, autoincrement=True - ) - public_id: Mapped[str] = mapped_column( - TEXT, index=True, unique=True, default=generate_nanoid - ) +# Initialize tiktoken encoder for token counting for message content +tokenizer = tiktoken.get_encoding("cl100k_base") + + +def count_tokens(text: str) -> int: + """Count tokens in a text string using tiktoken.""" + if not text: + return 0 + try: + return len(tokenizer.encode(text)) + except Exception as e: + # Fallback: rough estimation (4 chars per token) + logger.warning( + f"Error counting tokens for text: {text[:50]}{'...' if len(text) > 50 else ''}, using fallback (4 chars per token). Error: {str(e)}" + ) + return len(text) // 4 + + +# Association table for many-to-many relationship between sessions and peers +session_peers_table = Table( + "session_peers", + Base.metadata, + Column( + "workspace_name", + TEXT, + ForeignKey("workspaces.name"), + primary_key=True, + nullable=False, + ), + Column( + "session_name", + TEXT, + primary_key=True, + nullable=False, + ), + Column("peer_name", TEXT, primary_key=True, nullable=False), + Column("configuration", JSONB, default=dict), + Column("internal_metadata", JSONB, default=dict), + Column( + "joined_at", + DateTime(timezone=True), + nullable=False, + default=func.now(), + ), + Column( + "left_at", + DateTime(timezone=True), + nullable=True, + ), + # Composite foreign key constraint for sessions + ForeignKeyConstraint( + ["session_name", "workspace_name"], + ["sessions.name", "sessions.workspace_name"], + ), + # Composite foreign key constraint for peers + ForeignKeyConstraint( + ["peer_name", "workspace_name"], + ["peers.name", "peers.workspace_name"], + ), +) + + +class Workspace(Base): + __tablename__ = "workspaces" + id: Mapped[str] = mapped_column(TEXT, default=generate_nanoid, primary_key=True) name: Mapped[str] = mapped_column(TEXT, index=True, unique=True) - users = relationship("User", back_populates="app") + peers = relationship("Peer", back_populates="workspace") created_at: Mapped[datetime.datetime] = mapped_column( DateTime(timezone=True), index=True, default=func.now() ) - h_metadata: Mapped[dict] = mapped_column("metadata", JSONB, default={}) + h_metadata: Mapped[dict] = mapped_column("metadata", JSONB, default=dict) + internal_metadata: Mapped[dict] = mapped_column( + "internal_metadata", JSONB, default=dict + ) + configuration: Mapped[dict] = mapped_column(JSONB, default=dict) __table_args__ = ( - CheckConstraint("length(public_id) = 21", name="public_id_length"), + CheckConstraint("length(id) = 21", name="id_length"), CheckConstraint("length(name) <= 512", name="name_length"), - CheckConstraint("public_id ~ '^[A-Za-z0-9_-]+$'", name="public_id_format"), + CheckConstraint("id ~ '^[A-Za-z0-9_-]+$'", name="id_format"), ) -class User(Base): - __tablename__ = "users" - id: Mapped[int] = mapped_column( - BigInteger, Identity(), primary_key=True, index=True, autoincrement=True - ) - public_id: Mapped[str] = mapped_column( - TEXT, index=True, unique=True, default=generate_nanoid - ) +class Peer(Base): + __tablename__ = "peers" + id: Mapped[str] = mapped_column(TEXT, default=generate_nanoid, primary_key=True) name: Mapped[str] = mapped_column(TEXT, index=True) - h_metadata: Mapped[dict] = mapped_column("metadata", JSONB, default={}) + h_metadata: Mapped[dict] = mapped_column("metadata", JSONB, default=dict) + internal_metadata: Mapped[dict] = mapped_column( + "internal_metadata", JSONB, default=dict + ) created_at: Mapped[datetime.datetime] = mapped_column( DateTime(timezone=True), index=True, default=func.now() ) - app_id: Mapped[str] = mapped_column(ForeignKey("apps.public_id"), index=True) - app = relationship("App", back_populates="users") - sessions = relationship("Session", back_populates="user") - collections = relationship("Collection", back_populates="user") - metamessages = relationship("Metamessage", back_populates="user") + workspace_name: Mapped[str] = mapped_column( + ForeignKey("workspaces.name"), index=True + ) + configuration: Mapped[dict] = mapped_column(JSONB, default=dict) + + workspace = relationship("Workspace", back_populates="peers") + sessions = relationship( + "Session", secondary=session_peers_table, back_populates="peers" + ) + collections = relationship("Collection", back_populates="peer") __table_args__ = ( - UniqueConstraint("name", "app_id", name="unique_name_app_user"), - CheckConstraint("length(public_id) = 21", name="public_id_length"), + UniqueConstraint("name", "workspace_name", name="unique_name_workspace_peer"), + CheckConstraint("length(id) = 21", name="id_length"), CheckConstraint("length(name) <= 512", name="name_length"), - CheckConstraint("public_id ~ '^[A-Za-z0-9_-]+$'", name="public_id_format"), - Index("idx_users_app_lookup", "app_id", "public_id"), + CheckConstraint("id ~ '^[A-Za-z0-9_-]+$'", name="id_format"), + Index("idx_peers_workspace_lookup", "workspace_name", "name"), ) def __repr__(self) -> str: - return f"User(id={self.id}, app_id={self.app_id}, public_id={self.public_id} created_at={self.created_at}, h_metadata={self.h_metadata})" + return f"Peer(id={self.id}, name={self.name}, workspace_name={self.workspace_name}, created_at={self.created_at}, h_metadata={self.h_metadata}, configuration={self.configuration})" class Session(Base): __tablename__ = "sessions" - id: Mapped[int] = mapped_column( - BigInteger, Identity(), primary_key=True, index=True, autoincrement=True - ) - public_id: Mapped[str] = mapped_column( - TEXT, index=True, unique=True, default=generate_nanoid - ) + id: Mapped[str] = mapped_column(TEXT, primary_key=True, default=generate_nanoid) + name: Mapped[str] = mapped_column(TEXT, index=True) is_active: Mapped[bool] = mapped_column(default=True) - h_metadata: Mapped[dict] = mapped_column("metadata", JSONB, default={}) + h_metadata: Mapped[dict] = mapped_column("metadata", JSONB, default=dict) + internal_metadata: Mapped[dict] = mapped_column( + "internal_metadata", JSONB, default=dict + ) created_at: Mapped[datetime.datetime] = mapped_column( DateTime(timezone=True), index=True, default=func.now() ) messages = relationship("Message", back_populates="session") - metamessages = relationship("Metamessage", back_populates="session") - user_id: Mapped[str] = mapped_column(ForeignKey("users.public_id"), index=True) - app_id: Mapped[str] = mapped_column(ForeignKey("apps.public_id"), index=True) - user = relationship("User", back_populates="sessions") + workspace_name: Mapped[str] = mapped_column( + ForeignKey("workspaces.name"), index=True + ) + configuration: Mapped[dict] = mapped_column(JSONB, default=dict) + + peers = relationship( + "Peer", secondary=session_peers_table, back_populates="sessions" + ) __table_args__ = ( - CheckConstraint("length(public_id) = 21", name="public_id_length"), - CheckConstraint("public_id ~ '^[A-Za-z0-9_-]+$'", name="public_id_format"), - Index("idx_sessions_user_lookup", "user_id", "public_id"), + UniqueConstraint("name", "workspace_name", name="unique_session_name"), + CheckConstraint("length(name) <= 512", name="name_length"), + CheckConstraint("length(id) = 21", name="id_length"), + CheckConstraint("id ~ '^[A-Za-z0-9_-]+$'", name="id_format"), ) def __repr__(self) -> str: - return f"Session(id={self.id}, user_id={self.user_id}, is_active={self.is_active}, created_at={self.created_at}, h_metadata={self.h_metadata})" + return f"Session(id={self.id}, name={self.name}, workspace_name={self.workspace_name}, is_active={self.is_active}, created_at={self.created_at}, h_metadata={self.h_metadata})" class Message(Base): __tablename__ = "messages" id: Mapped[int] = mapped_column( - BigInteger, Identity(), primary_key=True, index=True, autoincrement=True + BigInteger, Identity(), primary_key=True, autoincrement=True ) public_id: Mapped[str] = mapped_column( TEXT, index=True, unique=True, default=generate_nanoid ) - session_id: Mapped[str] = mapped_column( - ForeignKey("sessions.public_id"), index=True - ) - is_user: Mapped[bool] + session_name: Mapped[str | None] = mapped_column(index=True, nullable=True) content: Mapped[str] = mapped_column(TEXT) - h_metadata: Mapped[dict] = mapped_column("metadata", JSONB, default={}) + h_metadata: Mapped[dict] = mapped_column("metadata", JSONB, default=dict) + internal_metadata: Mapped[dict] = mapped_column( + "internal_metadata", JSONB, default=dict + ) + token_count: Mapped[int] = mapped_column(Integer, default=0, nullable=False) created_at: Mapped[datetime.datetime] = mapped_column( DateTime(timezone=True), index=True, default=func.now() ) session = relationship("Session", back_populates="messages") - metamessages = relationship("Metamessage", back_populates="message") - user_id: Mapped[str] = mapped_column(ForeignKey("users.public_id"), index=True) - app_id: Mapped[str] = mapped_column(ForeignKey("apps.public_id"), index=True) + peer_name: Mapped[str] = mapped_column(index=True) + workspace_name: Mapped[str] = mapped_column( + ForeignKey("workspaces.name"), index=True + ) __table_args__ = ( CheckConstraint("length(public_id) = 21", name="public_id_length"), CheckConstraint("public_id ~ '^[A-Za-z0-9_-]+$'", name="public_id_format"), CheckConstraint("length(content) <= 65535", name="content_length"), + # Composite foreign key constraint for sessions + ForeignKeyConstraint( + ["session_name", "workspace_name"], + ["sessions.name", "sessions.workspace_name"], + ), + # Composite foreign key constraint for peers + ForeignKeyConstraint( + ["peer_name", "workspace_name"], + ["peers.name", "peers.workspace_name"], + ), Index( "idx_messages_session_lookup", - "session_id", + "session_name", "id", - postgresql_include=["public_id", "is_user", "created_at"], + postgresql_include=["id", "created_at"], + ), + # Full text search index on content column + Index( + "idx_messages_content_gin", + text("to_tsvector('english', content)"), + postgresql_using="gin", ), ) def __repr__(self) -> str: - return f"Message(id={self.id}, session_id={self.session_id}, is_user={self.is_user}, content={self.content[10:]})" + return f"Message(id={self.id}, session_name={self.session_name}, peer_name={self.peer_name}, content={self.content})" -class Metamessage(Base): - __tablename__ = "metamessages" - id: Mapped[int] = mapped_column( - BigInteger, Identity(), primary_key=True, index=True, autoincrement=True - ) - public_id: Mapped[str] = mapped_column( - TEXT, index=True, unique=True, default=generate_nanoid - ) - label: Mapped[str] = mapped_column(TEXT, index=True) - content: Mapped[str] = mapped_column(TEXT) - - # Foreign keys - message_id is now optional - user_id: Mapped[str] = mapped_column(ForeignKey("users.public_id"), index=True) - app_id: Mapped[str] = mapped_column(ForeignKey("apps.public_id"), index=True) - session_id: Mapped[str | None] = mapped_column( - ForeignKey("sessions.public_id"), index=True, nullable=True - ) - message_id: Mapped[str | None] = mapped_column( - ForeignKey("messages.public_id"), index=True, nullable=True - ) - - # Relationships - user = relationship("User", back_populates="metamessages") - session = relationship("Session", back_populates="metamessages") - message = relationship("Message", back_populates="metamessages") - - created_at: Mapped[datetime.datetime] = mapped_column( - DateTime(timezone=True), index=True, default=func.now() - ) - h_metadata: Mapped[dict] = mapped_column("metadata", JSONB, default={}) - - __table_args__ = ( - CheckConstraint("length(public_id) = 21", name="public_id_length"), - CheckConstraint("public_id ~ '^[A-Za-z0-9_-]+$'", name="public_id_format"), - CheckConstraint("length(content) <= 65535", name="content_length"), - CheckConstraint("length(label) <= 512", name="label_length"), - # Added constraints to ensure consistency - CheckConstraint( - "(message_id IS NULL) OR (session_id IS NOT NULL)", - name="message_requires_session", - ), - # Keep existing index - Index( - "idx_metamessages_lookup", - "label", - text("id DESC"), - postgresql_include=["public_id", "message_id", "created_at"], - ), - # Indices for user, session, and message lookups - Index( - "idx_metamessages_user_lookup", - "user_id", - "label", - text("id DESC"), - ), - Index( - "idx_metamessages_session_lookup", - "session_id", - "label", - text("id DESC"), - ), - Index( - "idx_metamessages_message_lookup", - "message_id", - "label", - text("id DESC"), - ), - ) - - def __repr__(self) -> str: - return f"Metamessages(id={self.id}, user_id={self.user_id}, session_id={self.session_id}, message_id={self.message_id}, label={self.label})" +@event.listens_for(Message, "before_insert") +def calculate_token_count_on_insert(_mapper, _connection, target): + """Calculate token count before inserting a new message.""" + target.token_count = count_tokens(target.content) class Collection(Base): __tablename__ = "collections" - id: Mapped[int] = mapped_column( - BigInteger, Identity(), primary_key=True, index=True, autoincrement=True - ) - public_id: Mapped[str] = mapped_column( - TEXT, index=True, unique=True, default=generate_nanoid - ) + id: Mapped[str] = mapped_column(TEXT, default=generate_nanoid, primary_key=True) name: Mapped[str] = mapped_column(TEXT, index=True) created_at: Mapped[datetime.datetime] = mapped_column( DateTime(timezone=True), index=True, default=func.now() ) - h_metadata: Mapped[dict] = mapped_column("metadata", JSONB, default={}) + h_metadata: Mapped[dict] = mapped_column("metadata", JSONB, default=dict) + internal_metadata: Mapped[dict] = mapped_column( + "internal_metadata", JSONB, default=dict + ) documents = relationship( "Document", back_populates="collection", cascade="all, delete, delete-orphan" ) - user = relationship("User", back_populates="collections") - user_id: Mapped[str] = mapped_column( - TEXT, ForeignKey("users.public_id"), index=True + peer = relationship("Peer", back_populates="collections") + peer_name: Mapped[str] = mapped_column(TEXT, index=True) + workspace_name: Mapped[str] = mapped_column( + ForeignKey("workspaces.name"), index=True ) - app_id: Mapped[str] = mapped_column(ForeignKey("apps.public_id"), index=True) __table_args__ = ( - UniqueConstraint("name", "user_id", name="unique_name_collection_user"), - CheckConstraint("length(public_id) = 21", name="public_id_length"), - CheckConstraint("public_id ~ '^[A-Za-z0-9_-]+$'", name="public_id_format"), + UniqueConstraint( + "name", "peer_name", "workspace_name", name="unique_name_collection_peer" + ), + CheckConstraint("length(id) = 21", name="id_length"), + CheckConstraint("id ~ '^[A-Za-z0-9_-]+$'", name="id_format"), CheckConstraint("length(name) <= 512", name="name_length"), + # Composite foreign key constraint for peers + ForeignKeyConstraint( + ["peer_name", "workspace_name"], + ["peers.name", "peers.workspace_name"], + ), ) class Document(Base): __tablename__ = "documents" - id: Mapped[int] = mapped_column( - BigInteger, Identity(), primary_key=True, index=True, autoincrement=True + id: Mapped[str] = mapped_column(TEXT, default=generate_nanoid, primary_key=True) + internal_metadata: Mapped[dict] = mapped_column( + "internal_metadata", JSONB, default=dict ) - public_id: Mapped[str] = mapped_column( - TEXT, index=True, unique=True, default=generate_nanoid - ) - h_metadata: Mapped[dict] = mapped_column("metadata", JSONB, default={}) content: Mapped[str] = mapped_column(TEXT) embedding = mapped_column(Vector(1536)) created_at: Mapped[datetime.datetime] = mapped_column( DateTime(timezone=True), index=True, default=func.now() ) - collection_id: Mapped[str] = mapped_column( - TEXT, ForeignKey("collections.public_id"), index=True + collection_name: Mapped[str] = mapped_column(TEXT, index=True) + peer_name: Mapped[str] = mapped_column(index=True) + workspace_name: Mapped[str] = mapped_column( + ForeignKey("workspaces.name"), index=True ) - user_id: Mapped[str] = mapped_column(ForeignKey("users.public_id"), index=True) - app_id: Mapped[str] = mapped_column(ForeignKey("apps.public_id"), index=True) collection = relationship("Collection", back_populates="documents") __table_args__ = ( - CheckConstraint("length(public_id) = 21", name="public_id_length"), + CheckConstraint("length(id) = 21", name="id_length"), CheckConstraint("length(content) <= 65535", name="content_length"), - CheckConstraint("public_id ~ '^[A-Za-z0-9_-]+$'", name="public_id_format"), + CheckConstraint("id ~ '^[A-Za-z0-9_-]+$'", name="id_format"), + # Composite foreign key constraint for collections + ForeignKeyConstraint( + ["collection_name", "peer_name", "workspace_name"], + ["collections.name", "collections.peer_name", "collections.workspace_name"], + ), + # Composite foreign key constraint for peers + ForeignKeyConstraint( + ["peer_name", "workspace_name"], + ["peers.name", "peers.workspace_name"], + ), # HNSW index on embedding column Index( "idx_documents_embedding_hnsw", "embedding", - postgresql_using="hnsw", # HNSW index type - postgresql_with={"m": 16, "ef_construction": 64}, # HNSW parameters - postgresql_ops={"embedding": "vector_cosine_ops"}, # Cosine distance operator + postgresql_using="hnsw", # HNSW index type + postgresql_with={"m": 16, "ef_construction": 64}, # HNSW parameters + postgresql_ops={ + "embedding": "vector_cosine_ops" + }, # Cosine distance operator ), ) @@ -290,7 +331,9 @@ class QueueItem(Base): id: Mapped[int] = mapped_column( BigInteger, Identity(), primary_key=True, autoincrement=True ) - session_id: Mapped[int] = mapped_column(ForeignKey("sessions.id"), index=True) + session_id: Mapped[str] = mapped_column( + ForeignKey("sessions.id"), index=True, nullable=True + ) payload: Mapped[dict] = mapped_column(JSONB, nullable=False) processed: Mapped[bool] = mapped_column(Boolean, default=False) @@ -298,7 +341,27 @@ class QueueItem(Base): class ActiveQueueSession(Base): __tablename__ = "active_queue_sessions" - session_id: Mapped[int] = mapped_column(BigInteger, primary_key=True, index=True) + id: Mapped[str] = mapped_column(TEXT, default=generate_nanoid, primary_key=True) + session_id: Mapped[str | None] = mapped_column( + ForeignKey("sessions.id"), nullable=True + ) + sender_name: Mapped[str | None] = mapped_column(TEXT, nullable=True) + target_name: Mapped[str | None] = mapped_column(TEXT, nullable=True) + task_type: Mapped[str] = mapped_column(TEXT) last_updated: Mapped[datetime.datetime] = mapped_column( DateTime(timezone=True), default=func.now(), onupdate=func.now() ) + + __table_args__ = ( + UniqueConstraint( + "session_id", + "sender_name", + "target_name", + "task_type", + name="unique_active_queue_session", + ), + ) + + +class SessionPeer(Base): + __table__ = session_peers_table diff --git a/src/routers/apps.py b/src/routers/apps.py deleted file mode 100644 index 9d0443e4..00000000 --- a/src/routers/apps.py +++ /dev/null @@ -1,138 +0,0 @@ -import logging -from typing import Optional - -from fastapi import APIRouter, Body, Depends, Path, Query -from fastapi_pagination import Page -from fastapi_pagination.ext.sqlalchemy import paginate - -from src import crud, schemas -from src.dependencies import db -from src.exceptions import AuthenticationException, ResourceNotFoundException -from src.security import JWTParams, require_auth - -logger = logging.getLogger(__name__) - -router = APIRouter( - prefix="/apps", - tags=["apps"], -) - - -@router.get("", response_model=schemas.App) -async def get_app( - app_id: Optional[str] = Query( - None, description="App ID to retrieve. If not provided, uses JWT token" - ), - jwt_params: JWTParams = Depends(require_auth()), - db=db, -): - """ - Get an App by ID. - - If app_id is provided as a query parameter, it uses that (must match JWT app_id). - Otherwise, it uses the app_id from the JWT token. - """ - # If app_id provided in query, check if it matches jwt or user is admin - if app_id: - if not jwt_params.ad and jwt_params.ap != app_id: - raise AuthenticationException("Unauthorized access to resource") - target_app_id = app_id - else: - # Use app_id from JWT - if not jwt_params.ap: - raise AuthenticationException("App ID not found in query parameter or JWT") - target_app_id = jwt_params.ap - - return await crud.get_app(db, app_id=target_app_id) - - -@router.post( - "/list", - response_model=Page[schemas.App], - dependencies=[Depends(require_auth(admin=True))], -) -async def get_all_apps( - options: Optional[schemas.AppGet] = Body( - None, description="Filtering and pagination options for the apps list" - ), - reverse: Optional[bool] = Query( - False, description="Whether to reverse the order of results" - ), - db=db, -): - """Get all Apps""" - filter_param = None - if options and hasattr(options, "filter"): - filter_param = options.filter - if filter_param == {}: - filter_param = None - - return await paginate( - db, - await crud.get_all_apps( - reverse=reverse, - filter=filter_param, - ), - ) - - -@router.get( - "/name/{name}", - response_model=schemas.App, - dependencies=[Depends(require_auth(admin=True))], -) -async def get_app_by_name( - name: str = Path(..., description="Name of the app to retrieve"), - db=db, -): - """Get an App by Name""" - # ResourceNotFoundException will be caught by global handler if app not found - app = await crud.get_app_by_name(db, name=name) - return app - - -@router.post( - "", response_model=schemas.App, dependencies=[Depends(require_auth(admin=True))] -) -async def create_app( - app: schemas.AppCreate = Body(..., description="App creation parameters"), - db=db, -): - """Create a new App""" - honcho_app = await crud.create_app(db, app=app) - return honcho_app - - -@router.get( - "/get_or_create/{name}", - response_model=schemas.App, - dependencies=[Depends(require_auth(admin=True))], -) -async def get_or_create_app( - name: str = Path(..., description="Name of the app to get or create"), - db=db, -): - """Get or Create an App""" - try: - app = await crud.get_app_by_name(db=db, name=name) - return app - except ResourceNotFoundException: - # App doesn't exist, create it - app = await create_app(db=db, app=schemas.AppCreate(name=name)) - return app - - -@router.put( - "/{app_id}", - response_model=schemas.App, - dependencies=[Depends(require_auth(app_id="app_id"))], -) -async def update_app( - app_id: str = Path(..., description="ID of the app to update"), - app: schemas.AppUpdate = Body(..., description="Updated app parameters"), - db=db, -): - """Update an App""" - # ResourceNotFoundException will be caught by global handler if app not found - honcho_app = await crud.update_app(db, app_id=app_id, app=app) - return honcho_app diff --git a/src/routers/collections.py b/src/routers/collections.py deleted file mode 100644 index ca1adeb2..00000000 --- a/src/routers/collections.py +++ /dev/null @@ -1,190 +0,0 @@ -from typing import Optional - -from fastapi import APIRouter, Body, Depends, Path, Query -from fastapi_pagination import Page -from fastapi_pagination.ext.sqlalchemy import paginate - -from src import crud, schemas -from src.dependencies import db -from src.exceptions import AuthenticationException -from src.security import JWTParams, require_auth - -router = APIRouter( - prefix="/apps/{app_id}/users/{user_id}/collections", - tags=["collections"], -) - - -@router.get( - "", - response_model=schemas.Collection, -) -async def get_collection( - app_id: str = Path(..., description="ID of the app"), - user_id: str = Path(..., description="ID of the user"), - collection_id: Optional[str] = Query( - None, description="Collection ID to retrieve. If not provided, uses JWT token" - ), - jwt_params: JWTParams = Depends(require_auth()), - db=db, -): - """ - Get a specific collection for a user. - - If collection_id is provided as a query parameter, it uses that (must match JWT collection_id). - Otherwise, it uses the collection_id from the JWT token. - """ - # Verify JWT has access to the requested resource - if not jwt_params.ad: - if jwt_params.ap is not None and jwt_params.ap != app_id: - raise AuthenticationException("Unauthorized access to resource") - if jwt_params.us is not None and jwt_params.us != user_id: - raise AuthenticationException("Unauthorized access to resource") - # If collection_id provided in query, check if it matches jwt or user is admin - if collection_id: - if ( - not jwt_params.ad - and jwt_params.co is not None - and jwt_params.co != collection_id - ): - raise AuthenticationException("Unauthorized access to resource") - target_collection_id = collection_id - else: - # Use collection_id from JWT - if not jwt_params.co: - raise AuthenticationException( - "Collection ID not found in query parameter or JWT" - ) - target_collection_id = jwt_params.co - - # Let crud function handle the ResourceNotFoundException - return await crud.get_collection_by_id( - db, app_id=app_id, collection_id=target_collection_id, user_id=user_id - ) - - -@router.post( - "/list", - response_model=Page[schemas.Collection], - dependencies=[Depends(require_auth(app_id="app_id", user_id="user_id"))], -) -async def get_collections( - app_id: str = Path(..., description="ID of the app"), - user_id: str = Path(..., description="ID of the user"), - options: Optional[schemas.CollectionGet] = Body( - None, description="Filtering options for the collections list" - ), - reverse: Optional[bool] = Query( - False, description="Whether to reverse the order of results" - ), - db=db, -): - """Get All Collections for a User""" - filter_param = None - if options and hasattr(options, "filter"): - filter_param = options.filter - if filter_param == {}: # Explicitly check for empty dict - filter_param = None - - return await paginate( - db, - await crud.get_collections( - app_id=app_id, user_id=user_id, filter=filter_param, reverse=reverse - ), - ) - - -@router.get( - "/name/{name}", - response_model=schemas.Collection, - dependencies=[Depends(require_auth(app_id="app_id", user_id="user_id"))], -) -async def get_collection_by_name( - app_id: str = Path(..., description="ID of the app"), - user_id: str = Path(..., description="ID of the user"), - name: str = Path(..., description="Name of the collection to retrieve"), - db=db, -) -> schemas.Collection: - """Get a Collection by Name""" - honcho_collection = await crud.get_collection_by_name( - db, app_id=app_id, user_id=user_id, name=name - ) - return honcho_collection - - -@router.post( - "", - response_model=schemas.Collection, - dependencies=[Depends(require_auth(app_id="app_id", user_id="user_id"))], -) -async def create_collection( - app_id: str = Path(..., description="ID of the app"), - user_id: str = Path(..., description="ID of the user"), - collection: schemas.CollectionCreate = Body( - ..., description="Collection creation parameters" - ), - db=db, -): - """Create a new Collection""" - # ValidationException will be caught by global handler if collection is invalid - # ConflictException will be caught by global handler if collection name already exists - return await crud.create_collection( - db, collection=collection, app_id=app_id, user_id=user_id - ) - - -@router.put( - "/{collection_id}", - response_model=schemas.Collection, - dependencies=[ - Depends( - require_auth( - app_id="app_id", user_id="user_id", collection_id="collection_id" - ) - ) - ], -) -async def update_collection( - app_id: str = Path(..., description="ID of the app"), - user_id: str = Path(..., description="ID of the user"), - collection_id: str = Path(..., description="ID of the collection to update"), - collection: schemas.CollectionUpdate = Body( - ..., description="Updated collection parameters" - ), - db=db, -): - "Update a Collection's name or metadata" - # ResourceNotFoundException will be caught by global handler if collection not found - # ValidationException will be caught by global handler if update data is invalid - honcho_collection = await crud.update_collection( - db, - collection=collection, - app_id=app_id, - user_id=user_id, - collection_id=collection_id, - ) - return honcho_collection - - -@router.delete( - "/{collection_id}", - dependencies=[ - Depends( - require_auth( - app_id="app_id", user_id="user_id", collection_id="collection_id" - ) - ) - ], -) -async def delete_collection( - app_id: str = Path(..., description="ID of the app"), - user_id: str = Path(..., description="ID of the user"), - collection_id: str = Path(..., description="ID of the collection to delete"), - db=db, -): - """Delete a Collection and its documents""" - # ResourceNotFoundException will be caught by global handler if collection not found - await crud.delete_collection( - db, app_id=app_id, user_id=user_id, collection_id=collection_id - ) - return {"message": "Collection deleted successfully"} diff --git a/src/routers/documents.py b/src/routers/documents.py deleted file mode 100644 index 127b9bca..00000000 --- a/src/routers/documents.py +++ /dev/null @@ -1,212 +0,0 @@ -import logging -from collections.abc import Sequence -from typing import Optional - -from fastapi import APIRouter, Body, Depends, Path, Query -from fastapi_pagination import Page -from fastapi_pagination.ext.sqlalchemy import paginate - -from src import crud, schemas -from src.dependencies import db -from src.exceptions import ResourceNotFoundException, ValidationException -from src.security import require_auth - -logger = logging.getLogger(__name__) - -router = APIRouter( - prefix="/apps/{app_id}/users/{user_id}/collections/{collection_id}/documents", - tags=["documents"], - dependencies=[ - Depends( - require_auth( - app_id="app_id", user_id="user_id", collection_id="collection_id" - ) - ) - ], -) - - -@router.post("/list", response_model=Page[schemas.Document]) -async def get_documents( - app_id: str = Path(..., description="ID of the app"), - user_id: str = Path(..., description="ID of the user"), - collection_id: str = Path(..., description="ID of the collection"), - options: Optional[schemas.DocumentGet] = Body( - None, description="Filtering options for the documents list" - ), - reverse: Optional[bool] = Query( - False, description="Whether to reverse the order of results" - ), - db=db, -): - """Get all of the Documents in a Collection""" - filter_param = None - if options and hasattr(options, "filter"): - filter_param = options.filter - if filter_param == {}: # Explicitly check for empty dict - filter_param = None - - try: - documents_query = await crud.get_documents( - app_id=app_id, - user_id=user_id, - collection_id=collection_id, - filter=filter_param, - reverse=reverse, - ) - - return await paginate(db, documents_query) - except ValueError as e: - logger.warning( - f"Failed to get documents for collection {collection_id}: {str(e)}" - ) - raise ResourceNotFoundException( - "Collection not found or does not belong to user" - ) from e - - -@router.get("/{document_id}", response_model=schemas.Document) -async def get_document( - app_id: str = Path(..., description="ID of the app"), - user_id: str = Path(..., description="ID of the user"), - collection_id: str = Path(..., description="ID of the collection"), - document_id: str = Path(..., description="ID of the document to retrieve"), - db=db, -): - """Get a document by ID""" - honcho_document = await crud.get_document( - db, - app_id=app_id, - user_id=user_id, - collection_id=collection_id, - document_id=document_id, - ) - return honcho_document - - -@router.post("/query", response_model=Sequence[schemas.Document]) -async def query_documents( - app_id: str = Path(..., description="ID of the app"), - user_id: str = Path(..., description="ID of the user"), - collection_id: str = Path(..., description="ID of the collection"), - options: schemas.DocumentQuery = Body( - ..., description="Query parameters for document search" - ), - db=db, -): - """Cosine Similarity Search for Documents""" - - try: - top_k = options.top_k - filter = options.filter - if options.filter == {}: - filter = None - - documents = await crud.query_documents( - db=db, - app_id=app_id, - user_id=user_id, - collection_id=collection_id, - query=options.query, - filter=filter, - top_k=top_k, - ) - - logger.info(f"Query documents successful for collection {collection_id}") - return documents - except ValueError as e: - logger.error( - f"Error querying documents in collection {collection_id}: {str(e)}" - ) - raise ValidationException("Error querying documents") from e - - -@router.post("", response_model=schemas.Document) -async def create_document( - app_id: str = Path(..., description="ID of the app"), - user_id: str = Path(..., description="ID of the user"), - collection_id: str = Path(..., description="ID of the collection"), - document: schemas.DocumentCreate = Body( - ..., description="Document creation parameters" - ), - db=db, -): - """Embed text as a vector and create a Document""" - try: - document_obj = await crud.create_document( - db, - document=document, - app_id=app_id, - user_id=user_id, - collection_id=collection_id, - ) - logger.info(f"Document created successfully in collection {collection_id}") - return document_obj - except ValueError as e: - logger.warning( - f"Failed to create document in collection {collection_id}: {str(e)}" - ) - raise ResourceNotFoundException( - "Collection not found or does not belong to user" - ) from e - - -@router.put( - "/{document_id}", - response_model=schemas.Document, -) -async def update_document( - app_id: str = Path(..., description="ID of the app"), - user_id: str = Path(..., description="ID of the user"), - collection_id: str = Path(..., description="ID of the collection"), - document_id: str = Path(..., description="ID of the document to update"), - document: schemas.DocumentUpdate = Body( - ..., description="Updated document parameters" - ), - db=db, -): - """Update the content and/or the metadata of a Document""" - if document.content is None and document.metadata is None: - logger.warning( - f"Document update attempted with empty content and metadata for document {document_id}" - ) - raise ValidationException("Content and metadata cannot both be None") - - try: - updated_document = await crud.update_document( - db, - document=document, - app_id=app_id, - user_id=user_id, - collection_id=collection_id, - document_id=document_id, - ) - logger.info(f"Document {document_id} updated successfully") - return updated_document - except ValueError as e: - logger.warning(f"Failed to update document {document_id}: {str(e)}") - raise ResourceNotFoundException("Collection or document not found") from e - - -@router.delete("/{document_id}") -async def delete_document( - app_id: str = Path(..., description="ID of the app"), - user_id: str = Path(..., description="ID of the user"), - collection_id: str = Path(..., description="ID of the collection"), - document_id: str = Path(..., description="ID of the document to delete"), - db=db, -): - """Delete a Document by ID""" - response = await crud.delete_document( - db, - app_id=app_id, - user_id=user_id, - collection_id=collection_id, - document_id=document_id, - ) - if response: - logger.info(f"Document {document_id} deleted successfully") - return {"message": "Document deleted successfully"} - else: - logger.warning(f"Document {document_id} not found or could not be deleted") - raise ResourceNotFoundException("Document not found or does not belong to user") diff --git a/src/routers/keys.py b/src/routers/keys.py index c40b0ae7..70435beb 100644 --- a/src/routers/keys.py +++ b/src/routers/keys.py @@ -24,14 +24,13 @@ router = APIRouter( @router.post("") async def create_key( - app_id: str | None = Query(None, description="ID of the app to scope the key to"), - user_id: str | None = Query(None, description="ID of the user to scope the key to"), + workspace_id: str | None = Query( + None, description="ID of the workspace to scope the key to" + ), + peer_id: str | None = Query(None, description="ID of the peer to scope the key to"), session_id: str | None = Query( None, description="ID of the session to scope the key to" ), - collection_id: str | None = Query( - None, description="ID of the collection to scope the key to" - ), expires_at: datetime.datetime | None = None, ): """Create a new Key""" @@ -39,18 +38,17 @@ async def create_key( raise DisabledException() # Validate that at least one parameter is provided for proper scoping - if not any([app_id, user_id, session_id, collection_id]): + if not any([workspace_id, peer_id, session_id]): raise ValidationException( - "At least one of app_id, user_id, session_id, or collection_id must be provided" + "At least one of workspace_id, peer_id, or session_id must be provided" ) key_str = create_jwt( JWTParams( exp=expires_at.isoformat() if expires_at else None, - ap=app_id, - us=user_id, - se=session_id, - co=collection_id, + w=workspace_id, + p=peer_id, + s=session_id, ) ) return { diff --git a/src/routers/messages.py b/src/routers/messages.py index 5ad4d939..54506d92 100644 --- a/src/routers/messages.py +++ b/src/routers/messages.py @@ -1,6 +1,6 @@ import logging import os -from typing import Optional +from typing import Any, Optional from fastapi import APIRouter, BackgroundTasks, Body, Depends, Path, Query from fastapi_pagination import Page @@ -16,17 +16,44 @@ from src.security import require_auth logger = logging.getLogger(__name__) router = APIRouter( - prefix="/apps/{app_id}/users/{user_id}/sessions/{session_id}/messages", + prefix="/workspaces/{workspace_id}/sessions/{session_id}/messages", tags=["messages"], dependencies=[ - Depends( - require_auth(app_id="app_id", user_id="user_id", session_id="session_id") - ) + Depends(require_auth(workspace_name="workspace_id", session_name="session_id")) ], ) -async def enqueue(payload: dict | list[dict]): +def create_processed_payload( + message: dict, + sender_name: Optional[str], + target_name: Optional[str], + task_type: str, +) -> dict: + """ + Create a processed payload from a message for queue processing. + + Args: + message: The original message dictionary + sender_name: Name of the message sender + target_name: Name of the target peer + task_type: Type of task ('representation' or 'summary') + + Returns: + Processed payload dictionary ready for queue processing + """ + processed_payload = { + k: str(v) if isinstance(v, str) else v for k, v in message.items() + } + # Remove peer_name from payload + processed_payload.pop("peer_name", None) # Use None as default to avoid KeyError + processed_payload["sender_name"] = sender_name + processed_payload["target_name"] = target_name + processed_payload["task_type"] = task_type + return processed_payload + + +async def enqueue(payload: list[dict]): """ Add message(s) to the deriver queue for processing. @@ -37,189 +64,226 @@ async def enqueue(payload: dict | list[dict]): # Use the get_db dependency to ensure proper transaction handling async with tracked_db("message_enqueue") as db_session: try: - if isinstance(payload, list): - if not payload: # Empty list check - logger.debug("Empty payload list, skipping enqueue") - return + # Determine if batch or single processing + if not payload: # Empty list check + logger.debug("Empty payload list, skipping enqueue") + return + logger.debug(f"Enqueueing batch of {len(payload)} messages") + workspace_name = payload[0]["workspace_name"] + session_name = payload[0]["session_name"] - logger.debug(f"Enqueueing batch of {len(payload)} messages") + # Case 1: session_name is None — only create representation for peer + if session_name is None: + peer_name = payload[0]["peer_name"] + logger.info( + "Session name is None, creating single representation queue items" + ) + peer = await crud.get_or_create_peers( + db_session, + workspace_name=workspace_name, + peers=[schemas.PeerCreate(name=peer_name)], + ) + peer = peer[0] - # Check session once since all messages are for same session - try: - session = await crud.get_session( - db_session, - app_id=payload[0]["app_id"], - user_id=payload[0]["user_id"], - session_id=payload[0]["session_id"], - ) - if not session: - logger.warning( - f"Session {payload[0]['session_id']} not found, skipping enqueue" - ) - return - except ResourceNotFoundException: - logger.warning( - f"Session {payload[0]['session_id']} not found, skipping enqueue" - ) - return - - logger.debug(f"Session {session.public_id} found for batch enqueue") - - # Check if deriver is disabled for this session - if ( - session.h_metadata.get("deriver_disabled") is not None - and session.h_metadata.get("deriver_disabled") is not False - ): + # Cast configuration to PeerConfig and check observe_me + peer_config = ( + schemas.PeerConfig(**peer.configuration) + if peer.configuration + else schemas.PeerConfig() + ) + if not peer_config.observe_me: logger.info( - f"Deriver is disabled for session {session.public_id}, skipping enqueue" + f"Peer {peer_name} has observe_me=False, skipping enqueue" ) return - # Process all payloads - queue_records = [ + queue_records: list[dict[str, Any]] = [] + + for message in payload: + processed_payload = create_processed_payload( + message=message, + sender_name=message["peer_name"], + target_name=message["peer_name"], + task_type="representation", + ) + queue_records.append( + { + "payload": processed_payload, + "session_id": None, + } + ) + + logger.debug( + f"Inserting {len(queue_records)} queue records for None session" + ) + stmt = insert(QueueItem).returning(QueueItem) + await db_session.execute(stmt, queue_records) + await db_session.commit() + logger.info( + f"Successfully enqueued {len(queue_records)} messages with None session" + ) + return + + # Case 2: Normal session processing + session = await crud.get_or_create_session( + db_session, + session=schemas.SessionCreate(name=session_name), + workspace_name=workspace_name, + ) + + # Check if deriver is disabled for this session + deriver_disabled = ( + session.configuration.get("deriver_disabled") is not None + and session.configuration.get("deriver_disabled") is not False + ) + + configuration_query = await crud.get_session_peer_configuration( + workspace_name=workspace_name, session_name=session_name + ) + peers_with_configuration_result = await db_session.execute( + configuration_query + ) + peers_with_configuration_list = peers_with_configuration_result.all() + peers_with_configuration = { + row.peer_name: [row.peer_configuration, row.session_peer_configuration] + for row in peers_with_configuration_list + } + + # Process all payloads - create multiple queue items per message + queue_records = [] + + for message in payload: + if deriver_disabled: + # still create a summary queue item for the session + processed_payload = create_processed_payload( + message=message, + sender_name=None, + target_name=None, + task_type="summary", + ) + queue_records.append( + { + "payload": processed_payload, + "session_id": session.id, + } + ) + continue + + sender_name = message["peer_name"] + + sender_session_peer_config = ( + schemas.SessionPeerConfig( + **peers_with_configuration[sender_name][1] + ) + if peers_with_configuration[sender_name][1] + else None + ) + sender_peer_config = ( + schemas.PeerConfig(**peers_with_configuration[sender_name][0]) + if peers_with_configuration[sender_name][0] + else schemas.PeerConfig() + ) + + observe_me = ( + sender_session_peer_config.observe_me + if sender_session_peer_config + else sender_peer_config.observe_me + ) + if not observe_me: + continue + + # Handle working representation for sender + processed_payload = create_processed_payload( + message=message, + sender_name=sender_name, + target_name=sender_name, + task_type="representation", + ) + + queue_records.append( { - "payload": { - k: str(v) if isinstance(v, str) else v for k, v in p.items() - }, + "payload": processed_payload, "session_id": session.id, } - for p in payload - ] + ) + for peer_name, configuration in peers_with_configuration.items(): + session_peer_config = ( + schemas.SessionPeerConfig(**configuration[1]) + if configuration[1] + else None + ) - logger.debug(f"Inserting {len(queue_records)} queue records") + if peer_name != sender_name: + # Handle local representation for other peers + if ( + session_peer_config is None + or not session_peer_config.observe_others + ): + continue + else: + # Create local representation for peer + processed_payload = create_processed_payload( + message=message, + sender_name=sender_name, + target_name=peer_name, + task_type="representation", + ) + queue_records.append( + { + "payload": processed_payload, + "session_id": session.id, + } + ) + + logger.debug(f"Inserting {len(queue_records)} queue records") + + if len(queue_records) > 0: # Use insert to maintain order stmt = insert(QueueItem).returning(QueueItem) await db_session.execute(stmt, queue_records) await db_session.commit() - logger.info(f"Successfully enqueued batch of {len(payload)} messages") - return - else: - # Single message enqueue - logger.debug( - f"Enqueueing single message for session {payload['session_id']}" - ) - try: - session = await crud.get_session( - db_session, - app_id=payload["app_id"], - user_id=payload["user_id"], - session_id=payload["session_id"], - ) - if not session: - logger.warning( - f"Session {payload['session_id']} not found, skipping enqueue" - ) - return - except ResourceNotFoundException: - logger.warning( - f"Session {payload['session_id']} not found, skipping enqueue" - ) - return - - # Check if deriver is disabled for this session - deriver_disabled = session.h_metadata.get("deriver_disabled") - if deriver_disabled is not None and deriver_disabled is not False: - logger.info( - f"Deriver is disabled for session {payload['session_id']}, skipping enqueue" - ) - return - - processed_payload = { - k: str(v) if isinstance(v, str) else v for k, v in payload.items() - } - # Use insert for consistency - stmt = ( - insert(QueueItem) - .values(payload=processed_payload, session_id=session.id) - .returning(QueueItem) - ) - await db_session.execute(stmt) - await db_session.commit() logger.info( - f"Successfully enqueued message for session {payload['session_id']}" + f"Successfully enqueued {len(payload)} messages with {len(queue_records)} total queue items" ) - return + except Exception as e: - logger.error(f"Failed to enqueue message: {str(e)}", exc_info=True) + logger.error(f"Failed to enqueue messages: {str(e)}", exc_info=True) if os.getenv("SENTRY_ENABLED", "False").lower() == "true": import sentry_sdk sentry_sdk.capture_exception(e) -@router.post("", response_model=schemas.Message) -async def create_message_for_session( +@router.post("/", response_model=list[schemas.Message]) +async def create_messages_for_session( background_tasks: BackgroundTasks, - app_id: str = Path(..., description="ID of the app"), - user_id: str = Path(..., description="ID of the user"), + workspace_id: str = Path(..., description="ID of the workspace"), session_id: str = Path(..., description="ID of the session"), - message: schemas.MessageCreate = Body( - ..., description="Message creation parameters" - ), - db=db, -): - """Adds a message to a session""" - try: - honcho_message = await crud.create_message( - db, message=message, app_id=app_id, user_id=user_id, session_id=session_id - ) - - # Prepare message payload for background processing - payload = { - "app_id": app_id, - "user_id": user_id, - "session_id": session_id, - "message_id": honcho_message.public_id, - "is_user": honcho_message.is_user, - "content": honcho_message.content, - "metadata": honcho_message.h_metadata, - } - - # Queue message for background processing - background_tasks.add_task(enqueue, payload) # type: ignore - logger.info( - f"Message {honcho_message.public_id} created and queued for processing" - ) - - return honcho_message - except ValueError as e: - logger.error(f"Failed to create message for session {session_id}: {str(e)}") - raise ResourceNotFoundException("Session not found") from e - - -@router.post("/batch", response_model=list[schemas.Message]) -async def create_batch_messages_for_session( - background_tasks: BackgroundTasks, - app_id: str = Path(..., description="ID of the app"), - user_id: str = Path(..., description="ID of the user"), - session_id: str = Path(..., description="ID of the session"), - batch: schemas.MessageBatchCreate = Body( + messages: schemas.MessageBatchCreate = Body( ..., description="Batch of messages to create" ), db=db, ): + workspace_name, session_name = workspace_id, session_id """Bulk create messages for a session while maintaining order. Maximum 100 messages per batch.""" try: created_messages = await crud.create_messages( db, - messages=batch.messages, - app_id=app_id, - user_id=user_id, - session_id=session_id, + messages=messages.messages, + workspace_name=workspace_name, + session_name=session_name, ) # Create payloads for all messages payloads = [ { - "app_id": app_id, - "user_id": user_id, - "session_id": session_id, - "message_id": message.public_id, - "is_user": message.is_user, + "workspace_name": workspace_name, + "session_name": session_name, + "message_id": message.id, "content": message.content, - "metadata": message.h_metadata, + "peer_name": message.peer_name, } for message in created_messages ] @@ -240,8 +304,7 @@ async def create_batch_messages_for_session( @router.post("/list", response_model=Page[schemas.Message]) async def get_messages( - app_id: str = Path(..., description="ID of the app"), - user_id: str = Path(..., description="ID of the user"), + workspace_id: str = Path(..., description="ID of the workspace"), session_id: str = Path(..., description="ID of the session"), options: Optional[schemas.MessageGet] = Body( None, description="Filtering options for the messages list" @@ -260,9 +323,8 @@ async def get_messages( filter = None messages_query = await crud.get_messages( - app_id=app_id, - user_id=user_id, - session_id=session_id, + workspace_name=workspace_id, + session_name=session_id, filter=filter, reverse=reverse, ) @@ -275,15 +337,14 @@ async def get_messages( @router.get("/{message_id}", response_model=schemas.Message) async def get_message( - app_id: str = Path(..., description="ID of the app"), - user_id: str = Path(..., description="ID of the user"), + workspace_id: str = Path(..., description="ID of the workspace"), session_id: str = Path(..., description="ID of the session"), message_id: str = Path(..., description="ID of the message to retrieve"), db=db, ): """Get a Message by ID""" honcho_message = await crud.get_message( - db, app_id=app_id, session_id=session_id, user_id=user_id, message_id=message_id + db, workspace_name=workspace_id, session_name=session_id, message_id=message_id ) if honcho_message is None: logger.warning(f"Message {message_id} not found in session {session_id}") @@ -293,8 +354,7 @@ async def get_message( @router.put("/{message_id}", response_model=schemas.Message) async def update_message( - app_id: str = Path(..., description="ID of the app"), - user_id: str = Path(..., description="ID of the user"), + workspace_id: str = Path(..., description="ID of the workspace"), session_id: str = Path(..., description="ID of the session"), message_id: str = Path(..., description="ID of the message to update"), message: schemas.MessageUpdate = Body( @@ -307,9 +367,8 @@ async def update_message( updated_message = await crud.update_message( db, message=message, - app_id=app_id, - user_id=user_id, - session_id=session_id, + workspace_name=workspace_id, + session_name=session_id, message_id=message_id, ) logger.info(f"Message {message_id} updated successfully") diff --git a/src/routers/metamessages.py b/src/routers/metamessages.py deleted file mode 100644 index dc882ddc..00000000 --- a/src/routers/metamessages.py +++ /dev/null @@ -1,154 +0,0 @@ -import logging -from typing import Optional - -from fastapi import APIRouter, Body, Depends, Path, Query -from fastapi_pagination import Page -from fastapi_pagination.ext.sqlalchemy import paginate - -from src import crud, schemas -from src.dependencies import db -from src.exceptions import ResourceNotFoundException, ValidationException -from src.security import require_auth - -logger = logging.getLogger(__name__) - -router = APIRouter( - prefix="/apps/{app_id}/users/{user_id}/metamessages", - tags=["metamessages"], - dependencies=[Depends(require_auth(app_id="app_id", user_id="user_id"))], -) - - -@router.post("", response_model=schemas.Metamessage) -async def create_metamessage( - app_id: str = Path(..., description="ID of the app"), - user_id: str = Path(..., description="ID of the user"), - metamessage: schemas.MetamessageCreate = Body( - ..., description="Metamessage creation parameters" - ), - db=db, -): - """ - Create a new metamessage associated with a user. - Optionally link to a session and message by providing those IDs in the request body. - """ - try: - metamessage_obj = await crud.create_metamessage( - db, - user_id=user_id, - metamessage=metamessage, - app_id=app_id, - ) - logger.info(f"Metamessage created successfully for user {user_id}") - return metamessage_obj - except (ResourceNotFoundException, ValidationException) as e: - logger.warning(f"Failed to create metamessage: {str(e)}") - raise - - -@router.post("/list", response_model=Page[schemas.Metamessage]) -async def get_metamessages( - app_id: str = Path(..., description="ID of the app"), - user_id: str = Path(..., description="ID of the user"), - options: Optional[schemas.MetamessageGet] = Body( - None, description="Filtering options for the metamessages list" - ), - reverse: Optional[bool] = Query( - False, description="Whether to reverse the order of results" - ), - db=db, -): - """ - Get metamessages with flexible filtering. - - - Filter by user only: No additional parameters needed - - Filter by session: Provide session_id - - Filter by message: Provide message_id (and session_id) - - Filter by type: Provide label - - Filter by metadata: Provide filter object - """ - session_id_param = None - message_id_param = None - label_param = None - filter_param = None - - if options: - if hasattr(options, "session_id") and options.session_id: - session_id_param = options.session_id - if hasattr(options, "message_id") and options.message_id: - message_id_param = options.message_id - if hasattr(options, "label") and options.label: - label_param = options.label - if hasattr(options, "filter") and options.filter: - filter_param = options.filter - if filter_param == {}: # Explicitly check for empty dict - filter_param = None - - try: - metamessages_query = await crud.get_metamessages( - app_id=app_id, - user_id=user_id, - session_id=session_id_param, - message_id=message_id_param, - label=label_param, - filter=filter_param, - reverse=reverse, - ) - return await paginate(db, metamessages_query) - except (ResourceNotFoundException, ValidationException) as e: - logger.warning(f"Failed to get metamessages: {str(e)}") - raise - - -@router.get( - "/{metamessage_id}", - response_model=schemas.Metamessage, -) -async def get_metamessage( - app_id: str = Path(..., description="ID of the app"), - user_id: str = Path(..., description="ID of the user"), - metamessage_id: str = Path(..., description="ID of the metamessage to retrieve"), - db=db, -): - """Get a specific Metamessage by ID""" - honcho_metamessage = await crud.get_metamessage( - db, - app_id=app_id, - user_id=user_id, - metamessage_id=metamessage_id, - ) - if honcho_metamessage is None: - logger.warning(f"Metamessage {metamessage_id} not found") - raise ResourceNotFoundException( - f"Metamessage with ID {metamessage_id} not found" - ) - return honcho_metamessage - - -@router.put( - "/{metamessage_id}", - response_model=schemas.Metamessage, -) -async def update_metamessage( - app_id: str = Path(..., description="ID of the app"), - user_id: str = Path(..., description="ID of the user"), - metamessage_id: str = Path(..., description="ID of the metamessage to update"), - metamessage: schemas.MetamessageUpdate = Body( - ..., description="Updated metamessage parameters" - ), - db=db, -): - """Update a metamessage's metadata, type, or relationships""" - try: - updated_metamessage = await crud.update_metamessage( - db, - metamessage=metamessage, - app_id=app_id, - user_id=user_id, - metamessage_id=metamessage_id, - ) - logger.info(f"Metamessage {metamessage_id} updated successfully") - return updated_metamessage - except (ResourceNotFoundException, ValidationException) as e: - logger.warning(f"Failed to update metamessage {metamessage_id}: {str(e)}") - raise diff --git a/src/routers/peers.py b/src/routers/peers.py new file mode 100644 index 00000000..ec89cfb9 --- /dev/null +++ b/src/routers/peers.py @@ -0,0 +1,315 @@ +import logging +from typing import Optional + +from fastapi import ( + APIRouter, + BackgroundTasks, + Body, + Depends, + Path, + Query, +) +from fastapi_pagination import Page +from fastapi_pagination.ext.sqlalchemy import paginate + +from src import agent, crud, schemas +from src.dependencies import db +from src.exceptions import ( + AuthenticationException, + ResourceNotFoundException, +) +from src.routers.messages import enqueue +from src.security import JWTParams, require_auth + +logger = logging.getLogger(__name__) + +router = APIRouter( + prefix="/workspaces/{workspace_id}/peers", + tags=["peers"], +) + + +@router.post( + "/list", + response_model=Page[schemas.Peer], + dependencies=[Depends(require_auth(workspace_name="workspace_id"))], +) +async def get_peers( + workspace_id: str = Path(..., description="ID of the workspace"), + options: Optional[schemas.PeerGet] = Body( + None, description="Filtering options for the peers list" + ), + db=db, +): + """Get All Peers for a Workspace""" + filter_param = None + if options and hasattr(options, "filter"): + filter_param = options.filter + if filter_param == {}: + filter_param = None + + return await paginate( + db, + await crud.get_peers(workspace_name=workspace_id, filter=filter_param), + ) + + +@router.post( + "", + response_model=schemas.Peer, +) +async def get_or_create_peer( + workspace_id: str = Path(..., description="ID of the workspace"), + peer: schemas.PeerCreate = Body(..., description="Peer creation parameters"), + jwt_params: JWTParams = Depends(require_auth()), + db=db, +): + """ + Get a Peer by ID + + If peer_id is provided as a query parameter, it uses that (must match JWT workspace_id). + Otherwise, it uses the peer_id from the JWT token. + """ + # validate workspace query param + if not jwt_params.ad and jwt_params.w is not None and jwt_params.w != workspace_id: + raise AuthenticationException("Unauthorized access to resource") + + if peer.name: + if not jwt_params.ad and jwt_params.p is not None and jwt_params.p != peer.name: + raise AuthenticationException("Unauthorized access to resource") + else: + # Use peer_id from JWT + if not jwt_params.p: + raise AuthenticationException("Peer ID not found in query parameter or JWT") + peer.name = jwt_params.p + peer = ( + await crud.get_or_create_peers(db, workspace_name=workspace_id, peers=[peer]) + )[0] + return peer + + +@router.put( + "/{peer_id}", + response_model=schemas.Peer, + dependencies=[ + Depends(require_auth(workspace_name="workspace_id", peer_name="peer_id")) + ], +) +async def update_peer( + workspace_id: str = Path(..., description="ID of the workspace"), + peer_id: str = Path(..., description="ID of the peer to update"), + peer: schemas.PeerUpdate = Body(..., description="Updated peer parameters"), + db=db, +): + """Update a Peer's name and/or metadata""" + updated_peer = await crud.update_peer( + db, workspace_name=workspace_id, peer_name=peer_id, peer=peer + ) + return updated_peer + + +@router.post( + "/{peer_id}/sessions", + response_model=Page[schemas.Session], + dependencies=[ + Depends(require_auth(workspace_name="workspace_id", peer_name="peer_id")) + ], +) +async def get_sessions_for_peer( + workspace_id: str = Path(..., description="ID of the workspace"), + peer_id: str = Path(..., description="ID of the peer"), + options: Optional[schemas.SessionGet] = Body( + None, description="Filtering options for the sessions list" + ), + db=db, +): + """Get All Sessions for a Peer""" + filter_param = None + is_active = True + + if options: + if hasattr(options, "filter"): + filter_param = options.filter + if filter_param == {}: + filter_param = None + if hasattr(options, "is_active"): + is_active = options.is_active + + return await paginate( + db, + await crud.get_sessions_for_peer( + workspace_name=workspace_id, + peer_name=peer_id, + is_active=is_active, + filter=filter_param, + ), + ) + + +@router.post( + "/{peer_id}/chat", + response_model=schemas.DialecticResponse, + responses={ + 200: { + "description": "Response to a question informed by Honcho's User Representation", + "content": {"text/event-stream": {}}, + }, + }, + dependencies=[ + Depends(require_auth(workspace_name="workspace_id", peer_name="peer_id")) + ], +) +async def chat( + workspace_id: str = Path(..., description="ID of the workspace"), + peer_id: str = Path(..., description="ID of the peer"), + options: schemas.DialecticOptions = Body( + ..., description="Dialectic Endpoint Parameters" + ), + db=db, +): + # Get or create the peer to ensure it exists + await crud.get_or_create_peers( + db, workspace_name=workspace_id, peers=[schemas.PeerCreate(name=peer_id)] + ) + + response = await agent.chat( + workspace_id, peer_id, options.session_id, options.queries, options.stream + ) + return response + + +@router.post( + "/{peer_id}/messages", + response_model=list[schemas.Message], + dependencies=[ + Depends(require_auth(workspace_name="workspace_id", peer_name="peer_id")) + ], +) +async def create_messages_for_peer( + background_tasks: BackgroundTasks, + workspace_id: str = Path(..., description="ID of the workspace"), + peer_id: str = Path(..., description="ID of the peer"), + messages: schemas.MessageBatchCreate = Body( + ..., description="Batch of messages to create" + ), + db=db, +): + """Create messages for a peer""" + workspace_name, peer_name = workspace_id, peer_id + """Bulk create messages for a peer while maintaining order.""" + try: + created_messages = await crud.create_messages_for_peer( + db, + messages=messages.messages, + workspace_name=workspace_name, + peer_name=peer_name, + ) + + # Create payloads for all messages + payloads = [ + { + "workspace_name": workspace_name, + "session_name": None, + "message_id": message.id, + "content": message.content, + "peer_name": message.peer_name, + } + for message in created_messages + ] + + # Enqueue all messages in one call + background_tasks.add_task(enqueue, payloads) # type: ignore + logger.info( + f"Batch of {len(created_messages)} messages created and queued for processing" + ) + + return created_messages + except ValueError as e: + logger.error(f"Failed to create batch messages for peer {peer_id}: {str(e)}") + raise ResourceNotFoundException("Peer not found") from e + + +@router.post( + "/{peer_id}/messages/list", + response_model=Page[schemas.Message], + dependencies=[ + Depends(require_auth(workspace_name="workspace_id", peer_name="peer_id")) + ], +) +async def get_messages_for_peer( + workspace_id: str = Path(..., description="ID of the workspace"), + peer_id: str = Path(..., description="ID of the peer"), + options: Optional[schemas.MessageGet] = Body( + None, description="Filtering options for the messages list" + ), + reverse: Optional[bool] = Query( + False, description="Whether to reverse the order of results" + ), + db=db, +): + """Get all messages for a peer""" + try: + filter = None + if options and hasattr(options, "filter"): + filter = options.filter + if filter == {}: + filter = None + + messages_query = await crud.get_messages_for_peer( + workspace_name=workspace_id, + peer_name=peer_id, + filter=filter, + reverse=reverse, + ) + + return await paginate(db, messages_query) + except ValueError as e: + logger.warning(f"Failed to get messages for peer {peer_id}: {str(e)}") + raise ResourceNotFoundException("Peer not found") from e + + +@router.post( + "/{peer_id}/representation", + response_model=dict[str, object], + dependencies=[ + Depends(require_auth(workspace_name="workspace_id", peer_name="peer_id")) + ], +) +async def get_working_representation( + workspace_id: str = Path(..., description="ID of the workspace"), + peer_id: str = Path(..., description="ID of the peer"), + options: schemas.PeerRepresentationGet = Body( + ..., description="Options for getting the peer representation" + ), + db=db, +): + """Get a peer's working representation for a session. + + If a session_id is provided in the body, we get the working representation of the peer in that session. + + In the current implementation, we don't offer representations of `target` so that parameter is ignored. + Future releases will allow for this. + """ + representation = await crud.get_working_representation( + db, workspace_id, peer_id, options.session_id + ) + return {"representation": representation} + + +@router.post( + "/{peer_id}/search", + response_model=Page[schemas.Message], + dependencies=[ + Depends(require_auth(workspace_name="workspace_id", peer_name="peer_id")) + ], +) +async def search_peer( + workspace_id: str = Path(..., description="ID of the workspace"), + peer_id: str = Path(..., description="ID of the peer"), + query: str = Body(..., description="Search query"), + db=db, +): + """Search a Peer""" + stmt = await crud.search(query, workspace_name=workspace_id, peer_name=peer_id) + + return await paginate(db, stmt) diff --git a/src/routers/sessions.py b/src/routers/sessions.py index b7108251..6fb47a8b 100644 --- a/src/routers/sessions.py +++ b/src/routers/sessions.py @@ -1,14 +1,11 @@ import logging from typing import Optional -from anthropic import AsyncMessageStreamManager -from fastapi import APIRouter, Body, Depends, Path, Query -from fastapi.exceptions import HTTPException -from fastapi.responses import StreamingResponse +from fastapi import APIRouter, Body, Depends, Path, Query, Response from fastapi_pagination import Page from fastapi_pagination.ext.sqlalchemy import paginate -from src import agent, crud, schemas +from src import crud, schemas from src.dependencies import db from src.exceptions import ( AuthenticationException, @@ -16,140 +13,106 @@ from src.exceptions import ( ValidationException, ) from src.security import JWTParams, require_auth +from src.utils import history logger = logging.getLogger(__name__) router = APIRouter( - prefix="/apps/{app_id}/users/{user_id}/sessions", + prefix="/workspaces/{workspace_id}/sessions", tags=["sessions"], ) -@router.get( +@router.post( "", response_model=schemas.Session, ) -async def get_session( - app_id: str = Path(..., description="ID of the app"), - user_id: str = Path(..., description="ID of the user"), - session_id: Optional[str] = Query( - None, description="Session ID to retrieve. If not provided, uses JWT token" +async def get_or_create_session( + workspace_id: str = Path(..., description="ID of the workspace"), + session: schemas.SessionCreate = Body( + ..., description="Session creation parameters" ), jwt_params: JWTParams = Depends(require_auth()), db=db, ): """ - Get a specific session for a user. + Get a specific session in a workspace. - If session_id is provided as a query parameter, it uses that (must match JWT session_id). - Otherwise, it uses the session_id from the JWT token. + If session_id is provided as a query parameter, it verifies the session is in the workspace. + Otherwise, it uses the session_id from the JWT token for verification. """ # Verify JWT has access to the requested resource - if not jwt_params.ad: - if jwt_params.ap is not None and jwt_params.ap != app_id: - raise AuthenticationException("Unauthorized access to resource") - if jwt_params.us is not None and jwt_params.us != user_id: - raise AuthenticationException("Unauthorized access to resource") - # If session_id provided in query, check if it matches jwt or user is admin - if session_id: + if not jwt_params.ad and jwt_params.w is not None and jwt_params.w != workspace_id: + raise AuthenticationException("Unauthorized access to resource") + + # Use session from JWT if not provided in query + if session.name: if ( not jwt_params.ad - and jwt_params.se is not None - and jwt_params.se != session_id + and jwt_params.s is not None + and jwt_params.s != session.name ): raise AuthenticationException("Unauthorized access to resource") - target_session_id = session_id else: - # Use session_id from JWT - if not jwt_params.se: + if not jwt_params.s: raise AuthenticationException( "Session ID not found in query parameter or JWT" ) - target_session_id = jwt_params.se + session.name = jwt_params.s - # Let crud function handle the ResourceNotFoundException - return await crud.get_session( - db, app_id=app_id, session_id=target_session_id, user_id=user_id - ) + # Handle session creation with proper error handling + try: + return await crud.get_or_create_session( + db, workspace_name=workspace_id, session=session + ) + except ValueError as e: + logger.warning(f"Failed to get or create session {session.name}: {str(e)}") + raise ValidationException(str(e)) from e @router.post( "/list", response_model=Page[schemas.Session], - dependencies=[Depends(require_auth(app_id="app_id", user_id="user_id"))], + dependencies=[Depends(require_auth(workspace_name="workspace_id"))], ) async def get_sessions( - app_id: str = Path(..., description="ID of the app"), - user_id: str = Path(..., description="ID of the user"), + workspace_id: str = Path(..., description="ID of the workspace"), options: Optional[schemas.SessionGet] = Body( None, description="Filtering and pagination options for the sessions list" ), - reverse: Optional[bool] = Query( - False, description="Whether to reverse the order of results" - ), db=db, ): - """Get All Sessions for a User""" + """Get All Sessions in a Workspace""" filter_param = None - is_active_param = False # Default to None, meaning no filter on is_active + is_active_param = False # Default from schema if options: - if hasattr(options, 'filter') and options.filter: + if hasattr(options, "filter") and options.filter: filter_param = options.filter - if filter_param == {}: # Explicitly check for empty dict + if filter_param == {}: # Explicitly check for empty dict filter_param = None - if hasattr(options, 'is_active'): # Check if is_active is present + if hasattr(options, "is_active"): # Check if is_active is present is_active_param = options.is_active return await paginate( db, await crud.get_sessions( - app_id=app_id, - user_id=user_id, - reverse=reverse, + workspace_name=workspace_id, is_active=is_active_param, filter=filter_param, ), ) -@router.post( - "", - response_model=schemas.Session, - dependencies=[Depends(require_auth(app_id="app_id", user_id="user_id"))], -) -async def create_session( - app_id: str = Path(..., description="ID of the app"), - user_id: str = Path(..., description="ID of the user"), - session: schemas.SessionCreate = Body( - ..., description="Session creation parameters" - ), - db=db, -): - """Create a Session for a User""" - try: - session_obj = await crud.create_session( - db, app_id=app_id, user_id=user_id, session=session - ) - logger.info(f"Session created successfully for user {user_id}") - return session_obj - except ValueError as e: - logger.warning(f"Failed to create session: {str(e)}") - raise ValidationException(str(e)) from e - - @router.put( "/{session_id}", response_model=schemas.Session, dependencies=[ - Depends( - require_auth(app_id="app_id", user_id="user_id", session_id="session_id") - ) + Depends(require_auth(workspace_name="workspace_id", session_name="session_id")) ], ) async def update_session( - app_id: str = Path(..., description="ID of the app"), - user_id: str = Path(..., description="ID of the user"), + workspace_id: str = Path(..., description="ID of the workspace"), session_id: str = Path(..., description="ID of the session to update"), session: schemas.SessionUpdate = Body( ..., description="Updated session parameters" @@ -159,7 +122,7 @@ async def update_session( """Update the metadata of a Session""" try: updated_session = await crud.update_session( - db, app_id=app_id, user_id=user_id, session_id=session_id, session=session + db, workspace_name=workspace_id, session_name=session_id, session=session ) logger.info(f"Session {session_id} updated successfully") return updated_session @@ -171,21 +134,18 @@ async def update_session( @router.delete( "/{session_id}", dependencies=[ - Depends( - require_auth(app_id="app_id", user_id="user_id", session_id="session_id") - ) + Depends(require_auth(workspace_name="workspace_id", session_name="session_id")) ], ) async def delete_session( - app_id: str = Path(..., description="ID of the app"), - user_id: str = Path(..., description="ID of the user"), + workspace_id: str = Path(..., description="ID of the workspace"), session_id: str = Path(..., description="ID of the session to delete"), db=db, ): """Delete a session by marking it as inactive""" try: await crud.delete_session( - db, app_id=app_id, user_id=user_id, session_id=session_id + db, workspace_name=workspace_id, session_name=session_id ) logger.info(f"Session {session_id} deleted successfully") return {"message": "Session deleted successfully"} @@ -194,93 +154,322 @@ async def delete_session( raise ResourceNotFoundException("Session not found") from e -@router.post( - "/{session_id}/chat", - response_model=schemas.DialecticResponse, - responses={ - 200: { - "description": "Response to a question informed by Honcho's User Representation", - "content": {"text/event-stream": {}}, - }, - }, - dependencies=[ - Depends( - require_auth(app_id="app_id", user_id="user_id", session_id="session_id") - ) - ], -) -async def chat( - app_id: str = Path(..., description="ID of the app"), - user_id: str = Path(..., description="ID of the user"), - session_id: str = Path(..., description="ID of the session"), - options: schemas.DialecticOptions = Body( - ..., description="Dialectic Endpoint Parameters" - ), -): - - """Chat with the Dialectic API""" - if not options.stream: - return await agent.chat( - app_id=app_id, - user_id=user_id, - session_id=session_id, - queries=options.queries, - ) - else: - - async def parse_stream(): - try: - stream = await agent.chat( - app_id=app_id, - user_id=user_id, - session_id=session_id, - queries=options.queries, - stream=True, - ) - if type(stream) is AsyncMessageStreamManager: - async with stream as stream_manager: - async for text in stream_manager.text_stream: - yield text - except Exception as e: - logger.error(f"Error in stream: {str(e)}") - raise HTTPException(status_code=500, detail=str(e)) from e - - return StreamingResponse( - content=parse_stream(), media_type="text/event-stream", status_code=200 - ) - - @router.get( "/{session_id}/clone", response_model=schemas.Session, dependencies=[ - Depends( - require_auth(app_id="app_id", user_id="user_id", session_id="session_id") - ) + Depends(require_auth(workspace_name="workspace_id", session_name="session_id")) ], ) async def clone_session( - app_id: str = Path(..., description="ID of the app"), - user_id: str = Path(..., description="ID of the user"), + workspace_id: str = Path(..., description="ID of the workspace"), session_id: str = Path(..., description="ID of the session to clone"), db=db, message_id: Optional[str] = Query( None, description="Message ID to cut off the clone at" ), - deep_copy: bool = Query(False, description="Whether to deep copy metamessages"), ): """Clone a session, optionally up to a specific message""" try: + # TODO: Update crud.clone_session to work with new paradigm cloned_session = await crud.clone_session( db, - app_id=app_id, - user_id=user_id, - original_session_id=session_id, + workspace_name=workspace_id, + original_session_name=session_id, cutoff_message_id=message_id, - deep_copy=deep_copy, ) logger.info(f"Session {session_id} cloned successfully") return cloned_session except ValueError as e: logger.warning(f"Failed to clone session {session_id}: {str(e)}") raise ResourceNotFoundException("Session not found") from e + + +@router.post( + "/{session_id}/peers", + response_model=schemas.Session, + dependencies=[ + Depends(require_auth(workspace_name="workspace_id", session_name="session_id")) + ], +) +async def add_peers_to_session( + workspace_id: str = Path(..., description="ID of the workspace"), + session_id: str = Path(..., description="ID of the session"), + peers: dict[str, schemas.SessionPeerConfig] = Body( + ..., description="List of peer IDs to add to the session" + ), + db=db, +): + """Add peers to a session""" + try: + workspace_name, session_name = workspace_id, session_id + session = await crud.get_or_create_session( + db, + session=schemas.SessionCreate( + name=session_name, + peers=peers, + ), + workspace_name=workspace_name, + ) + logger.info(f"Added peers to session {session_name} successfully") + return session + except ValueError as e: + logger.warning(f"Failed to add peers to session {session_name}: {str(e)}") + raise ResourceNotFoundException("Session not found") from e + + +@router.put( + "/{session_id}/peers", + response_model=schemas.Session, + dependencies=[ + Depends(require_auth(workspace_name="workspace_id", session_name="session_id")) + ], +) +async def set_session_peers( + workspace_id: str = Path(..., description="ID of the workspace"), + session_id: str = Path(..., description="ID of the session"), + peers: dict[str, schemas.SessionPeerConfig] = Body( + ..., description="List of peer IDs to set for the session" + ), + db=db, +): + """Set the peers in a session""" + try: + workspace_name, session_name = workspace_id, session_id + await crud.set_peers_for_session( + db, + workspace_name=workspace_name, + session_name=session_name, + peer_names=peers, + ) + # Get the session to return + session = await crud.get_or_create_session( + db, + session=schemas.SessionCreate(name=session_name), + workspace_name=workspace_name, + ) + logger.info(f"Set peers for session {session_name} successfully") + return session + except ValueError as e: + logger.warning(f"Failed to set peers for session {session_name}: {str(e)}") + raise ResourceNotFoundException("Failed to set peers for session") from e + + +@router.delete( + "/{session_id}/peers", + response_model=schemas.Session, + dependencies=[ + Depends(require_auth(workspace_name="workspace_id", session_name="session_id")) + ], +) +async def remove_peers_from_session( + workspace_id: str = Path(..., description="ID of the workspace"), + session_id: str = Path(..., description="ID of the session"), + peers: list[str] = Body( + ..., description="List of peer IDs to remove from the session" + ), + db=db, +): + """Remove peers from a session""" + try: + session_name = session_id + await crud.remove_peers_from_session( + db, + workspace_name=workspace_id, + session_name=session_name, + peer_names=set(peers), + ) + # Get the session to return + session = await crud.get_or_create_session( + db, + session=schemas.SessionCreate(name=session_name), + workspace_name=workspace_id, + ) + logger.info(f"Removed peers from session {session_name} successfully") + return session + except ValueError as e: + logger.warning(f"Failed to remove peers from session {session_name}: {str(e)}") + raise ResourceNotFoundException("Session not found") from e + + +@router.get( + "/{session_id}/peers/{peer_id}/config", + response_model=schemas.SessionPeerConfig, + dependencies=[ + Depends(require_auth(workspace_name="workspace_id", session_name="session_id")) + ], +) +async def get_peer_config( + workspace_id: str = Path(..., description="ID of the workspace"), + session_id: str = Path(..., description="ID of the session"), + peer_id: str = Path(..., description="ID of the peer"), + db=db, +): + """Get the configuration for a peer in a session""" + return await crud.get_peer_config( + db, + workspace_name=workspace_id, + session_name=session_id, + peer_id=peer_id, + ) + + +@router.post( + "/{session_id}/peers/{peer_id}/config", + dependencies=[ + Depends(require_auth(workspace_name="workspace_id", session_name="session_id")) + ], +) +async def set_peer_config( + workspace_id: str = Path(..., description="ID of the workspace"), + session_id: str = Path(..., description="ID of the session"), + peer_id: str = Path(..., description="ID of the peer"), + config: schemas.SessionPeerConfig = Body(..., description="Peer configuration"), + db=db, +): + """Set the configuration for a peer in a session""" + try: + await crud.set_peer_config( + db, + workspace_name=workspace_id, + session_name=session_id, + peer_id=peer_id, + config=config, + ) + logger.info( + f"Set peer config for {peer_id} in session {session_id} successfully" + ) + return Response(status_code=200) + except ValueError as e: + logger.warning( + f"Failed to set peer config for {peer_id} in session {session_id}: {str(e)}" + ) + raise ResourceNotFoundException("Session not found") from e + + +@router.get( + "/{session_id}/peers", + response_model=Page[schemas.Peer], + dependencies=[ + Depends(require_auth(workspace_name="workspace_id", session_name="session_id")) + ], +) +async def get_session_peers( + workspace_id: str = Path(..., description="ID of the workspace"), + session_id: str = Path(..., description="ID of the session"), + db=db, +): + """Get peers from a session""" + try: + session_name = session_id + peers_query = await crud.get_peers_from_session( + workspace_name=workspace_id, session_name=session_name + ) + return await paginate(db, peers_query) + except ValueError as e: + logger.warning(f"Failed to get peers from session {session_name}: {str(e)}") + raise ResourceNotFoundException("Session not found") from e + + +@router.get( + "/{session_id}/context", + response_model=schemas.SessionContext, + dependencies=[ + Depends(require_auth(workspace_name="workspace_id", session_name="session_id")) + ], +) +async def get_session_context( + workspace_id: str = Path(..., description="ID of the workspace"), + session_id: str = Path(..., description="ID of the session"), + tokens: Optional[int] = Query( + None, + description="Number of tokens to use for the context. Includes summary if set to true", + ), + summary: bool = Query( + False, + description="Whether to summarize the session history prior to the cutoff message", + ), # default to false + db=db, +): + """ + Produce a context object from the session. The caller provides a token limit which the entire context must fit into. + To do this, we allocate 40% of the token limit to the summary, and 60% to recent messages -- as many as can fit. + If the caller does not want a summary, we allocate all the tokens to recent messages. + The default token limit if not provided is 2048. (TODO: make this configurable) + """ + token_limit = tokens or 2048 + summary_tokens = int(token_limit * 0.4) if summary else 0 + messages_tokens = token_limit - summary_tokens + + # Get the messages to return verbatim + messages_stmt = await crud.get_messages( + workspace_name=workspace_id, + session_name=session_id, + token_limit=messages_tokens, + ) + result = await db.execute(messages_stmt) + messages = list(result.scalars().all()) + + # Get the most recently created summary for the session + last_summary = await history.get_summary( + db, + workspace_name=workspace_id, + session_name=session_id, + ) + + # Get messages between the last summary and the first message we'll return verbatim, if any + messages_before = await crud.get_messages_id_range( + db, + workspace_name=workspace_id, + session_name=session_id, + peer_name=None, + start_id=last_summary["message_id"] if last_summary else 0, + end_id=messages[0].id if messages else None, + ) + + # Make a summary if the user wants one + if summary_tokens > 0: + # Make a *new* summary if there are unsummarized messages between the last summary and the ones + # we'll return verbatim, or if the last summary is too many tokens -- otherwise, just use the last summary + if ( + not last_summary + or len(messages_before) > 0 + or last_summary["token_count"] > summary_tokens + ): + new_summary = await history.create_summary( + messages=messages_before, + max_tokens=summary_tokens, + ) + summary_content = new_summary["content"] + else: + summary_content = last_summary["content"] + summary_tokens = last_summary["token_count"] + else: + summary_content = "" + + return schemas.SessionContext( + name=session_id, + messages=messages, + summary=summary_content, + ) + + +@router.post( + "/{session_id}/search", + response_model=Page[schemas.Message], + dependencies=[ + Depends(require_auth(workspace_name="workspace_id", session_name="session_id")) + ], +) +async def search_session( + workspace_id: str = Path(..., description="ID of the workspace"), + session_id: str = Path(..., description="ID of the session"), + query: str = Body(..., description="Search query"), + db=db, +): + """Search a Session""" + stmt = await crud.search( + query, workspace_name=workspace_id, session_name=session_id + ) + + return await paginate(db, stmt) diff --git a/src/routers/users.py b/src/routers/users.py deleted file mode 100644 index eef601eb..00000000 --- a/src/routers/users.py +++ /dev/null @@ -1,162 +0,0 @@ -import logging -from typing import Optional - -from fastapi import APIRouter, Body, Depends, Path, Query -from fastapi_pagination import Page -from fastapi_pagination.ext.sqlalchemy import paginate - -from src import crud, schemas -from src.dependencies import db -from src.exceptions import ( - AuthenticationException, - ResourceNotFoundException, -) -from src.security import JWTParams, require_auth - -logger = logging.getLogger(__name__) - -router = APIRouter( - prefix="/apps/{app_id}/users", - tags=["users"], -) - - -@router.post( - "", - response_model=schemas.User, - dependencies=[Depends(require_auth(app_id="app_id", user_id="user_id"))], -) -async def create_user( - app_id: str = Path(..., description="ID of the app"), - user: schemas.UserCreate = Body(..., description="User creation parameters"), - db=db, -): - """Create a new User""" - user_obj = await crud.create_user(db, app_id=app_id, user=user) - return user_obj - - -@router.post( - "/list", - response_model=Page[schemas.User], - dependencies=[Depends(require_auth(app_id="app_id", user_id="user_id"))], -) -async def get_users( - app_id: str = Path(..., description="ID of the app"), - options: Optional[schemas.UserGet] = Body( - None, description="Filtering options for the users list" - ), - reverse: bool = Query(False, description="Whether to reverse the order of results"), - db=db, -): - """Get All Users for an App""" - filter_param = None - if options and hasattr(options, "filter"): - filter_param = options.filter - if filter_param == {}: - filter_param = None - - return await paginate( - db, - await crud.get_users(app_id=app_id, reverse=reverse, filter=filter_param), - ) - - -@router.get( - "", - response_model=schemas.User, -) -async def get_user( - app_id: str = Path(..., description="ID of the app"), - user_id: Optional[str] = Query( - None, description="User ID to retrieve. If not provided, users JWT token" - ), - jwt_params: JWTParams = Depends(require_auth()), - db=db, -): - """ - Get a User by ID - - If user_id is provided as a query parameter, it uses that (must match JWT app_id). - Otherwise, it uses the user_id from the JWT token. - """ - # validate app query param - if not jwt_params.ad and jwt_params.ap is not None and jwt_params.ap != app_id: - raise AuthenticationException("Unauthorized access to resource") - - if user_id: - if not jwt_params.ad and jwt_params.us is not None and jwt_params.us != user_id: - raise AuthenticationException("Unauthorized access to resource") - target_user_id = user_id - else: - # Use user_id from JWT - if not jwt_params.us: - raise AuthenticationException("User ID not found in query parameter or JWT") - target_user_id = jwt_params.us - user = await crud.get_user(db, app_id=app_id, user_id=target_user_id) - return user - - -@router.get( - "/name/{name}", - response_model=schemas.User, - dependencies=[ - Depends( - require_auth( - app_id="app_id", - ) - ) - ], -) -async def get_user_by_name( - app_id: str = Path(..., description="ID of the app"), - name: str = Path(..., description="Name of the user to retrieve"), - db=db, -): - """Get a User by name""" - user = await crud.get_user_by_name(db, app_id=app_id, name=name) - return user - - -@router.get( - "/get_or_create/{name}", - response_model=schemas.User, - dependencies=[ - Depends( - require_auth( - app_id="app_id", - ) - ) - ], -) -async def get_or_create_user( - app_id: str = Path(..., description="ID of the app"), - name: str = Path(..., description="Name of the user to get or create"), - db=db, -): - """Get a User or create a new one by the input name""" - try: - user = await crud.get_user_by_name(db, app_id=app_id, name=name) - return user - except ResourceNotFoundException: - # User doesn't exist, create it - user = await create_user( - db=db, app_id=app_id, user=schemas.UserCreate(name=name) - ) - return user - - -@router.put( - "/{user_id}", - response_model=schemas.User, - dependencies=[Depends(require_auth(app_id="app_id", user_id="user_id"))], -) -async def update_user( - app_id: str = Path(..., description="ID of the app"), - user_id: str = Path(..., description="ID of the user to update"), - user: schemas.UserUpdate = Body(..., description="Updated user parameters"), - db=db, -): - """Update a User's name and/or metadata""" - updated_user = await crud.update_user(db, app_id=app_id, user_id=user_id, user=user) - return updated_user diff --git a/src/routers/workspaces.py b/src/routers/workspaces.py new file mode 100644 index 00000000..a1aa4524 --- /dev/null +++ b/src/routers/workspaces.py @@ -0,0 +1,107 @@ +import logging +from typing import Optional + +from fastapi import APIRouter, Body, Depends, Path +from fastapi_pagination import Page +from fastapi_pagination.ext.sqlalchemy import paginate + +from src import crud, schemas +from src.dependencies import db +from src.exceptions import AuthenticationException +from src.security import JWTParams, require_auth + +logger = logging.getLogger(__name__) + +router = APIRouter( + prefix="/workspaces", + tags=["workspaces"], +) + + +@router.post("", response_model=schemas.Workspace) +async def get_or_create_workspace( + workspace: schemas.WorkspaceCreate = Body( + ..., description="Workspace creation parameters" + ), + jwt_params: JWTParams = Depends(require_auth()), + db=db, +): + """ + Get a Workspace by ID. + + If workspace_id is provided as a query parameter, it uses that (must match JWT workspace_id). + Otherwise, it uses the workspace_id from the JWT token. + """ + # If workspace_id provided in query, check if it matches jwt or user is admin + if workspace.name: + if not jwt_params.ad and jwt_params.w != workspace.name: + raise AuthenticationException("Unauthorized access to resource") + else: + # Use workspace_id from JWT + if not jwt_params.w: + raise AuthenticationException( + "Workspace ID not found in query parameter or JWT" + ) + workspace.name = jwt_params.w + + return await crud.get_or_create_workspace(db, workspace=workspace) + + +@router.post( + "/list", + response_model=Page[schemas.Workspace], + dependencies=[Depends(require_auth(admin=True))], +) +async def get_all_workspaces( + options: Optional[schemas.WorkspaceGet] = Body( + None, description="Filtering and pagination options for the workspaces list" + ), + db=db, +): + """Get all Workspaces""" + filter_param = None + if options and hasattr(options, "filter"): + filter_param = options.filter + if filter_param == {}: + filter_param = None + + return await paginate( + db, + await crud.get_all_workspaces(filter=filter_param), + ) + + +@router.put( + "/{workspace_id}", + response_model=schemas.Workspace, + dependencies=[Depends(require_auth(workspace_name="workspace_id"))], +) +async def update_workspace( + workspace_id: str = Path(..., description="ID of the workspace to update"), + workspace: schemas.WorkspaceUpdate = Body( + ..., description="Updated workspace parameters" + ), + db=db, +): + """Update a Workspace""" + # ResourceNotFoundException will be caught by global handler if workspace not found + honcho_workspace = await crud.update_workspace( + db, workspace_name=workspace_id, workspace=workspace + ) + return honcho_workspace + + +@router.post( + "/{workspace_id}/search", + response_model=Page[schemas.Message], + dependencies=[Depends(require_auth(workspace_name="workspace_id"))], +) +async def search_workspace( + workspace_id: str = Path(..., description="ID of the workspace to search"), + query: str = Body(..., description="Search query"), + db=db, +): + """Search a Workspace""" + stmt = await crud.search(query, workspace_name=workspace_id) + + return await paginate(db, stmt) diff --git a/src/schemas.py b/src/schemas.py index bf8d2ce0..e5825e75 100644 --- a/src/schemas.py +++ b/src/schemas.py @@ -1,67 +1,92 @@ import datetime -from typing import Annotated +from typing import Annotated, Optional -from pydantic import BaseModel, ConfigDict, Field, computed_field, field_validator +from pydantic import BaseModel, ConfigDict, Field, field_validator + +RESOURCE_NAME_PATTERN = r"^[a-zA-Z0-9_-]+$" -class AppBase(BaseModel): +class WorkspaceBase(BaseModel): pass -class AppCreate(AppBase): - name: Annotated[str, Field(min_length=1, max_length=100)] +class WorkspaceCreate(WorkspaceBase): + name: Annotated[ + str, + Field(alias="id", min_length=1, max_length=100, pattern=RESOURCE_NAME_PATTERN), + ] metadata: dict = {} + configuration: dict = {} + + model_config = ConfigDict(populate_by_name=True) -class AppGet(AppBase): +class WorkspaceGet(WorkspaceBase): filter: dict | None = None -class AppUpdate(AppBase): - name: str | None = None +class WorkspaceUpdate(WorkspaceBase): metadata: dict | None = None + configuration: dict | None = None -class App(AppBase): - public_id: str = Field(serialization_alias='id') - name: str - h_metadata: dict = Field(default={}, serialization_alias='metadata') +class Workspace(WorkspaceBase): + name: str = Field(serialization_alias="id") + h_metadata: dict = Field(default_factory=dict, serialization_alias="metadata") + configuration: dict = Field(default_factory=dict) created_at: datetime.datetime - model_config = ConfigDict( - from_attributes=True, - populate_by_name=True + model_config = ConfigDict(from_attributes=True, populate_by_name=True) + + +class PeerBase(BaseModel): + pass + + +class PeerCreate(PeerBase): + name: Annotated[ + str, + Field(alias="id", min_length=1, max_length=100, pattern=RESOURCE_NAME_PATTERN), + ] + metadata: dict | None = None + configuration: dict | None = None + + model_config = ConfigDict(populate_by_name=True) + + +class PeerGet(PeerBase): + filter: dict | None = None + + +class PeerUpdate(PeerBase): + metadata: dict | None = None + configuration: dict | None = None + + +class Peer(PeerBase): + name: str = Field(serialization_alias="id") + workspace_name: str = Field(serialization_alias="workspace_id") + created_at: datetime.datetime + h_metadata: dict = Field(default_factory=dict, serialization_alias="metadata") + configuration: dict = Field(default_factory=dict) + + model_config = ConfigDict(from_attributes=True, populate_by_name=True) + + +class PeerRepresentationGet(BaseModel): + session_id: str = Field( + ..., description="Get the working representation within this session" + ) + target: Optional[str] = Field( + None, + description="Optional peer ID to get the representation for, from the perspective of this peer", ) -class UserBase(BaseModel): - pass - - -class UserCreate(UserBase): - name: Annotated[str, Field(min_length=1, max_length=100)] - metadata: dict = {} - - -class UserGet(UserBase): - filter: dict | None = None - - -class UserUpdate(UserBase): - name: str | None = None - metadata: dict | None = None - - -class User(UserBase): - public_id: str = Field(serialization_alias='id') - name: str - app_id: str - created_at: datetime.datetime - h_metadata: dict = Field(default={}, serialization_alias='metadata') - - model_config = ConfigDict( - from_attributes=True, - populate_by_name=True +class PeerConfig(BaseModel): + observe_me: bool = Field( + default=True, + description="Whether honcho should form a global theory-of-mind representation of this peer", ) @@ -71,8 +96,8 @@ class MessageBase(BaseModel): class MessageCreate(MessageBase): content: Annotated[str, Field(min_length=0, max_length=50000)] - is_user: bool - metadata: dict = {} + peer_name: str = Field(alias="peer_id") + metadata: dict | None = None class MessageGet(MessageBase): @@ -80,31 +105,53 @@ class MessageGet(MessageBase): class MessageUpdate(MessageBase): - metadata: dict + metadata: dict | None = None class Message(MessageBase): - public_id: str = Field(serialization_alias='id') + public_id: str = Field(serialization_alias="id") content: str - is_user: bool - session_id: str - h_metadata: dict = Field(default={}, serialization_alias='metadata') + peer_name: str = Field(serialization_alias="peer_id") + session_name: str | None = Field(serialization_alias="session_id") + h_metadata: dict = Field(default_factory=dict, serialization_alias="metadata") created_at: datetime.datetime - app_id: str - user_id: str + workspace_name: str = Field(serialization_alias="workspace_id") + token_count: int - model_config = ConfigDict( - from_attributes=True, - populate_by_name=True - ) + model_config = ConfigDict(from_attributes=True, populate_by_name=True) + + +class MessageBatchCreate(BaseModel): + """Schema for batch message creation with a max of 100 messages""" + + messages: list[MessageCreate] = Field(..., min_length=1, max_length=100) class SessionBase(BaseModel): pass +class SessionPeerConfig(BaseModel): + observe_others: bool = Field( + default=False, + description="Whether this peer should form a session-level theory-of-mind representation of other peers in the session", + ) + observe_me: bool = Field( + default=True, + description="Whether other peers in this session should try to form a session-level theory-of-mind representation of this peer", + ) + + class SessionCreate(SessionBase): - metadata: dict = {} + name: Annotated[ + str, + Field(alias="id", min_length=1, max_length=100, pattern=RESOURCE_NAME_PATTERN), + ] + metadata: dict | None = None + peer_names: dict[str, SessionPeerConfig] | None = Field(default=None, alias="peers") + configuration: dict | None = None + + model_config = ConfigDict(populate_by_name=True) class SessionGet(SessionBase): @@ -113,120 +160,27 @@ class SessionGet(SessionBase): class SessionUpdate(SessionBase): - metadata: dict + metadata: dict | None = None + configuration: dict | None = None class Session(SessionBase): - public_id: str = Field(serialization_alias='id') + name: str = Field(serialization_alias="id") is_active: bool - user_id: str - app_id: str - h_metadata: dict = Field(default={}, serialization_alias='metadata') + workspace_name: str = Field(serialization_alias="workspace_id") + h_metadata: dict = Field(default_factory=dict, serialization_alias="metadata") + configuration: dict = Field(default_factory=dict) created_at: datetime.datetime - model_config = ConfigDict( - from_attributes=True, - populate_by_name=True - ) + model_config = ConfigDict(from_attributes=True, populate_by_name=True) -class MetamessageBase(BaseModel): - pass +class SessionContext(SessionBase): + name: str = Field(serialization_alias="id") + messages: list[Message] + summary: str - -class MetamessageCreate(MetamessageBase): - label: Annotated[str, Field(min_length=1, max_length=50, alias='metamessage_type')] - content: Annotated[str, Field(min_length=0, max_length=50000)] - session_id: str | None = None - message_id: str | None = None - metadata: dict = {} - - model_config = ConfigDict(populate_by_name=True) - - -class MetamessageGet(MetamessageBase): - label: str | None = Field(default=None, alias='metamessage_type') - session_id: str | None = None - message_id: str | None = None - filter: dict | None = None - - model_config = ConfigDict(populate_by_name=True) - - -class MetamessageUpdate(MetamessageBase): - session_id: str | None = None - message_id: str | None = None - label: str | None = Field(default=None, alias='metamessage_type') - metadata: dict | None = None - - model_config = ConfigDict(populate_by_name=True) - - -class Metamessage(MetamessageBase): - public_id: str = Field(serialization_alias='id') - label: str - content: str - user_id: str - app_id: str - session_id: str | None - message_id: str | None - h_metadata: dict = Field(default={}, serialization_alias='metadata') - created_at: datetime.datetime - - # Included for backwards compatibility with the old metamessage_type field - @computed_field - @property - def metamessage_type(self) -> str: - return self.label - - model_config = ConfigDict( - from_attributes=True, - populate_by_name=True - ) - - -class CollectionBase(BaseModel): - pass - - -class CollectionCreate(CollectionBase): - name: Annotated[str, Field(min_length=1, max_length=100)] - metadata: dict = {} - - @field_validator("name") - def validate_name(cls, v): - if v.lower() == "honcho": - raise ValueError("Collection name cannot be 'honcho'") - return v - - -class CollectionGet(CollectionBase): - filter: dict | None = None - - -class CollectionUpdate(CollectionBase): - name: str | None = None - metadata: dict | None = None - - @field_validator("name") - def validate_name(cls, v): - if v is not None and v.lower() == "honcho": - raise ValueError("Collection name cannot be 'honcho'") - return v - - -class Collection(CollectionBase): - public_id: str = Field(serialization_alias='id') - name: str - user_id: str - app_id: str - h_metadata: dict = Field(default={}, serialization_alias='metadata') - created_at: datetime.datetime - - model_config = ConfigDict( - from_attributes=True, - populate_by_name=True - ) + model_config = ConfigDict(from_attributes=True, populate_by_name=True) class DocumentBase(BaseModel): @@ -238,37 +192,14 @@ class DocumentCreate(DocumentBase): metadata: dict = {} -class DocumentGet(DocumentBase): - filter: dict | None = None - - -class DocumentQuery(DocumentBase): - query: Annotated[str, Field(min_length=1, max_length=1000)] - filter: dict | None = None - top_k: int = Field(default=5, ge=1, le=50) - - -class DocumentUpdate(DocumentBase): - metadata: dict | None = Field(None, max_length=10000) - content: Annotated[str | None, Field(min_length=1, max_length=100000)] = None - - -class Document(DocumentBase): - public_id: str = Field(serialization_alias='id') - content: str - h_metadata: dict = Field(default={}, serialization_alias='metadata') - created_at: datetime.datetime - collection_id: str - app_id: str - user_id: str - - model_config = ConfigDict( - from_attributes=True, - populate_by_name=True - ) - - class DialecticOptions(BaseModel): + session_id: Optional[str] = Field( + None, description="ID of the session to scope the representation to" + ) + target: Optional[str] = Field( + None, + description="Optional peer to get the representation for, from the perspective of this peer", + ) queries: str | list[str] stream: bool = False @@ -289,9 +220,3 @@ class DialecticOptions(BaseModel): class DialecticResponse(BaseModel): content: str - - -class MessageBatchCreate(BaseModel): - """Schema for batch message creation with a max of 100 messages""" - - messages: list[MessageCreate] = Field(..., max_length=100) diff --git a/src/security.py b/src/security.py index 6b1e837e..29eda037 100644 --- a/src/security.py +++ b/src/security.py @@ -41,10 +41,14 @@ security = HTTPBearer( class JWTParams(BaseModel): """ JWT parameters used to produce tokens valid for different routes. - Hierarchy: app > user > (session / collection) + Workspaces are the top level of the hierarchy -- a workspace key will + give access to all peers/sessions/collections in that workspace. - All routers require at least the most tightly scoped parameter. - Routes will accept a JWT with a scope higher in the hierarchy. + A session key will allow the listing and creation of messages in + that session. + + A peer key will allow the listing and creation of peer-level messages + and querying the peer's dialectic endpoint. Names shortened to minimize token size. Timestamp is included so that many unique tokens can be generated for the same resource. @@ -56,19 +60,17 @@ class JWTParams(BaseModel): `t`: a string timestamp of when the JWT was created `exp`: a string timestamp of when the JWT expires (optional) `ad`: a boolean flag indicating if the JWT is an admin JWT - `ap`: (string) app id - `us`: (string) user id - `se`: (string) session id - `co`: (string) collection id + `w`: (string) workspace name + `p`: (string) peer name + `s`: (string) session name """ t: str = datetime.datetime.now().isoformat() exp: Optional[str] = None ad: Optional[bool] = None - ap: Optional[str] = None - us: Optional[str] = None - se: Optional[str] = None - co: Optional[str] = None + w: Optional[str] = None + p: Optional[str] = None + s: Optional[str] = None def create_admin_jwt() -> str: @@ -104,14 +106,12 @@ async def verify_jwt(token: str) -> JWTParams: raise AuthenticationException("JWT expired") if "ad" in decoded: params.ad = decoded["ad"] - if "ap" in decoded: - params.ap = decoded["ap"] - if "us" in decoded: - params.us = decoded["us"] - if "se" in decoded: - params.se = decoded["se"] - if "co" in decoded: - params.co = decoded["co"] + if "w" in decoded: + params.w = decoded["w"] + if "p" in decoded: + params.p = decoded["p"] + if "s" in decoded: + params.s = decoded["s"] return params except jwt.PyJWTError: raise AuthenticationException("Invalid JWT") from None @@ -119,10 +119,9 @@ async def verify_jwt(token: str) -> JWTParams: def require_auth( admin: Optional[bool] = None, - app_id: Optional[str] = None, - user_id: Optional[str] = None, - session_id: Optional[str] = None, - collection_id: Optional[str] = None, + workspace_name: Optional[str] = None, + peer_name: Optional[str] = None, + session_name: Optional[str] = None, ): """ Generate a dependency that requires authentication for the given parameters. @@ -133,35 +132,30 @@ def require_auth( credentials: HTTPAuthorizationCredentials = Depends(security), db: AsyncSession = Depends(get_db), ): - app_id_param = ( - request.path_params.get(app_id) or request.query_params.get(app_id) - if app_id + workspace_name_param = ( + request.path_params.get(workspace_name) + or request.query_params.get(workspace_name) + if workspace_name else None ) - user_id_param = ( - request.path_params.get(user_id) or request.query_params.get(user_id) - if user_id + peer_name_param = ( + request.path_params.get(peer_name) or request.query_params.get(peer_name) + if peer_name else None ) - session_id_param = ( - request.path_params.get(session_id) or request.query_params.get(session_id) - if session_id - else None - ) - collection_id_param = ( - request.path_params.get(collection_id) - or request.query_params.get(collection_id) - if collection_id + session_name_param = ( + request.path_params.get(session_name) + or request.query_params.get(session_name) + if session_name else None ) return await auth( credentials=credentials, admin=admin, - app_id=app_id_param, - user_id=user_id_param, - session_id=session_id_param, - collection_id=collection_id_param, + workspace_name=workspace_name_param, + peer_name=peer_name_param, + session_name=session_name_param, ) return auth_dependency @@ -170,10 +164,9 @@ def require_auth( async def auth( credentials: Annotated[HTTPAuthorizationCredentials, Depends(security)], admin: Optional[bool] = None, - app_id: Optional[str] = None, - user_id: Optional[str] = None, - session_id: Optional[str] = None, - collection_id: Optional[str] = None, + workspace_name: Optional[str] = None, + peer_name: Optional[str] = None, + session_name: Optional[str] = None, ) -> JWTParams: """Authenticate the given JWT and return the decoded parameters.""" if not USE_AUTH: @@ -190,24 +183,23 @@ async def auth( if admin: raise AuthenticationException("Resource requires admin privileges") - # Check if the JWT has direct access to the requested resource - # For session or collection level access - if session_id and jwt_params.se == session_id: - return jwt_params - if collection_id and jwt_params.co == collection_id: + # For session level access + if session_name and jwt_params.s == session_name: + if workspace_name and jwt_params.w != workspace_name: + raise AuthenticationException("JWT not permissioned for this resource") return jwt_params - # For user level access - can access all sessions/collections under this user - if user_id and jwt_params.us == user_id: + # For peer level access + if peer_name and jwt_params.p == peer_name: + if workspace_name and jwt_params.w != workspace_name: + raise AuthenticationException("JWT not permissioned for this resource") return jwt_params - # For app level access - can access all users/sessions/collections under this app - if app_id and jwt_params.ap == app_id: + # For workspace level access - can access all peers/sessions under this workspace + if workspace_name and jwt_params.w == workspace_name: return jwt_params - if any([session_id, collection_id, user_id, app_id]): - print([session_id, collection_id, user_id, app_id]) - print(jwt_params) + if any([session_name, peer_name, workspace_name]): raise AuthenticationException("JWT not permissioned for this resource") # Route did not specify any parameters, so it should parse parameters itself diff --git a/src/utils/history.py b/src/utils/history.py index 6cec266f..849aed87 100644 --- a/src/utils/history.py +++ b/src/utils/history.py @@ -1,28 +1,48 @@ +import datetime import logging from enum import Enum -from typing import Optional, Union, cast +from typing import Optional, TypedDict -from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from src.utils.model_client import ModelClient, ModelProvider -from .. import models +from .. import crud, models logger = logging.getLogger(__name__) + +# TypedDict definitions for summary data +class Summary(TypedDict): + """ + A summary object. Stored in session metadata and used in a session's get_context. + + Attributes: + content: The summary text. + message_count: The number of messages covered by this summary. + summary_type: The type of summary (short or long). + created_at: The timestamp of when the summary was created (ISO format string). + message_id: The primary key ID of the message that triggered this summary. + token_count: The number of tokens in the summary text. + """ + + content: str + message_count: int + summary_type: str + created_at: str + message_id: int + token_count: int + + # Export the public functions __all__ = [ - "get_session_summaries", - "get_messages_since_message", - "get_messages_since_latest_summary", + "get_summary", "create_summary", - "save_summary_metamessage", + "save_summary", "get_summarized_history", "should_create_summary", - "MESSAGES_PER_SHORT_SUMMARY", - "MESSAGES_PER_LONG_SUMMARY", "SummaryType", + "Summary", ] @@ -31,7 +51,7 @@ MESSAGES_PER_SHORT_SUMMARY = 20 # How often to create short summaries MESSAGES_PER_LONG_SUMMARY = 60 # How often to create long summaries -# The types of metamessages to use for summaries +# The types of summary to store in the session metadata class SummaryType(Enum): SHORT = "honcho_chat_summary_short" LONG = "honcho_chat_summary_long" @@ -42,110 +62,64 @@ DEFAULT_PROVIDER = ModelProvider.GEMINI DEFAULT_MODEL = "gemini-2.0-flash-lite" -async def get_session_summaries( +async def get_summary( db: AsyncSession, - session_id: str, + workspace_name: str, + session_name: str, summary_type: SummaryType = SummaryType.SHORT, - only_latest: bool = False, -) -> Union[list[models.Metamessage], Optional[models.Metamessage]]: +) -> Optional[Summary]: """ - Get summaries for a given session. + Get summary for a given session or peer. Args: db: Database session - session_id: The session ID + workspace_name: The workspace name + session_name: The session name summary_type: Type of summary to retrieve ("short" or "long") - only_latest: Whether to return only the latest summary Returns: - If only_latest is True: The most recent summary metamessage, or None if none exists - If only_latest is False: A list of all summary metamessages for the session + The summary data dictionary, or None if no summary exists """ - # Determine the metamessage type based on summary_type + from src.exceptions import ResourceNotFoundException + label = ( SummaryType.SHORT.value if summary_type == SummaryType.SHORT else SummaryType.LONG.value ) - stmt = ( - select(models.Metamessage) - .where(models.Metamessage.session_id == session_id) - .where(models.Metamessage.label == label) - .order_by(models.Metamessage.id.desc()) - ) + try: + session = await crud.get_session(db, session_name, workspace_name) + except ResourceNotFoundException: + # If session doesn't exist, there's no summary to retrieve + return None - if only_latest: - stmt = stmt.limit(1) - result = await db.execute(stmt) - # Always return a metamessage instance or None - return result.scalar_one_or_none() - else: - result = await db.execute(stmt) - return list(result.scalars().all()) - - -async def get_messages_since_message( - db: AsyncSession, session_id: str, message_id: Optional[str] = None -) -> list[models.Message]: - """ - Get all messages since a specific message. - - Args: - db: Database session - session_id: The session ID - message_id: The reference message ID - - Returns: - List of messages after the reference message or all messages if message_id is None - """ - # Base query for messages in this session - query = ( - select(models.Message) - .where(models.Message.session_id == session_id) - .order_by(models.Message.id) - ) - - # If we have a reference message ID, filter to get newer messages - if message_id: - # First, get the ID of the message - message_id_subquery = ( - select(models.Message.id) - .where(models.Message.public_id == message_id) - .scalar_subquery() - ) - - # Then filter to only get messages with higher IDs - query = query.where(models.Message.id > message_id_subquery) - - # Execute query - result = await db.execute(query) - return list(result.scalars().all()) + summaries = session.internal_metadata.get("summaries", {}) + if not summaries or label not in summaries: + return None + return summaries[label] async def create_summary( messages: list[models.Message], - previous_summary: Optional[str] = None, + previous_summary_text: Optional[str] = None, summary_type: SummaryType = SummaryType.SHORT, -) -> str: + max_tokens: Optional[int] = None, +) -> Summary: """ Generate a summary of the provided messages using an LLM. Args: messages: List of messages to summarize - previous_summary: Optional previous summary to provide context + previous_summary_text: Optional previous summary to provide context summary_type: Type of summary to create ("short" or "long") + max_tokens: Optional maximum number of tokens to generate. Supersedes summary_type. Returns: A summary of the conversation """ - # Combine messages into a conversation format - conversation = "\n".join( - [ - f"{'human' if msg.is_user else 'assistant'}: {msg.content}" - for msg in messages - ] - ) + # Combine messages into a conversation format string + conversation = format_messages(messages) # Adjust system prompt based on summary type if summary_type == SummaryType.LONG: @@ -182,9 +156,9 @@ but brief enough to be helpful. Return only the summary without any explanation or meta-commentary.""" # Include previous summary if available - if previous_summary: + if previous_summary_text: user_prompt = f"""Here is a previous summary of the conversation: -{previous_summary} +{previous_summary_text} Now please summarize these additional messages, incorporating the context from the previous summary. Your summary should summarize the entire conversation in a self-contained way, such that someone could read it and understand the entire conversation. @@ -201,167 +175,205 @@ Provide a {"comprehensive" if summary_type == SummaryType.LONG else "concise"} s # Generate the summary llm_messages = [{"role": "user", "content": user_prompt}] + max_tokens = max_tokens or (1000 if summary_type == SummaryType.SHORT else 2000) + try: - summary = await client.generate( + summary_text = await client.generate( messages=llm_messages, system=system_prompt, - max_tokens=1000 - if summary_type == SummaryType.SHORT - else 2000, # Allow longer responses for long summaries + max_tokens=max_tokens, temperature=0.0, use_caching=True, ) - return summary + logger.info("Successfully generated summary for session") + return Summary( + content=summary_text, + message_count=len(messages), + summary_type=summary_type.value, + created_at=datetime.datetime.now(datetime.timezone.utc).isoformat(), + message_id=messages[-1].id, + # cheating: just use the max tokens instead of counting + # tokens in response. in the future we can update client + # to return tokens if that's doable. + token_count=max_tokens, + ) except Exception as e: logger.error(f"Error generating summary: {str(e)}") # Fallback to a basic summary in case of error - return f"Conversation with {len(messages)} messages about {messages[-1].content[:30]}..." + # Do not save this failed summary to the session metadata. + return Summary( + content=f"Conversation with {len(messages)} messages about {messages[-1].content[:30]}..." + if messages + else "No messages to summarize!", + message_count=0, + summary_type=summary_type.value, + created_at=datetime.datetime.now(datetime.timezone.utc).isoformat(), + message_id=messages[-1].id if messages else 0, + token_count=50, + ) -async def save_summary_metamessage( +async def save_summary( db: AsyncSession, - app_id: str, - user_id: str, - session_id: str, - message_id: str, - summary_content: str, - message_count: int, - summary_type: SummaryType = SummaryType.SHORT, -) -> models.Metamessage: + summary: Summary, + workspace_name: str, + session_name: str, +) -> None: """ - Save a summary as a metamessage. + Save a summary as metadata on a session. Args: db: Database session - user_id: User ID - session_id: Session ID - message_id: The ID of the most recent message being summarized - summary_content: The summary text to save - message_count: Number of messages covered by this summary - summary_type: Type of summary to save + summary: The summary to save + workspace_name: Workspace name + session_name: Session name Returns: - The created metamessage + The updated session """ + from src.exceptions import ResourceNotFoundException + # Get the label value from the enum - label_value = summary_type.value + label_value = summary["summary_type"] - # Create and save the metamessage - metamessage = models.Metamessage( - app_id=app_id, - user_id=user_id, - session_id=session_id, - message_id=message_id, - label=label_value, - content=summary_content, - h_metadata={"message_count": message_count, "summary_type": summary_type.name}, - ) + try: + session = await crud.get_session(db, session_name, workspace_name) + except ResourceNotFoundException: + # If session doesn't exist, we can't save the summary + logger.warning( + f"Cannot save summary: session {session_name} not found in workspace {workspace_name}" + ) + return + + # Get existing summaries or create new dict + existing_summaries = session.internal_metadata.get("summaries", {}) + existing_summaries[label_value] = summary + + # Update the object metadata - create new dict to ensure SQLAlchemy detects the change + updated_metadata = session.internal_metadata.copy() + updated_metadata["summaries"] = existing_summaries + session.internal_metadata = updated_metadata - db.add(metamessage) await db.commit() logger.info( - f"Saved {summary_type.name.lower()} summary metamessage for session {session_id} covering {message_count} messages" + "Saved %s for session %s covering %s messages", + summary["summary_type"], + session_name, + summary["message_count"], ) - return metamessage - - -async def get_full_history( - db: AsyncSession, session_id: str -) -> tuple[str, list[models.Message]]: - """ - Get all messages for a given session. - """ - messages = await get_messages_since_message(db, session_id) - return format_messages(messages), messages async def get_summarized_history( - db: AsyncSession, session_id: str, summary_type: SummaryType = SummaryType.SHORT -) -> tuple[str, list[models.Message], Optional[models.Metamessage]]: + db: AsyncSession, + workspace_name: str, + session_name: str, + peer_name: str, + cutoff: int | None = None, + summary_type: SummaryType = SummaryType.SHORT, +) -> str: """ Get a summarized version of the chat history by combining the latest summary with all messages since that summary. + Note: history is exclusive of the cutoff message. + Args: db: Database session - session_id: The session ID + workspace_name: The workspace name + session_name: The session name + peer_name: The peer name + cutoff: (Optional) message ID to cutoff at summary_type: Type of summary to get ("short" or "long") Returns: - A tuple containing: - - String formatted history text with summary and recent messages - - List of messages since the latest summary - - The latest summary metamessage, or None if no summary exists + A string formatted history text with summary and recent messages """ # Get messages since the latest summary and the summary itself - messages, latest_summary = await get_messages_since_latest_summary( - db, session_id, summary_type + messages, latest_summary = await get_latest_summary_and_messages_since( + db, workspace_name, session_name, peer_name, cutoff, summary_type ) # Format messages messages_text = format_messages(messages) - # We know latest_summary is either a Metamessage or None because of the type - # narrowing in get_messages_since_latest_summary if latest_summary: # Combine summary with recent messages - history_text = f"[CONVERSATION SUMMARY: {latest_summary.content}]\n\n[RECENT MESSAGES]\n{messages_text}" + return f"[CONVERSATION SUMMARY: {latest_summary['content']}]\n\n[RECENT MESSAGES]\n{messages_text}" else: # No summary available, return just the messages - history_text = messages_text - return history_text, messages, latest_summary + return messages_text -async def get_messages_since_latest_summary( - db: AsyncSession, session_id: str, summary_type: SummaryType = SummaryType.SHORT -) -> tuple[list[models.Message], Optional[models.Metamessage]]: +async def get_latest_summary_and_messages_since( + db: AsyncSession, + workspace_name: str, + session_name: str, + peer_name: str, + cutoff: int | None = None, + summary_type: SummaryType = SummaryType.SHORT, +) -> tuple[list[models.Message], Optional[Summary]]: """ - Get all messages since the latest summary for a session. + Get all messages since the latest summary for a session or peer. This is a convenience method that combines: - 1. Getting the latest summary for the session + 1. Getting the latest summary for the session or peer 2. Getting all messages since that summary + Note that if the latest summary is not found, this will return all messages + since the start of the session. + + Note: history is exclusive of the cutoff message. + Args: db: Database session - session_id: The session ID + workspace_name: The workspace name + session_name: The session name + peer_name: The peer name + cutoff: (Optional) message ID to cutoff at summary_type: Type of summary to get ("short" or "long") Returns: A tuple containing: - List of messages since the latest summary (or all messages if no summary exists) - - The latest summary metamessage, or None if no summary exists + - The latest summary data, or None if no summary exists """ - # Get the latest summary (will be a Metamessage instance or None) - # We include only_latest=True to get a single Metamessage (not a list) - summary = await get_session_summaries( - db, session_id, summary_type=summary_type, only_latest=True - ) - - # Type narrowing - summary is now either None or a Metamessage instance - latest_summary = cast(Optional[models.Metamessage], summary) + # Get the latest summary + summary = await get_summary(db, workspace_name, session_name, summary_type) # Check if we have a valid summary with a message_id - if latest_summary is not None: - messages = await get_messages_since_message( - db, session_id, latest_summary.message_id + if summary: + messages = await crud.get_messages_id_range( + db, + workspace_name, + session_name, + peer_name, + start_id=summary["message_id"], + end_id=cutoff, ) - return messages, latest_summary + return messages, summary else: - messages = await get_messages_since_message(db, session_id) + messages = await crud.get_messages_id_range( + db, workspace_name, session_name, peer_name, end_id=cutoff + ) return messages, None async def should_create_summary( - db: AsyncSession, session_id: str, summary_type: SummaryType = SummaryType.SHORT -) -> tuple[bool, list[models.Message], Optional[models.Metamessage]]: + db: AsyncSession, + workspace_name: str, + session_name: str, + peer_name: str, + message_id: int, + summary_type: SummaryType = SummaryType.SHORT, +) -> tuple[bool, list[models.Message], Optional[Summary]]: """ - Determine if a new summary should be created for this session. + Determine if a new summary should be created for this object (peer or session). Args: db: Database session - session_id: The session ID + workspace_name: The workspace name + session_name: The session name summary_type: Type of summary to check for ("short" or "long") Returns: @@ -370,8 +382,13 @@ async def should_create_summary( - List of messages to be included in the summary - The latest summary of the requested type, or None if no summary exists """ - messages, latest_summary = await get_messages_since_latest_summary( - db, session_id, summary_type + messages, latest_summary = await get_latest_summary_and_messages_since( + db, + workspace_name, + session_name, + peer_name, + cutoff=message_id, + summary_type=summary_type, ) threshold = ( MESSAGES_PER_SHORT_SUMMARY @@ -379,15 +396,20 @@ async def should_create_summary( else MESSAGES_PER_LONG_SUMMARY ) should_create = len(messages) >= threshold + logger.debug( + "Should create summary: %s, messages: %s, threshold: %s", + should_create, + len(messages), + threshold, + ) return should_create, messages, latest_summary def format_messages(messages: list[models.Message]) -> str: """ - Format a list of messages into a string. + Format a list of messages into a string by concatenating their content and + prefixing each with the peer name. """ if len(messages) == 0: return "" - return "\n".join( - [f"{'user' if msg.is_user else 'assistant'}: {msg.content}" for msg in messages] - ) + return "\n".join([f"{msg.peer_name}: {msg.content}" for msg in messages]) diff --git a/tests/conftest.py b/tests/conftest.py index 0d111596..51d5e6c9 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -204,16 +204,18 @@ def auth_client(client, request, monkeypatch): async def sample_data(db_session): """Helper function to create test data""" # Create test app - test_app = models.App(name=str(generate_nanoid())) - db_session.add(test_app) + test_workspace = models.Workspace(name=str(generate_nanoid())) + db_session.add(test_workspace) await db_session.flush() # Create test user - test_user = models.User(name=str(generate_nanoid()), app_id=test_app.public_id) - db_session.add(test_user) + test_peer = models.Peer( + name=str(generate_nanoid()), workspace_name=test_workspace.name + ) + db_session.add(test_peer) await db_session.flush() - yield test_app, test_user + yield test_workspace, test_peer await db_session.rollback() @@ -254,3 +256,122 @@ def mock_openai_embeddings(): mock_response.data = [MagicMock(embedding=[0.1] * 1536)] mock_create.return_value = mock_response yield mock_create + + +@pytest.fixture(autouse=True) +def mock_model_client(request): + """Mock ModelClient to avoid needing API keys during tests""" + # Skip mocking for ModelClient unit tests + if "test_model_client" in request.node.name or "test_model_client.py" in str( + request.fspath + ): + yield None + return + + # Create a mock instance + mock_client_instance = MagicMock() + mock_client_instance.generate = AsyncMock(return_value="Test summary content") + mock_client_instance.stream = AsyncMock() + + with ( + patch("src.utils.history.ModelClient") as mock_history_client, + patch("src.deriver.tom.single_prompt.ModelClient") as mock_single_prompt_client, + patch("src.deriver.tom.long_term.ModelClient") as mock_long_term_client, + patch("src.agent.ModelClient") as mock_agent_client, + ): + # Make all class constructors return our mock instance + mock_history_client.return_value = mock_client_instance + mock_single_prompt_client.return_value = mock_client_instance + mock_long_term_client.return_value = mock_client_instance + mock_agent_client.return_value = mock_client_instance + + yield { + "history": mock_history_client, + "single_prompt": mock_single_prompt_client, + "long_term": mock_long_term_client, + "agent": mock_agent_client, + "instance": mock_client_instance, + } + + +@pytest.fixture(autouse=True) +def mock_tracked_db(db_session): + """Mock tracked_db to use the test database session""" + from contextlib import asynccontextmanager + + @asynccontextmanager + async def mock_tracked_db_context(operation_name=None): + yield db_session + + with patch("src.dependencies.tracked_db", mock_tracked_db_context): + yield + + +@pytest.fixture(autouse=True) +def mock_crud_collection_operations(): + """Mock CRUD operations that try to commit to database during tests""" + from nanoid import generate as generate_nanoid + + from src import models + + async def mock_get_or_create_collection( + db, workspace_name, peer_name, collection_name + ): + # Create a mock collection object that doesn't require database commit + mock_collection = models.Collection( + name="honcho", + workspace_name=workspace_name, + peer_name=peer_name, + ) + mock_collection.id = generate_nanoid() + return mock_collection + + with patch( + "src.crud.get_or_create_collection", + mock_get_or_create_collection, + ): + yield + + +@pytest.fixture(autouse=True) +def mock_agent_api_calls(request): + """Mock API calls made by the agent during tests""" + # Mock the agent-specific functions + with ( + patch("src.agent.generate_semantic_queries") as mock_generate_queries, + patch("src.deriver.tom.get_tom_inference") as mock_tom_inference, + patch("src.agent.get_user_representation_long_term") as mock_user_rep, + patch( + "src.deriver.tom.embeddings.CollectionEmbeddingStore.get_relevant_facts" + ) as mock_get_facts, + patch("src.agent.Dialectic.call") as mock_dialectic_call, + patch("src.agent.Dialectic.stream") as mock_dialectic_stream, + ): + # Mock semantic query generation + mock_generate_queries.return_value = ["test query 1", "test query 2"] + + # Mock ToM inference + mock_tom_inference.return_value = ( + "Test prediction about user mental state" + ) + + # Mock user representation generation + mock_user_rep.return_value = ( + "Test user representation" + ) + + # Mock embedding store facts retrieval + mock_get_facts.return_value = ["fact 1", "fact 2", "fact 3"] + + # Mock Dialectic API calls + mock_dialectic_call.return_value = [{"text": "Test dialectic response"}] + mock_dialectic_stream.return_value = AsyncMock() + + yield { + "generate_queries": mock_generate_queries, + "tom_inference": mock_tom_inference, + "user_rep": mock_user_rep, + "get_facts": mock_get_facts, + "dialectic_call": mock_dialectic_call, + "dialectic_stream": mock_dialectic_stream, + } diff --git a/tests/integration/test_enqueue.py b/tests/integration/test_enqueue.py new file mode 100644 index 00000000..8de7617a --- /dev/null +++ b/tests/integration/test_enqueue.py @@ -0,0 +1,599 @@ +from typing import Optional +from unittest.mock import patch + +import pytest +from nanoid import generate as generate_nanoid +from sqlalchemy import select + +from src import crud, models, schemas +from src.models import QueueItem +from src.routers.messages import enqueue + + +@pytest.mark.asyncio +class TestEnqueueFunction: + """Test suite for the enqueue function's internal logic""" + + # Helper methods + def create_sample_payload( + self, + workspace_name="test_workspace", + session_name: Optional[str] = "test_session", + peer_name="test_peer", + count=1, + ): + """Create sample payload for testing""" + return [ + { + "workspace_name": workspace_name, + "session_name": session_name, + "message_id": f"msg_{i}", + "content": f"Test message {i}", + "metadata": {"test": f"value_{i}"}, + "peer_name": peer_name, + } + for i in range(count) + ] + + async def count_queue_items(self, db_session): + """Helper to count queue items in database""" + result = await db_session.execute(select(QueueItem)) + return len(result.scalars().all()) + + # Edge Cases & Input Validation Tests + @pytest.mark.asyncio + async def test_empty_payload_skips_enqueue(self, caplog): + """Test that empty payload list is handled gracefully""" + with caplog.at_level("DEBUG"): + await enqueue([]) + + assert "Empty payload list, skipping enqueue" in caplog.text + + @pytest.mark.asyncio + async def test_malformed_payload_logs_error(self, caplog): + """Test that malformed payload logs appropriate error""" + malformed_payload = [{"incomplete": "data"}] + + with caplog.at_level("ERROR"): + await enqueue(malformed_payload) # Should not raise, but log error + + assert "Failed to enqueue messages: 'workspace_name'" in caplog.text + + # PEER MESSAGE TESTS + + @pytest.mark.asyncio + @patch("src.routers.messages.tracked_db") + async def test_none_session_with_observe_me_true( + self, mock_tracked_db, db_session, sample_data + ): + """Test peer-only processing when observe_me=True""" + mock_tracked_db.return_value.__aenter__.return_value = db_session + test_workspace, test_peer = sample_data + + payload = self.create_sample_payload( + workspace_name=test_workspace.name, + session_name=None, + peer_name=test_peer.name, + count=1, + ) + + initial_count = await self.count_queue_items(db_session) + await enqueue(payload) + final_count = await self.count_queue_items(db_session) + + # Should create 1 queue item (representation) + assert final_count - initial_count == 1 + + # Verify queue items have correct structure + result = await db_session.execute( + select(QueueItem).where(QueueItem.session_id.is_(None)) + ) + queue_items = result.scalars().all() + + for item in queue_items: + payload_data = item.payload + assert payload_data["task_type"] == "representation" + assert payload_data["sender_name"] == test_peer.name + assert payload_data["target_name"] == test_peer.name + + @pytest.mark.asyncio + @patch("src.routers.messages.tracked_db") + async def test_none_session_with_observe_me_false( + self, mock_tracked_db, db_session, sample_data, caplog + ): + """Test peer-only processing when observe_me=False""" + mock_tracked_db.return_value.__aenter__.return_value = db_session + + test_workspace, test_peer = sample_data + + test_peer.configuration = {"observe_me": False} + await db_session.commit() + + payload = self.create_sample_payload( + workspace_name=test_workspace.name, + session_name=None, + peer_name=test_peer.name, + ) + + initial_count = await self.count_queue_items(db_session) + with caplog.at_level("INFO"): + await enqueue(payload) + final_count = await self.count_queue_items(db_session) + + # Should not create any queue items + assert final_count == initial_count + assert ( + f"Peer {test_peer.name} has observe_me=False, skipping enqueue" + in caplog.text + ) + + # SESSION MESSAGES + @pytest.mark.asyncio + @patch("src.routers.messages.tracked_db") + async def test_session_with_deriver_disabled( + self, mock_tracked_db, db_session, sample_data, caplog + ): + """Test that deriver disabled sessions skip enqueue""" + mock_tracked_db.return_value.__aenter__.return_value = db_session + + test_workspace, test_peer = sample_data + + # Create session with deriver disabled + test_session = models.Session( + workspace_name=test_workspace.name, + name=str(generate_nanoid()), + configuration={"deriver_disabled": True}, + ) + db_session.add(test_session) + await db_session.commit() + + payload = self.create_sample_payload( + workspace_name=test_workspace.name, + session_name=test_session.name, + peer_name=test_peer.name, + ) + + initial_count = await self.count_queue_items(db_session) + await enqueue(payload) + final_count = await self.count_queue_items(db_session) + + # we expect 1 queue item for session level summarization + assert final_count == initial_count + 1 + + @pytest.mark.asyncio + @patch("src.routers.messages.tracked_db") + async def test_session_normal_processing_single_peer( + self, mock_tracked_db, db_session, sample_data + ): + mock_tracked_db.return_value.__aenter__.return_value = db_session + + test_workspace, test_peer = sample_data + + test_session = await crud.get_or_create_session( + db_session, + schemas.SessionCreate( + name=str(generate_nanoid()), + peers={test_peer.name: schemas.SessionPeerConfig(observe_me=True)}, + ), + test_workspace.name, + ) + await db_session.commit() + + payload = self.create_sample_payload( + workspace_name=test_workspace.name, + session_name=test_session.name, + peer_name=test_peer.name, + ) + + initial_count = await self.count_queue_items(db_session) + await enqueue(payload) + final_count = await self.count_queue_items(db_session) + + # Should create 1 queue items: 1 representation + assert final_count - initial_count == 1 + + # Verify queue items + result = await db_session.execute( + select(QueueItem).where(QueueItem.session_id == test_session.id) + ) + queue_items = result.scalars().all() + + task_types = [item.payload["task_type"] for item in queue_items] + assert "representation" in task_types + + @pytest.mark.asyncio + @patch("src.routers.messages.tracked_db") + async def test_session_with_multiple_peers_none_observe_others( + self, mock_tracked_db, db_session, sample_data + ): + """Test session processing with multiple peers where some observe others""" + mock_tracked_db.return_value.__aenter__.return_value = db_session + + test_workspace, test_peer1 = sample_data + + # Create second peer + test_peer2 = models.Peer( + workspace_name=test_workspace.name, name=str(generate_nanoid()) + ) + db_session.add(test_peer2) + + test_session = await crud.get_or_create_session( + db_session, + schemas.SessionCreate( + name=str(generate_nanoid()), + peers={ + test_peer1.name: schemas.SessionPeerConfig(), + test_peer2.name: schemas.SessionPeerConfig(), + }, + ), + test_workspace.name, + ) + + await db_session.commit() + + NUM_MESSAGES = 3 + payload = self.create_sample_payload( + workspace_name=test_workspace.name, + session_name=test_session.name, + peer_name=test_peer1.name, + count=NUM_MESSAGES, # Message from peer1 + ) + + initial_count = await self.count_queue_items(db_session) + await enqueue(payload) + final_count = await self.count_queue_items(db_session) + + # Should create NUM_MESSAGES * queue items: + # 1 representation per sender + assert final_count - initial_count == NUM_MESSAGES + + result = await db_session.execute( + select(QueueItem).where(QueueItem.session_id == test_session.id) + ) + queue_items = result.scalars().all() + + # Explicitly match up payloads by sender/target/task_type + # For each message, we expect a representation and a summary queue item + expected_payloads = [] + for _ in range(NUM_MESSAGES): + expected_payloads.append( + { + "sender_name": test_peer1.name, + "target_name": test_peer1.name, + "task_type": "representation", + } + ) + actual_payloads = [ + { + "sender_name": item.payload.get("sender_name"), + "target_name": item.payload.get("target_name"), + "task_type": item.payload.get("task_type"), + } + for item in queue_items + ] + assert len(actual_payloads) == len(expected_payloads) + + # Assert that all expected payloads are present in actual_payloads + for expected in expected_payloads: + assert expected in actual_payloads + + @pytest.mark.asyncio + @patch("src.routers.messages.tracked_db") + async def test_session_with_multiple_peers_all_observe_others( + self, mock_tracked_db, db_session, sample_data + ): + """Test session processing with multiple peers where some observe others""" + mock_tracked_db.return_value.__aenter__.return_value = db_session + + test_workspace, test_peer1 = sample_data + + # Create second peer + test_peer2 = models.Peer( + workspace_name=test_workspace.name, name=str(generate_nanoid()) + ) + db_session.add(test_peer2) + + test_session = await crud.get_or_create_session( + db_session, + schemas.SessionCreate( + name=str(generate_nanoid()), + peers={ + test_peer1.name: schemas.SessionPeerConfig(), + test_peer2.name: schemas.SessionPeerConfig(observe_others=True), + }, + ), + test_workspace.name, + ) + await db_session.commit() + + NUM_MESSAGES = 3 + payload = self.create_sample_payload( + workspace_name=test_workspace.name, + session_name=test_session.name, + peer_name=test_peer1.name, + count=NUM_MESSAGES, # Message from peer1 + ) + + initial_count = await self.count_queue_items(db_session) + await enqueue(payload) + final_count = await self.count_queue_items(db_session) + + # Should create NUM_MESSAGES * 2 queue items: + # 1 representation for sender, 1 local representation for observer + assert final_count - initial_count == NUM_MESSAGES * 2 + + result = await db_session.execute( + select(QueueItem).where(QueueItem.session_id == test_session.id) + ) + queue_items = result.scalars().all() + + # Explicitly match up payloads by sender/target/task_type + # For each message, we expect a representation and a summary queue item + expected_payloads = [] + for _ in range(NUM_MESSAGES): + expected_payloads.append( + { + "sender_name": test_peer1.name, + "target_name": test_peer1.name, + "task_type": "representation", + } + ) + expected_payloads.append( + { + "sender_name": test_peer1.name, + "target_name": test_peer2.name, + "task_type": "representation", + } + ) + actual_payloads = [ + { + "sender_name": item.payload.get("sender_name"), + "target_name": item.payload.get("target_name"), + "task_type": item.payload.get("task_type"), + } + for item in queue_items + ] + assert len(actual_payloads) == len(expected_payloads) + + # Assert that all expected payloads are present in actual_payloads + for expected in expected_payloads: + assert expected in actual_payloads + + @pytest.mark.asyncio + @patch("src.routers.messages.tracked_db") + async def test_session_with_multiple_peers_some_observe_others( + self, mock_tracked_db, db_session, sample_data + ): + """Test session processing with multiple peers where some observe others""" + mock_tracked_db.return_value.__aenter__.return_value = db_session + + test_workspace, test_peer1 = sample_data + + # Create second peer + observing_peer = models.Peer( + workspace_name=test_workspace.name, name=str(generate_nanoid()) + ) + db_session.add(observing_peer) + + # Create third peer + unobserving_peer = models.Peer( + workspace_name=test_workspace.name, name=str(generate_nanoid()) + ) + db_session.add(unobserving_peer) + + # Create session + test_session = await crud.get_or_create_session( + db_session, + schemas.SessionCreate( + name=str(generate_nanoid()), + peers={ + test_peer1.name: schemas.SessionPeerConfig(observe_me=True), + observing_peer.name: schemas.SessionPeerConfig(observe_others=True), + unobserving_peer.name: schemas.SessionPeerConfig(), + }, + ), + test_workspace.name, + ) + await db_session.commit() + + NUM_MESSAGES = 3 + payload = self.create_sample_payload( + workspace_name=test_workspace.name, + session_name=test_session.name, + peer_name=test_peer1.name, + count=NUM_MESSAGES, # Message from peer1 + ) + + initial_count = await self.count_queue_items(db_session) + await enqueue(payload) + final_count = await self.count_queue_items(db_session) + + # Should create NUM_MESSAGES * 2 queue items: + # 1 representation for sender, 1 local representation for 1 peer observer + assert final_count - initial_count == NUM_MESSAGES * 2 + + result = await db_session.execute( + select(QueueItem).where(QueueItem.session_id == test_session.id) + ) + queue_items = result.scalars().all() + + # Explicitly match up payloads by sender/target/task_type + # For each message, we expect a representation and a summary queue item + expected_payloads = [] + for _ in range(NUM_MESSAGES): + expected_payloads.append( + { + "sender_name": test_peer1.name, + "target_name": test_peer1.name, + "task_type": "representation", + } + ) + expected_payloads.append( + { + "sender_name": test_peer1.name, + "target_name": observing_peer.name, + "task_type": "representation", + } + ) + actual_payloads = [ + { + "sender_name": item.payload.get("sender_name"), + "target_name": item.payload.get("target_name"), + "task_type": item.payload.get("task_type"), + } + for item in queue_items + ] + assert len(actual_payloads) == len(expected_payloads) + + # Assert that all expected payloads are present in actual_payloads + for expected in expected_payloads: + assert expected in actual_payloads + + assert unobserving_peer.name not in [ + item.payload.get("target_name") for item in queue_items + ] + + @pytest.mark.asyncio + @patch("src.routers.messages.tracked_db") + async def test_session_peer_config_overrides_peer_config( + self, mock_tracked_db, db_session, sample_data + ): + """Test that session peer config overrides peer config""" + mock_tracked_db.return_value.__aenter__.return_value = db_session + + test_workspace, test_peer = sample_data + + # Set peer configuration to observe_me=True + test_peer.configuration = {"observe_me": True} + + # Create session + test_session = await crud.get_or_create_session( + db_session, + schemas.SessionCreate( + name=str(generate_nanoid()), + peers={test_peer.name: schemas.SessionPeerConfig(observe_me=False)}, + ), + test_workspace.name, + ) + + await db_session.commit() + + payload = self.create_sample_payload( + workspace_name=test_workspace.name, + session_name=test_session.name, + peer_name=test_peer.name, + ) + + initial_count = await self.count_queue_items(db_session) + await enqueue(payload) + final_count = await self.count_queue_items(db_session) + + # Should create 0 queue items + assert final_count - initial_count == 0 + + result = await db_session.execute( + select(QueueItem).where(QueueItem.session_id == test_session.id) + ) + queue_items = result.scalars().all() + assert len(queue_items) == 0 + + @pytest.mark.asyncio + @patch("src.routers.messages.tracked_db") + async def test_multi_sender_scenario( + self, mock_tracked_db, db_session, sample_data + ): + """Test complex scenario with multiple peers and mixed configurations""" + mock_tracked_db.return_value.__aenter__.return_value = db_session + + test_workspace, test_peer1 = sample_data + + # Create additional peers + additional_sender_peer = models.Peer( + workspace_name=test_workspace.name, name=str(generate_nanoid()) + ) + + observer_peer = models.Peer( + workspace_name=test_workspace.name, name=str(generate_nanoid()) + ) + + db_session.add_all([additional_sender_peer, observer_peer]) + + test_session = await crud.get_or_create_session( + db_session, + schemas.SessionCreate( + name=str(generate_nanoid()), + peers={ + test_peer1.name: schemas.SessionPeerConfig(), + additional_sender_peer.name: schemas.SessionPeerConfig(), + observer_peer.name: schemas.SessionPeerConfig(observe_others=True), + }, + ), + test_workspace.name, + ) + await db_session.commit() + + payload1 = self.create_sample_payload( + workspace_name=test_workspace.name, + session_name=test_session.name, + peer_name=test_peer1.name, + ) + + payload2 = self.create_sample_payload( + workspace_name=test_workspace.name, + session_name=test_session.name, + peer_name=additional_sender_peer.name, + ) + + payload = payload1 + payload2 + + initial_count = await self.count_queue_items(db_session) + await enqueue(payload) + final_count = await self.count_queue_items(db_session) + + # Should create 4 queue items: + # 1 representation for test_peer1 (sender) and observer_peer (target) + # 1 representation for additional_sender_peer (sender) and observer_peer (target) + assert final_count - initial_count == 4 + + # Verify the correct representations were created + result = await db_session.execute( + select(QueueItem).where(QueueItem.session_id == test_session.id) + ) + queue_items = result.all() + + # Build expected payloads for this scenario + expected_payloads = [] + # 2 messages: one from test_peer1, one from additional_sender_peer + for sender in [test_peer1.name, additional_sender_peer.name]: + # representation for sender (self) + expected_payloads.append( + { + "task_type": "representation", + "sender_name": sender, + "target_name": sender, + } + ) + # representation for observer_peer (observe_others=True) + expected_payloads.append( + { + "task_type": "representation", + "sender_name": sender, + "target_name": observer_peer.name, + } + ) + + # Extract actual payloads (task_type, sender_name, target_name) from queue_items + actual_payloads = [ + { + "task_type": item[0].payload.get("task_type"), + "sender_name": item[0].payload.get("sender_name"), + "target_name": item[0].payload.get("target_name"), + } + for item in queue_items + ] + + assert len(actual_payloads) == len(expected_payloads) + + # For each expected payload, assert it is present in actual_payloads + for expected in expected_payloads: + assert expected in actual_payloads diff --git a/tests/routes/test_apps.py b/tests/routes/test_apps.py deleted file mode 100644 index a8569655..00000000 --- a/tests/routes/test_apps.py +++ /dev/null @@ -1,151 +0,0 @@ -import pytest -from nanoid import generate as generate_nanoid - - -def test_create_app(client): - name = str(generate_nanoid()) - response = client.post( - "/v1/apps", json={"name": name, "metadata": {"key": "value"}} - ) - print(response) - assert response.status_code == 200 - data = response.json() - print("===================") - print(data) - print("===================") - assert data["name"] == name - assert data["metadata"] == {"key": "value"} - assert "id" in data - - -def test_create_app_no_metadata(client): - name = str(generate_nanoid()) - response = client.post("/v1/apps", json={"name": name}) - print(response) - assert response.status_code == 200 - data = response.json() - print("===================") - print(data) - print("===================") - assert data["name"] == name - assert data["metadata"] == {} - assert "id" in data - - -def test_get_or_create_app(client): - name = str(generate_nanoid()) - # Should return a ResourceNotFoundException with 404 status - response = client.get(f"/v1/apps/name/{name}") - assert response.status_code == 404 - assert "detail" in response.json() - - # This should create the app - response = client.get(f"/v1/apps/get_or_create/{name}") - assert response.status_code == 200 - data = response.json() - assert data["name"] == name - assert "id" in data - - -def test_get_or_create_existing_app(client): - name = str(generate_nanoid()) - - # App doesn't exist yet - response = client.get(f"/v1/apps/name/{name}") - assert response.status_code == 404 - - # Create the app - response = client.post( - "/v1/apps", json={"name": name, "metadata": {"key": "value"}} - ) - assert response.status_code == 200 - app1 = response.json() - - # Now get_or_create should find the existing app - response = client.get(f"/v1/apps/get_or_create/{name}") - assert response.status_code == 200 - app2 = response.json() - - # Both should be the same app - assert app1["name"] == app2["name"] - assert app1["id"] == app2["id"] - assert app1["metadata"] == app2["metadata"] - - -def test_get_app_by_id(client, sample_data): - test_app, _ = sample_data - response = client.get(f"/v1/apps?app_id={test_app.public_id}") - assert response.status_code == 200 - data = response.json() - assert data["name"] == test_app.name - assert data["id"] == str(test_app.public_id) - - -@pytest.mark.asyncio -async def test_get_all_apps(client, db_session, sample_data): - test_app, test_user = sample_data - - # create a test app with metadata - response = client.post( - "/v1/apps", - json={ - "name": "test_app", - "metadata": {"test_key": "test_value"}, - }, - ) - - response = client.post( - "/v1/apps/list", - json={}, - ) - assert response.status_code == 200 - data = response.json() - assert "items" in data - assert len(data["items"]) > 0 - - response = client.post( - "/v1/apps/list", - json={"filter": {"test_key": "test_value"}}, - ) - assert response.status_code == 200 - data = response.json() - assert "items" in data - assert len(data["items"]) > 0 - assert data["items"][0]["metadata"] == {"test_key": "test_value"} - - -def test_get_app_by_name(client, sample_data): - test_app, _ = sample_data - response = client.get(f"/v1/apps/name/{test_app.name}") - assert response.status_code == 200 - data = response.json() - assert data["name"] == test_app.name - assert data["id"] == str(test_app.public_id) - - -def test_update_app(client, sample_data): - test_app, _ = sample_data - new_name = str(generate_nanoid()) - response = client.put( - f"/v1/apps/{test_app.public_id}", - json={"name": new_name, "metadata": {"new_key": "new_value"}}, - ) - assert response.status_code == 200 - data = response.json() - assert data["name"] == new_name - assert data["metadata"] == {"new_key": "new_value"} - - -def test_create_duplicate_app_name(client): - # Create an app - name = str(generate_nanoid()) - response = client.post("/v1/apps", json={"name": name}) - assert response.status_code == 200 - - # Try to create another app with the same name - response = client.post("/v1/apps", json={"name": name}) - - # Should get a ConflictException with 409 status - assert response.status_code == 409 - data = response.json() - assert "detail" in data diff --git a/tests/routes/test_collections.py b/tests/routes/test_collections.py deleted file mode 100644 index 5464129a..00000000 --- a/tests/routes/test_collections.py +++ /dev/null @@ -1,185 +0,0 @@ -import pytest -from nanoid import generate as generate_nanoid - - -def test_create_collection(client, sample_data) -> None: - test_app, test_user = sample_data - response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections", - json={"name": "test_collection", "metadata": {}}, - ) - assert response.status_code == 200 - data = response.json() - assert data["name"] == "test_collection" - assert data["metadata"] == {} - assert "id" in data - - -def test_get_collection_by_id(client, sample_data) -> None: - test_app, test_user = sample_data - # Make the collection - response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections", - json={"name": "test_collection", "metadata": {}}, - ) - assert response.status_code == 200 - data = response.json() - # Get the collection - response = client.get( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections?collection_id={data['id']}" - ) - assert response.status_code == 200 - data = response.json() - assert data["name"] == "test_collection" - assert data["metadata"] == {} - assert "id" in data - - -def test_get_collection_by_name(client, sample_data) -> None: - test_app, test_user = sample_data - # Make the collection - response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections", - json={"name": "test_collection", "metadata": {}}, - ) - assert response.status_code == 200 - data = response.json() - # Get the collection - response = client.get( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections/name/test_collection" - ) - assert response.status_code == 200 - data = response.json() - assert data["name"] == "test_collection" - assert data["metadata"] == {} - assert "id" in data - - -def test_get_collections(client, sample_data) -> None: - test_app, test_user = sample_data - # Make Sample Collections - client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections", - json={"name": str(generate_nanoid()), "metadata": {"test": "key"}}, - ) - client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections", - json={"name": str(generate_nanoid()), "metadata": {"test": "key"}}, - ) - client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections", - json={"name": str(generate_nanoid()), "metadata": {"test": "key2"}}, - ) - - # Get the Collections - response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections/list", - json={}, - ) - assert response.status_code == 200 - data = response.json() - assert len(data["items"]) == 3 - - response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections/list", - json={"filter": {"test": "key"}}, - ) - - assert response.status_code == 200 - data = response.json() - assert len(data["items"]) == 2 - assert data["items"][0]["metadata"] == {"test": "key"} - assert data["items"][1]["metadata"] == {"test": "key"} - - -def test_update_collection(client, sample_data) -> None: - test_app, test_user = sample_data - # Make the collection - response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections", - json={"name": "test_collection", "metadata": {}}, - ) - assert response.status_code == 200 - data = response.json() - # Update the collection - response = client.put( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections/{data['id']}", - json={"name": "test_collection_updated", "metadata": {"new_key": "new_value"}}, - ) - assert response.status_code == 200 - data = response.json() - assert data["name"] == "test_collection_updated" - assert data["metadata"] == {"new_key": "new_value"} - assert "id" in data - - -def test_delete_collection(client, sample_data) -> None: - test_app, test_user = sample_data - # Make the collection - response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections", - json={"name": "test_collection", "metadata": {}}, - ) - assert response.status_code == 200 - data = response.json() - # Delete the collection - response = client.delete( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections/{data['id']}" - ) - assert response.status_code == 200 - response = client.get( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections?collection_id={data['id']}" - ) - assert response.status_code == 404 - - -@pytest.mark.asyncio -async def test_cannot_delete_honcho_collection(client, sample_data, db_session) -> None: - test_app, test_user = sample_data - # Make the protected "honcho" collection - response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections", - json={"name": "honcho", "metadata": {}}, - ) - assert response.status_code == 422 # Should get validation error when trying to create - - # Try to create it directly through API to test delete protection - response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections", - json={"name": "test_collection", "metadata": {}}, - ) - assert response.status_code == 200 - data = response.json() - collection_id = data["id"] - - # Update the collection to have the reserved name - response = client.put( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections/{collection_id}", - json={"name": "honcho", "metadata": {}}, - ) - assert response.status_code == 422 # Should get validation error - - # Create the protected collection using the internal method directly with db_session - from src.crud import create_user_protected_collection - - # Create the protected collection - honcho_collection = await create_user_protected_collection( - db_session, app_id=test_app.public_id, user_id=test_user.public_id - ) - await db_session.flush() - - # Get the protected collection - response = client.get( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections/name/honcho" - ) - assert response.status_code == 200 - data = response.json() - assert data["name"] == "honcho" - protected_id = data["id"] - - # Try to delete the protected collection - response = client.delete( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections/{protected_id}" - ) - assert response.status_code == 422 # Should get validation error - assert "reserved name" in response.json()["detail"].lower() diff --git a/tests/routes/test_documents.py b/tests/routes/test_documents.py deleted file mode 100644 index fa801a67..00000000 --- a/tests/routes/test_documents.py +++ /dev/null @@ -1,261 +0,0 @@ -import pytest -from nanoid import generate as generate_nanoid - - -def test_update_document_validation_error(client, sample_data): - test_app, test_user = sample_data - # Create a collection - response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections", - json={"name": "test_collection", "metadata": {}}, - ) - assert response.status_code == 200 - collection = response.json() - - # Create a document - response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections/{collection['id']}/documents", - json={"content": "test_text", "metadata": {}}, - ) - assert response.status_code == 200 - document = response.json() - - # Try to update the document with empty content and metadata - response = client.put( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections/{collection['id']}/documents/{document['id']}", - json={"content": None, "metadata": None}, - ) - - # Should get a ValidationException with 422 status - assert response.status_code == 422 - data = response.json() - assert "detail" in data - - -def test_create_document(client, sample_data): - test_app, test_user = sample_data - # Create a collection - response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections", - json={"name": "test_collection", "metadata": {}}, - ) - assert response.status_code == 200 - data = response.json() - # Create a document - response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections/{data['id']}/documents", - json={"content": "test_text", "metadata": {}}, - ) - assert response.status_code == 200 - data = response.json() - assert data["content"] == "test_text" - assert data["metadata"] == {} - assert "id" in data - - -def test_get_document(client, sample_data): - test_app, test_user = sample_data - # Create a collection - response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections", - json={"name": "test_collection", "metadata": {}}, - ) - assert response.status_code == 200 - collection = response.json() - # Create a document - response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections/{collection['id']}/documents", - json={"content": "test_text", "metadata": {}}, - ) - assert response.status_code == 200 - document = response.json() - # Get the document - response = client.get( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections/{collection['id']}/documents/{document['id']}" - ) - assert response.status_code == 200 - data = response.json() - assert data["content"] == "test_text" - assert data["metadata"] == {} - assert "id" in data - - -def test_get_documents(client, sample_data): - test_app, test_user = sample_data - # Create a collection - response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections", - json={"name": str(generate_nanoid()), "metadata": {}}, - ) - assert response.status_code == 200 - collection = response.json() - # Create a document - response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections/{collection['id']}/documents", - json={"content": "test_text", "metadata": {"test": "key"}}, - ) - response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections/{collection['id']}/documents", - json={"content": "test_text", "metadata": {"test": "key"}}, - ) - response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections/{collection['id']}/documents", - json={"content": "test_text", "metadata": {"test": "key2"}}, - ) - # Get the documents - response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections/{collection['id']}/documents/list", - json={}, - ) - assert response.status_code == 200 - data = response.json() - assert len(data["items"]) == 3 - - response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections/{collection['id']}/documents/list", - json={"filter": {"test": "key"}}, - ) - assert response.status_code == 200 - data = response.json() - assert len(data["items"]) == 2 - assert data["items"][0]["metadata"]["test"] == "key" - assert data["items"][1]["metadata"]["test"] == "key" - - -def test_query_documents(client, sample_data): - test_app, test_user = sample_data - # Create a collection - response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections", - json={"name": str(generate_nanoid()), "metadata": {}}, - ) - assert response.status_code == 200 - collection = response.json() - # Create a document - response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections/{collection['id']}/documents", - json={"content": "test_text", "metadata": {"test": "key"}}, - ) - response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections/{collection['id']}/documents", - json={"content": "test_text", "metadata": {"test": "key"}}, - ) - response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections/{collection['id']}/documents", - json={"content": "test_text", "metadata": {"test": "key2"}}, - ) - # Get the documents - response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections/{collection['id']}/documents/query", - json={"query": "test"}, - ) - assert response.status_code == 200 - data = response.json() - print("=====================") - print(data) - print("=====================") - assert len(data) == 3 - - response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections/{collection['id']}/documents/query", - json={"query": "test", "filter": {"test": "key"}}, - ) - assert response.status_code == 200 - data = response.json() - assert len(data) == 2 - assert data[0]["metadata"]["test"] == "key" - assert data[1]["metadata"]["test"] == "key" - - -def test_update_document(client, sample_data): - test_app, test_user = sample_data - # Create a collection - response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections", - json={"name": "test_collection", "metadata": {}}, - ) - assert response.status_code == 200 - data = response.json() - # Create a document - response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections/{data['id']}/documents", - json={"content": "test_text", "metadata": {}}, - ) - assert response.status_code == 200 - data = response.json() - # Update the document - response = client.put( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections/{data['id']}/documents/{data['id']}", - json={"content": "test_text_updated", "metadata": {"new_key": "new_value"}}, - ) - - -def test_delete_document(client, sample_data): - test_app, test_user = sample_data - # Create a collection - response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections", - json={"name": "test_collection", "metadata": {}}, - ) - assert response.status_code == 200 - collection = response.json() - # Create a document - response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections/{collection['id']}/documents", - json={"content": "test_text", "metadata": {}}, - ) - assert response.status_code == 200 - document = response.json() - # Delete the document - response = client.delete( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections/{collection['id']}/documents/{document['id']}" - ) - assert response.status_code == 200 - response = client.get( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections/{collection['id']}/documents/{document['id']}" - ) - assert response.status_code == 404 - - -@pytest.mark.asyncio -async def test_cannot_delete_documents_in_honcho_collection(client, sample_data, db_session): - test_app, test_user = sample_data - - # Create the protected collection using the internal method directly with db_session - from src.crud import create_user_protected_collection - - # Create the protected honcho collection - honcho_collection = await create_user_protected_collection( - db_session, app_id=test_app.public_id, user_id=test_user.public_id - ) - await db_session.flush() - - # Get the protected collection - response = client.get( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections/name/honcho" - ) - assert response.status_code == 200 - collection = response.json() - assert collection["name"] == "honcho" - - # Create a document in the protected collection - response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections/{collection['id']}/documents", - json={"content": "protected document", "metadata": {}}, - ) - assert response.status_code == 200 - document = response.json() - - # Try to delete the document from the protected collection - response = client.delete( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections/{collection['id']}/documents/{document['id']}" - ) - assert response.status_code == 422 # Should get validation error - assert "honcho" in response.json()["detail"].lower() - - # The document should still exist - response = client.get( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections/{collection['id']}/documents/{document['id']}" - ) - assert response.status_code == 200 - assert response.json()["content"] == "protected document" diff --git a/tests/routes/test_keys.py b/tests/routes/test_keys.py index d7690e46..487b0bfb 100644 --- a/tests/routes/test_keys.py +++ b/tests/routes/test_keys.py @@ -12,20 +12,22 @@ def test_create_key_no_params(auth_client): def test_create_key_with_params(auth_client, sample_data): """Test creating a key with specific parameters""" - test_app, test_user = sample_data + test_workspace, test_peer = sample_data if auth_client.auth_type != "admin": return # Skip test if not admin authentication # Test with app_id - response = auth_client.post("/v1/keys", params={"app_id": test_app.public_id}) + response = auth_client.post( + "/v1/keys", params={"workspace_id": test_workspace.name} + ) assert response.status_code == 200 assert "key" in response.json() # Test with app_id and user_id response = auth_client.post( "/v1/keys", - params={"app_id": test_app.public_id, "user_id": test_user.public_id}, + params={"workspace_id": test_workspace.name, "peer_id": test_peer.name}, ) assert response.status_code == 200 assert "key" in response.json() @@ -34,8 +36,8 @@ def test_create_key_with_params(auth_client, sample_data): response = auth_client.post( "/v1/keys", params={ - "app_id": test_app.public_id, - "user_id": test_user.public_id, + "workspace_id": test_workspace.name, + "peer_id": test_peer.name, "session_id": "test-session", "collection_id": "test-collection", }, @@ -56,8 +58,10 @@ def test_create_key_with_expires_at(auth_client, sample_data): else: assert response.status_code == 401 - test_app, _ = sample_data + test_workspace, _ = sample_data # assert that the key is expired - response = auth_client.post("/v1/keys", params={"app_id": test_app.public_id}) + response = auth_client.post( + "/v1/keys", params={"workspace_id": test_workspace.name} + ) assert response.status_code == 401 diff --git a/tests/routes/test_messages.py b/tests/routes/test_messages.py index 3328269e..72876884 100644 --- a/tests/routes/test_messages.py +++ b/tests/routes/test_messages.py @@ -1,47 +1,170 @@ import pytest +from nanoid import generate as generate_nanoid from src import models # Import your SQLAlchemy models @pytest.mark.asyncio async def test_create_message(client, db_session, sample_data): - test_app, test_user = sample_data + test_workspace, test_peer = sample_data + # Create a test session - test_session = models.Session(user_id=test_user.public_id, app_id=test_app.public_id) + test_session = models.Session( + workspace_name=test_workspace.name, name=str(generate_nanoid()) + ) db_session.add(test_session) await db_session.commit() response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions/{test_session.public_id}/messages", + f"/v1/workspaces/{test_workspace.name}/sessions/{test_session.name}/messages", json={ - "content": "Test message", - "is_user": True, - "metadata": {"message_key": "message_value"}, + "messages": [ + { + "content": "Test message", + "peer_id": test_peer.name, + "metadata": {"message_key": "message_value"}, + } + ] }, ) assert response.status_code == 200 data = response.json() - assert data["content"] == "Test message" - assert data["is_user"] is True - assert data["metadata"] == {"message_key": "message_value"} - assert "id" in data + assert len(data) == 1 + message = data[0] + assert message["content"] == "Test message" + assert message["peer_id"] == test_peer.name + assert message["session_id"] == test_session.name + assert message["metadata"] == {"message_key": "message_value"} + assert "id" in message + + +@pytest.mark.asyncio +async def test_create_batch_messages_with_metadata(client, db_session, sample_data): + """Test batch message creation with metadata for each message""" + test_workspace, test_peer = sample_data + + # Create a test session + test_session = models.Session( + workspace_name=test_workspace.name, name=str(generate_nanoid()) + ) + db_session.add(test_session) + await db_session.commit() + + response = client.post( + f"/v1/workspaces/{test_workspace.name}/sessions/{test_session.name}/messages", + json={ + "messages": [ + { + "content": "Message 1", + "peer_id": test_peer.name, + "metadata": {"type": "question", "priority": "high"}, + }, + { + "content": "Message 2", + "peer_id": test_peer.name, + "metadata": {"type": "answer", "priority": "low"}, + }, + ] + }, + ) + assert response.status_code == 200 + data = response.json() + assert len(data) == 2 + + # Check first message + assert data[0]["content"] == "Message 1" + assert data[0]["metadata"] == {"type": "question", "priority": "high"} + + # Check second message + assert data[1]["content"] == "Message 2" + assert data[1]["metadata"] == {"type": "answer", "priority": "low"} + + +@pytest.mark.asyncio +async def test_create_batch_messages_without_metadata(client, db_session, sample_data): + """Test batch message creation without metadata (should default to empty dict)""" + test_workspace, test_peer = sample_data + + # Create a test session + test_session = models.Session( + workspace_name=test_workspace.name, name=str(generate_nanoid()) + ) + db_session.add(test_session) + await db_session.commit() + + response = client.post( + f"/v1/workspaces/{test_workspace.name}/sessions/{test_session.name}/messages", + json={ + "messages": [ + { + "content": "Message without metadata", + "peer_id": test_peer.name, + } + ] + }, + ) + assert response.status_code == 200 + data = response.json() + assert len(data) == 1 + assert data[0]["content"] == "Message without metadata" + assert data[0]["metadata"] == {} + + +@pytest.mark.asyncio +async def test_create_batch_messages_with_null_metadata( + client, db_session, sample_data +): + """Test batch message creation with null metadata""" + test_workspace, test_peer = sample_data + + # Create a test session + test_session = models.Session( + workspace_name=test_workspace.name, name=str(generate_nanoid()) + ) + db_session.add(test_session) + await db_session.commit() + + response = client.post( + f"/v1/workspaces/{test_workspace.name}/sessions/{test_session.name}/messages", + json={ + "messages": [ + { + "content": "Message with null metadata", + "peer_id": test_peer.name, + "metadata": None, + } + ] + }, + ) + assert response.status_code == 200 + data = response.json() + assert len(data) == 1 + assert data[0]["content"] == "Message with null metadata" + assert data[0]["metadata"] == {} @pytest.mark.asyncio async def test_get_messages(client, db_session, sample_data): - test_app, test_user = sample_data + test_workspace, test_peer = sample_data + # Create a test session and message - test_session = models.Session(user_id=test_user.public_id, app_id=test_app.public_id) + test_session = models.Session( + workspace_name=test_workspace.name, name=str(generate_nanoid()) + ) db_session.add(test_session) await db_session.commit() + test_message = models.Message( - session_id=test_session.public_id, content="Test message", is_user=True, app_id=test_app.public_id, user_id=test_user.public_id + session_name=test_session.name, + content="Test message", + workspace_name=test_workspace.name, + peer_name=test_peer.name, ) db_session.add(test_message) await db_session.commit() response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions/{test_session.public_id}/messages/list", + f"/v1/workspaces/{test_workspace.name}/sessions/{test_session.name}/messages/list", json={}, ) assert response.status_code == 200 @@ -49,31 +172,180 @@ async def test_get_messages(client, db_session, sample_data): assert "items" in data assert len(data["items"]) > 0 assert data["items"][0]["content"] == "Test message" - assert data["items"][0]["is_user"] is True + assert data["items"][0]["peer_id"] == test_peer.name + assert data["items"][0]["session_id"] == test_session.name assert data["items"][0]["metadata"] == {} @pytest.mark.asyncio -async def test_get_filtered_messages(client, db_session, sample_data): - test_app, test_user = sample_data - # Create a test session and message - test_session = models.Session(user_id=test_user.public_id, app_id=test_app.public_id) +async def test_get_messages_with_reverse(client, db_session, sample_data): + """Test getting messages with reverse parameter""" + test_workspace, test_peer = sample_data + + # Create a test session + test_session = models.Session( + workspace_name=test_workspace.name, name=str(generate_nanoid()) + ) db_session.add(test_session) await db_session.commit() + + # Create multiple messages to test ordering + test_message1 = models.Message( + session_name=test_session.name, + content="First message", + workspace_name=test_workspace.name, + peer_name=test_peer.name, + ) + test_message2 = models.Message( + session_name=test_session.name, + content="Second message", + workspace_name=test_workspace.name, + peer_name=test_peer.name, + ) + db_session.add(test_message1) + db_session.add(test_message2) + await db_session.commit() + + # Test normal order + response = client.post( + f"/v1/workspaces/{test_workspace.name}/sessions/{test_session.name}/messages/list", + json={}, + ) + assert response.status_code == 200 + normal_data = response.json() + + # Test reversed order + response = client.post( + f"/v1/workspaces/{test_workspace.name}/sessions/{test_session.name}/messages/list?reverse=true", + json={}, + ) + assert response.status_code == 200 + reversed_data = response.json() + + # Both should have items + assert len(normal_data["items"]) >= 2 + assert len(reversed_data["items"]) >= 2 + + # Order should be different (first item in normal should be last in reversed) + if len(normal_data["items"]) > 1 and len(reversed_data["items"]) > 1: + assert normal_data["items"][0]["id"] != reversed_data["items"][0]["id"] + + +@pytest.mark.asyncio +async def test_get_messages_with_empty_filter(client, db_session, sample_data): + """Test getting messages with empty filter object""" + test_workspace, test_peer = sample_data + + # Create a test session and message + test_session = models.Session( + workspace_name=test_workspace.name, name=str(generate_nanoid()) + ) + db_session.add(test_session) + await db_session.commit() + test_message = models.Message( - session_id=test_session.public_id, + session_name=test_session.name, content="Test message", - is_user=True, - app_id=test_app.public_id, - user_id=test_user.public_id, + workspace_name=test_workspace.name, + peer_name=test_peer.name, + ) + db_session.add(test_message) + await db_session.commit() + + response = client.post( + f"/v1/workspaces/{test_workspace.name}/sessions/{test_session.name}/messages/list", + json={"filter": {}}, + ) + assert response.status_code == 200 + data = response.json() + assert "items" in data + assert isinstance(data["items"], list) + + +@pytest.mark.asyncio +async def test_get_messages_with_null_filter(client, db_session, sample_data): + """Test getting messages with null filter""" + test_workspace, test_peer = sample_data + + # Create a test session and message + test_session = models.Session( + workspace_name=test_workspace.name, name=str(generate_nanoid()) + ) + db_session.add(test_session) + await db_session.commit() + + test_message = models.Message( + session_name=test_session.name, + content="Test message", + workspace_name=test_workspace.name, + peer_name=test_peer.name, + ) + db_session.add(test_message) + await db_session.commit() + + response = client.post( + f"/v1/workspaces/{test_workspace.name}/sessions/{test_session.name}/messages/list", + json={"filter": None}, + ) + assert response.status_code == 200 + data = response.json() + assert "items" in data + assert isinstance(data["items"], list) + + +@pytest.mark.asyncio +async def test_get_messages_no_body(client, db_session, sample_data): + """Test getting messages without any request body""" + test_workspace, test_peer = sample_data + + # Create a test session and message + test_session = models.Session( + workspace_name=test_workspace.name, name=str(generate_nanoid()) + ) + db_session.add(test_session) + await db_session.commit() + + test_message = models.Message( + session_name=test_session.name, + content="Test message", + workspace_name=test_workspace.name, + peer_name=test_peer.name, + ) + db_session.add(test_message) + await db_session.commit() + + response = client.post( + f"/v1/workspaces/{test_workspace.name}/sessions/{test_session.name}/messages/list" + ) + assert response.status_code == 200 + data = response.json() + assert "items" in data + assert isinstance(data["items"], list) + + +@pytest.mark.asyncio +async def test_get_filtered_messages(client, db_session, sample_data): + test_workspace, test_peer = sample_data + + # Create a test session and messages + test_session = models.Session( + workspace_name=test_workspace.name, name=str(generate_nanoid()) + ) + db_session.add(test_session) + await db_session.commit() + + test_message = models.Message( + session_name=test_session.name, + content="Test message", + workspace_name=test_workspace.name, + peer_name=test_peer.name, h_metadata={"key": "value"}, ) test_message2 = models.Message( - session_id=test_session.public_id, - content="Test message", - is_user=True, - app_id=test_app.public_id, - user_id=test_user.public_id, + session_name=test_session.name, + content="Test message 2", + workspace_name=test_workspace.name, + peer_name=test_peer.name, h_metadata={"key": "value2"}, ) db_session.add(test_message) @@ -81,34 +353,93 @@ async def test_get_filtered_messages(client, db_session, sample_data): await db_session.commit() response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions/{test_session.public_id}/messages/list", + f"/v1/workspaces/{test_workspace.name}/sessions/{test_session.name}/messages/list", json={"filter": {"key": "value2"}}, ) assert response.status_code == 200 data = response.json() - print(data) assert "items" in data assert len(data["items"]) > 0 - assert data["items"][0]["content"] == "Test message" - assert data["items"][0]["is_user"] is True + assert data["items"][0]["content"] == "Test message 2" + assert data["items"][0]["peer_id"] == test_peer.name + assert data["items"][0]["session_id"] == test_session.name assert data["items"][0]["metadata"] == {"key": "value2"} @pytest.mark.asyncio -async def test_update_message(client, db_session, sample_data): - test_app, test_user = sample_data - # Create a test session and message - test_session = models.Session(user_id=test_user.public_id, app_id=test_app.public_id) +async def test_get_filtered_messages_with_complex_filter( + client, db_session, sample_data +): + """Test getting messages with complex metadata filter""" + test_workspace, test_peer = sample_data + + # Create a test session and messages + test_session = models.Session( + workspace_name=test_workspace.name, name=str(generate_nanoid()) + ) db_session.add(test_session) await db_session.commit() + + test_message1 = models.Message( + session_name=test_session.name, + content="Message 1", + workspace_name=test_workspace.name, + peer_name=test_peer.name, + h_metadata={"type": "question", "priority": "high", "category": "technical"}, + ) + test_message2 = models.Message( + session_name=test_session.name, + content="Message 2", + workspace_name=test_workspace.name, + peer_name=test_peer.name, + h_metadata={"type": "answer", "priority": "high", "category": "technical"}, + ) + test_message3 = models.Message( + session_name=test_session.name, + content="Message 3", + workspace_name=test_workspace.name, + peer_name=test_peer.name, + h_metadata={"type": "question", "priority": "low", "category": "general"}, + ) + db_session.add(test_message1) + db_session.add(test_message2) + db_session.add(test_message3) + await db_session.commit() + + # Filter by multiple criteria + response = client.post( + f"/v1/workspaces/{test_workspace.name}/sessions/{test_session.name}/messages/list", + json={"filter": {"priority": "high", "category": "technical"}}, + ) + assert response.status_code == 200 + data = response.json() + assert "items" in data + # Should return messages 1 and 2 (both have high priority and technical category) + assert len(data["items"]) >= 2 + + +@pytest.mark.asyncio +async def test_update_message(client, db_session, sample_data): + test_workspace, test_peer = sample_data + + # Create a test session and message + test_session = models.Session( + workspace_name=test_workspace.name, name=str(generate_nanoid()) + ) + db_session.add(test_session) + await db_session.commit() + test_message = models.Message( - session_id=test_session.public_id, content="Test message", is_user=True, app_id=test_app.public_id, user_id=test_user.public_id + session_name=test_session.name, + content="Test message", + workspace_name=test_workspace.name, + peer_name=test_peer.name, ) db_session.add(test_message) await db_session.commit() response = client.put( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions/{test_session.public_id}/messages/{test_message.public_id}", + f"/v1/workspaces/{test_workspace.name}/sessions/{test_session.name}/messages/{test_message.public_id}", json={"metadata": {"new_key": "new_value"}}, ) assert response.status_code == 200 @@ -117,101 +448,271 @@ async def test_update_message(client, db_session, sample_data): @pytest.mark.asyncio -async def test_update_message_empty_metadata(client, db_session, sample_data): - test_app, test_user = sample_data +async def test_update_message_with_complex_metadata(client, db_session, sample_data): + """Test updating message with complex metadata structure""" + test_workspace, test_peer = sample_data + # Create a test session and message - test_session = models.Session(user_id=test_user.public_id, app_id=test_app.public_id) + test_session = models.Session( + workspace_name=test_workspace.name, name=str(generate_nanoid()) + ) db_session.add(test_session) await db_session.commit() + test_message = models.Message( - session_id=test_session.public_id, content="Test message", is_user=True, app_id=test_app.public_id, user_id=test_user.public_id + session_name=test_session.name, + content="Test message", + workspace_name=test_workspace.name, + peer_name=test_peer.name, + ) + db_session.add(test_message) + await db_session.commit() + + complex_metadata = { + "tags": ["important", "follow-up"], + "score": 8.5, + "nested": {"category": "technical", "subcategory": "api"}, + "processed": True, + } + + response = client.put( + f"/v1/workspaces/{test_workspace.name}/sessions/{test_session.name}/messages/{test_message.public_id}", + json={"metadata": complex_metadata}, + ) + assert response.status_code == 200 + data = response.json() + assert data["metadata"] == complex_metadata + + +@pytest.mark.asyncio +async def test_update_message_empty_metadata(client, db_session, sample_data): + # note that this should not change the metadata of the message. + # this test is to ensure that the metadata is not changed when it is set to None. + + test_workspace, test_peer = sample_data + + # Create a test session and message + test_session = models.Session( + workspace_name=test_workspace.name, name=str(generate_nanoid()) + ) + db_session.add(test_session) + await db_session.commit() + + test_message = models.Message( + session_name=test_session.name, + content="Test message", + workspace_name=test_workspace.name, + peer_name=test_peer.name, + h_metadata={"test_key": "test_value"}, ) db_session.add(test_message) await db_session.commit() response = client.put( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions/{test_session.public_id}/messages/{test_message.public_id}", + f"/v1/workspaces/{test_workspace.name}/sessions/{test_session.name}/messages/{test_message.public_id}", json={"metadata": None}, ) - assert response.status_code == 422 + assert response.status_code == 200 + + # now ensure that the metadata is not changed + response = client.get( + f"/v1/workspaces/{test_workspace.name}/sessions/{test_session.name}/messages/{test_message.public_id}" + ) + assert response.status_code == 200 data = response.json() - print(data) - # assert data["detail"] == "Message metadata cannot be empty" + assert data["metadata"] == {"test_key": "test_value"} @pytest.mark.asyncio -async def test_create_batch_messages(client, db_session, sample_data): - test_app, test_user = sample_data - # Create a test session - test_session = models.Session(user_id=test_user.public_id, app_id=test_app.public_id) +async def test_update_message_with_empty_dict_metadata(client, db_session, sample_data): + """Test updating message with empty dictionary metadata""" + test_workspace, test_peer = sample_data + + # Create a test session and message + test_session = models.Session( + workspace_name=test_workspace.name, name=str(generate_nanoid()) + ) db_session.add(test_session) await db_session.commit() - # Create batch of test messages - test_messages = { - "messages": [ - { - "content": f"Test message {i}", - "is_user": i % 2 == 0, # Alternating user/non-user messages - "metadata": {"batch_index": i}, - } - for i in range(3) - ] - } - - response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions/{test_session.public_id}/messages/batch", - json=test_messages, + test_message = models.Message( + session_name=test_session.name, + content="Test message", + workspace_name=test_workspace.name, + peer_name=test_peer.name, + h_metadata={"old_key": "old_value"}, ) + db_session.add(test_message) + await db_session.commit() + response = client.put( + f"/v1/workspaces/{test_workspace.name}/sessions/{test_session.name}/messages/{test_message.public_id}", + json={"metadata": {}}, + ) assert response.status_code == 200 data = response.json() + assert data["metadata"] == {} - # Verify the response contains all messages - assert len(data) == 3 - # Verify messages are in the correct order and have correct content - for i, message in enumerate(data): - assert message["content"] == f"Test message {i}" - assert message["is_user"] == (i % 2 == 0) - assert message["metadata"] == {"batch_index": i} - assert "id" in message - assert message["session_id"] == test_session.public_id +@pytest.mark.asyncio +async def test_get_single_message(client, db_session, sample_data): + test_workspace, test_peer = sample_data - # Verify messages were actually saved to the database + # Create a test session and message + test_session = models.Session( + workspace_name=test_workspace.name, name=str(generate_nanoid()) + ) + db_session.add(test_session) + await db_session.commit() + + test_message = models.Message( + session_name=test_session.name, + content="Test message", + workspace_name=test_workspace.name, + peer_name=test_peer.name, + ) + db_session.add(test_message) + await db_session.commit() + + response = client.get( + f"/v1/workspaces/{test_workspace.name}/sessions/{test_session.name}/messages/{test_message.public_id}" + ) + assert response.status_code == 200 + data = response.json() + assert data["content"] == "Test message" + assert data["peer_id"] == test_peer.name + assert data["session_id"] == test_session.name + assert data["workspace_id"] == test_workspace.name + assert data["id"] == test_message.public_id + + +@pytest.mark.asyncio +async def test_get_nonexistent_message(client, db_session, sample_data): + """Test getting a message that doesn't exist""" + test_workspace, test_peer = sample_data + + # Create a test session + test_session = models.Session( + workspace_name=test_workspace.name, name=str(generate_nanoid()) + ) + db_session.add(test_session) + await db_session.commit() + + nonexistent_message_id = str(generate_nanoid()) + response = client.get( + f"/v1/workspaces/{test_workspace.name}/sessions/{test_session.name}/messages/{nonexistent_message_id}" + ) + assert response.status_code == 404 + + +@pytest.mark.asyncio +async def test_update_nonexistent_message(client, db_session, sample_data): + """Test updating a message that doesn't exist""" + test_workspace, test_peer = sample_data + + # Create a test session + test_session = models.Session( + workspace_name=test_workspace.name, name=str(generate_nanoid()) + ) + db_session.add(test_session) + await db_session.commit() + + nonexistent_message_id = str(generate_nanoid()) + response = client.put( + f"/v1/workspaces/{test_workspace.name}/sessions/{test_session.name}/messages/{nonexistent_message_id}", + json={"metadata": {"key": "value"}}, + ) + assert response.status_code == 404 + + +@pytest.mark.asyncio +async def test_create_messages_for_nonexistent_session(client, sample_data): + """Test creating messages for a session that doesn't exist - should create the session""" + test_workspace, test_peer = sample_data + + nonexistent_session_id = str(generate_nanoid()) response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions/{test_session.public_id}/messages/list", + f"/v1/workspaces/{test_workspace.name}/sessions/{nonexistent_session_id}/messages", + json={ + "messages": [ + { + "content": "Test message", + "peer_id": test_peer.name, + } + ] + }, + ) + # Should create the session and return 200 with the created message + assert response.status_code == 200 + data = response.json() + assert len(data) == 1 + assert data[0]["content"] == "Test message" + assert data[0]["workspace_id"] == test_workspace.name + assert data[0]["session_id"] == nonexistent_session_id + assert data[0]["peer_id"] == test_peer.name + + +@pytest.mark.asyncio +async def test_get_messages_for_nonexistent_session(client, sample_data): + """Test getting messages for a session that doesn't exist - should return empty list""" + test_workspace, test_peer = sample_data + + nonexistent_session_id = str(generate_nanoid()) + response = client.post( + f"/v1/workspaces/{test_workspace.name}/sessions/{nonexistent_session_id}/messages/list", json={}, ) + # Should return 200 with empty results (session doesn't exist = no messages) assert response.status_code == 200 - saved_messages = response.json()["items"] - assert len(saved_messages) == 3 + data = response.json() + assert "items" in data + assert len(data["items"]) == 0 @pytest.mark.asyncio -async def test_create_batch_messages_limit(client, db_session, sample_data): - test_app, test_user = sample_data - test_session = models.Session(user_id=test_user.public_id, app_id=test_app.public_id) +async def test_create_empty_batch_messages(client, db_session, sample_data): + """Test creating an empty batch of messages""" + test_workspace, test_peer = sample_data + + # Create a test session + test_session = models.Session( + workspace_name=test_workspace.name, name=str(generate_nanoid()) + ) db_session.add(test_session) await db_session.commit() - # Create batch with more than 100 messages - test_messages = { - "messages": [ - { - "content": f"Test message {i}", - "is_user": i % 2 == 0, - "metadata": {"batch_index": i}, - } - for i in range(101) # 101 messages - ] - } + response = client.post( + f"/v1/workspaces/{test_workspace.name}/sessions/{test_session.name}/messages", + json={"messages": []}, + ) + # Should return 422 for validation error (empty list not allowed) + assert response.status_code == 422 + + +@pytest.mark.asyncio +async def test_create_batch_messages_max_limit(client, db_session, sample_data): + """Test creating batch messages at the maximum limit (100 messages)""" + test_workspace, test_peer = sample_data + + # Create a test session + test_session = models.Session( + workspace_name=test_workspace.name, name=str(generate_nanoid()) + ) + db_session.add(test_session) + await db_session.commit() + + # Create exactly 100 messages (the maximum allowed) + messages = [ + {"content": f"Message {i}", "peer_id": test_peer.name, "metadata": {"index": i}} + for i in range(100) + ] response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions/{test_session.public_id}/messages/batch", - json=test_messages, + f"/v1/workspaces/{test_workspace.name}/sessions/{test_session.name}/messages", + json={"messages": messages}, ) - - assert response.status_code == 422 # Validation error + assert response.status_code == 200 data = response.json() - assert "messages" in data["detail"][0]["loc"] # Error should mention messages field + assert len(data) == 100 + assert data[0]["content"] == "Message 0" + assert data[99]["content"] == "Message 99" diff --git a/tests/routes/test_metamessages.py b/tests/routes/test_metamessages.py deleted file mode 100644 index 0a743220..00000000 --- a/tests/routes/test_metamessages.py +++ /dev/null @@ -1,440 +0,0 @@ -import pytest - -from src import models # Import your SQLAlchemy models - - -@pytest.mark.asyncio -async def test_create_metamessage(client, db_session, sample_data): - test_app, test_user = sample_data - # Create a test session - test_session = models.Session( - user_id=test_user.public_id, app_id=test_app.public_id - ) - db_session.add(test_session) - await db_session.commit() - test_message = models.Message( - session_id=test_session.public_id, - content="Test message", - is_user=True, - app_id=test_app.public_id, - user_id=test_user.public_id, - ) - db_session.add(test_message) - await db_session.commit() - - response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/metamessages", - json={ - "session_id": str(test_session.public_id), - "message_id": str(test_message.public_id), - "content": "Test Metamessage", - "metadata": {}, - "label": "test_type", - }, - ) - assert response.status_code == 200 - data = response.json() - assert data["user_id"] == str(test_user.public_id) - assert data["session_id"] == str(test_session.public_id) - assert data["message_id"] == str(test_message.public_id) - assert data["content"] == "Test Metamessage" - assert data["metadata"] == {} - assert data["label"] == "test_type" - assert data["metamessage_type"] == "test_type" - - -@pytest.mark.asyncio -async def test_get_metamessage(client, db_session, sample_data): - test_app, test_user = sample_data - # Create a test session - test_session = models.Session( - user_id=test_user.public_id, app_id=test_app.public_id - ) - db_session.add(test_session) - await db_session.commit() - test_message = models.Message( - session_id=test_session.public_id, - content="Test message", - is_user=True, - app_id=test_app.public_id, - user_id=test_user.public_id, - ) - db_session.add(test_message) - await db_session.commit() - test_metamessage = models.Metamessage( - user_id=test_user.public_id, - app_id=test_app.public_id, - session_id=test_session.public_id, - message_id=test_message.public_id, - content="Test Metamessage", - h_metadata={}, - label="test_type", - ) - db_session.add(test_metamessage) - await db_session.commit() - - response = client.get( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/metamessages/{test_metamessage.public_id}" - ) - assert response.status_code == 200 - data = response.json() - assert data["user_id"] == str(test_user.public_id) - assert data["app_id"] == str(test_app.public_id) - assert data["session_id"] == str(test_session.public_id) - assert data["message_id"] == str(test_message.public_id) - assert data["content"] == "Test Metamessage" - assert data["metadata"] == {} - assert data["label"] == "test_type" - assert data["metamessage_type"] == "test_type" - - -@pytest.mark.asyncio -async def test_get_metamessages_by_session(client, db_session, sample_data): - test_app, test_user = sample_data - # Create a test session - test_session = models.Session( - user_id=test_user.public_id, app_id=test_app.public_id - ) - db_session.add(test_session) - await db_session.commit() - test_message = models.Message( - session_id=test_session.public_id, - content="Test message", - is_user=True, - app_id=test_app.public_id, - user_id=test_user.public_id, - ) - db_session.add(test_message) - await db_session.commit() - - # Create metamessages for the same session - test_metamessage_1 = models.Metamessage( - user_id=test_user.public_id, - app_id=test_app.public_id, - session_id=test_session.public_id, - message_id=test_message.public_id, - content="Test Metamessage", - h_metadata={}, - label="test_type", - ) - test_metamessage_2 = models.Metamessage( - user_id=test_user.public_id, - app_id=test_app.public_id, - session_id=test_session.public_id, - message_id=test_message.public_id, - content="Test Metamessage", - h_metadata={}, - label="test_type", - ) - test_metamessage_3 = models.Metamessage( - user_id=test_user.public_id, - app_id=test_app.public_id, - session_id=test_session.public_id, - message_id=test_message.public_id, - content="Test Metamessage", - h_metadata={}, - label="test_type", - ) - test_metamessage_4 = models.Metamessage( - user_id=test_user.public_id, - app_id=test_app.public_id, - session_id=test_session.public_id, - message_id=test_message.public_id, - content="Test Metamessage", - h_metadata={}, - label="test_type_2", - ) - db_session.add(test_metamessage_1) - db_session.add(test_metamessage_2) - db_session.add(test_metamessage_3) - db_session.add(test_metamessage_4) - await db_session.commit() - - # Filter by session and type - response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/metamessages/list", - json={ - "session_id": str(test_session.public_id), - "label": "test_type", - }, - ) - - assert response.status_code == 200 - data = response.json() - assert "items" in data - assert len(data["items"]) == 3 - assert data["items"][0]["content"] == "Test Metamessage" - assert data["items"][0]["label"] == "test_type" - assert data["items"][0]["metamessage_type"] == "test_type" - assert data["items"][0]["session_id"] == str(test_session.public_id) - assert data["items"][0]["metadata"] == {} - assert data["items"][0]["app_id"] == str(test_app.public_id) - - -@pytest.mark.asyncio -async def test_get_metamessage_by_user(client, db_session, sample_data): - test_app, test_user = sample_data - # Create 3 test sessions - test_session_1 = models.Session( - user_id=test_user.public_id, app_id=test_app.public_id - ) - test_session_2 = models.Session( - user_id=test_user.public_id, app_id=test_app.public_id - ) - test_session_3 = models.Session( - user_id=test_user.public_id, app_id=test_app.public_id - ) - db_session.add(test_session_1) - db_session.add(test_session_2) - db_session.add(test_session_3) - await db_session.commit() - - # Create a message in each session - test_message_1 = models.Message( - session_id=test_session_1.public_id, - content="Test message", - is_user=True, - app_id=test_app.public_id, - user_id=test_user.public_id, - ) - test_message_2 = models.Message( - session_id=test_session_2.public_id, - content="Test message", - is_user=True, - app_id=test_app.public_id, - user_id=test_user.public_id, - ) - test_message_3 = models.Message( - session_id=test_session_3.public_id, - content="Test message", - is_user=True, - app_id=test_app.public_id, - user_id=test_user.public_id, - ) - db_session.add(test_message_1) - db_session.add(test_message_2) - db_session.add(test_message_3) - await db_session.commit() - - # Create metamessages across different sessions - test_metamessage_1 = models.Metamessage( - user_id=test_user.public_id, - app_id=test_app.public_id, - session_id=test_session_1.public_id, - message_id=test_message_1.public_id, - content="Test Metamessage", - h_metadata={}, - label="test_type", - ) - test_metamessage_2 = models.Metamessage( - user_id=test_user.public_id, - app_id=test_app.public_id, - session_id=test_session_2.public_id, - message_id=test_message_2.public_id, - content="Test Metamessage", - h_metadata={}, - label="test_type", - ) - test_metamessage_3 = models.Metamessage( - user_id=test_user.public_id, - app_id=test_app.public_id, - session_id=test_session_3.public_id, - message_id=test_message_3.public_id, - content="Test Metamessage", - h_metadata={}, - label="test_type", - ) - test_metamessage_4 = models.Metamessage( - user_id=test_user.public_id, - app_id=test_app.public_id, - session_id=test_session_3.public_id, - message_id=test_message_3.public_id, - content="Test Metamessage", - h_metadata={}, - label="test_type_2", - ) - # Create a user-level metamessage (no session/message) - test_metamessage_5 = models.Metamessage( - user_id=test_user.public_id, - app_id=test_app.public_id, - content="User level metamessage", - h_metadata={}, - label="test_type", - ) - db_session.add(test_metamessage_1) - db_session.add(test_metamessage_2) - db_session.add(test_metamessage_3) - db_session.add(test_metamessage_4) - db_session.add(test_metamessage_5) - await db_session.commit() - - # Filter only by type across all user's metamessages - response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/metamessages/list", - json={"label": "test_type"}, - ) - assert response.status_code == 200 - data = response.json() - assert len(data["items"]) == 4 # All test_type metamessages for the user - assert data["items"][0]["content"] in ["Test Metamessage", "User level metamessage"] - assert data["items"][0]["label"] == "test_type" - assert data["items"][0]["metamessage_type"] == "test_type" - assert data["items"][0]["user_id"] == str(test_user.public_id) - assert data["items"][0]["app_id"] == str(test_app.public_id) - - -@pytest.mark.asyncio -async def test_create_user_level_metamessage(client, db_session, sample_data): - test_app, test_user = sample_data - - # Create a user-level metamessage (no session or message) - response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/metamessages", - json={ - "content": "User level insight", - "metadata": {"source": "user_profile"}, - "label": "user_insight", - }, - ) - assert response.status_code == 200 - data = response.json() - assert data["user_id"] == str(test_user.public_id) - assert data["session_id"] is None - assert data["message_id"] is None - assert data["content"] == "User level insight" - assert data["metadata"] == {"source": "user_profile"} - assert data["metamessage_type"] == "user_insight" - assert data["app_id"] == str(test_app.public_id) - assert data["label"] == "user_insight" - assert data["metamessage_type"] == "user_insight" - - -@pytest.mark.asyncio -async def test_update_metamessage(client, db_session, sample_data): - test_app, test_user = sample_data - # Create a test session - test_session = models.Session( - user_id=test_user.public_id, app_id=test_app.public_id - ) - db_session.add(test_session) - await db_session.commit() - test_message = models.Message( - session_id=test_session.public_id, - content="Test message", - is_user=True, - app_id=test_app.public_id, - user_id=test_user.public_id, - ) - db_session.add(test_message) - await db_session.commit() - test_metamessage = models.Metamessage( - user_id=test_user.public_id, - app_id=test_app.public_id, - session_id=test_session.public_id, - message_id=test_message.public_id, - content="Test Metamessage", - h_metadata={}, - label="test_type", - ) - db_session.add(test_metamessage) - await db_session.commit() - - response = client.put( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/metamessages/{test_metamessage.public_id}", - json={ - "metadata": {"new_key": "new_value"}, - "label": "updated_type", - }, - ) - assert response.status_code == 200 - data = response.json() - assert data["metadata"] == {"new_key": "new_value"} - assert data["label"] == "updated_type" - assert data["metamessage_type"] == "updated_type" - assert data["user_id"] == str(test_user.public_id) - assert data["session_id"] == str(test_session.public_id) - assert data["message_id"] == str(test_message.public_id) - assert data["app_id"] == str(test_app.public_id) - - -@pytest.mark.asyncio -async def test_create_metamessage_with_label_and_alias(client, db_session, sample_data): - test_app, test_user = sample_data - # Create a common session and message for both test cases - test_session = models.Session( - user_id=test_user.public_id, app_id=test_app.public_id - ) - db_session.add(test_session) - await db_session.commit() - test_message = models.Message( - session_id=test_session.public_id, - content="Shared message content", - is_user=True, - app_id=test_app.public_id, - user_id=test_user.public_id, - ) - db_session.add(test_message) - await db_session.commit() - - common_payload_parts = { - "session_id": str(test_session.public_id), - "message_id": str(test_message.public_id), - "metadata": {"source": "input_test"}, - } - test_value_for_type = "input_consistency_test_type" - - # 1. Create metamessage using "label" - response_with_label = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/metamessages", - json={ - **common_payload_parts, - "content": "Content created with label", - "label": test_value_for_type, - }, - ) - assert response_with_label.status_code == 200 - data_from_label_input = response_with_label.json() - - # 2. Create metamessage using "metamessage_type" (alias) - response_with_alias = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/metamessages", - json={ - **common_payload_parts, - "content": "Content created with alias", - "metamessage_type": test_value_for_type, # Using alias - }, - ) - assert response_with_alias.status_code == 200 - data_from_alias_input = response_with_alias.json() - - # Assertions for the first response (created with "label") - assert data_from_label_input["content"] == "Content created with label" - assert data_from_label_input["label"] == test_value_for_type - assert data_from_label_input["metamessage_type"] == test_value_for_type - assert data_from_label_input["metadata"] == common_payload_parts["metadata"] - assert data_from_label_input["metamessage_type"] == test_value_for_type - assert data_from_label_input["app_id"] == str(test_app.public_id) - assert data_from_label_input["user_id"] == str(test_user.public_id) - assert data_from_label_input["session_id"] == common_payload_parts["session_id"] - assert data_from_label_input["message_id"] == common_payload_parts["message_id"] - - # Assertions for the second response (created with "metamessage_type") - assert data_from_alias_input["content"] == "Content created with alias" - assert ( - data_from_alias_input["label"] == test_value_for_type - ) # Output should still be "label" - assert data_from_alias_input["metamessage_type"] == test_value_for_type - assert data_from_alias_input["metadata"] == common_payload_parts["metadata"] - assert data_from_alias_input["metamessage_type"] == test_value_for_type - assert data_from_alias_input["user_id"] == str(test_user.public_id) - assert data_from_alias_input["session_id"] == common_payload_parts["session_id"] - assert data_from_alias_input["message_id"] == common_payload_parts["message_id"] - assert data_from_alias_input["app_id"] == str(test_app.public_id) - # Key assertion: The output for the type/label field is consistent ("label") and has the correct value - assert data_from_label_input["label"] == data_from_alias_input["label"] - assert ( - data_from_label_input["metamessage_type"] - == data_from_alias_input["metamessage_type"] - ) - assert data_from_label_input["label"] == data_from_label_input["metamessage_type"] - assert data_from_alias_input["label"] == data_from_alias_input["metamessage_type"] diff --git a/tests/routes/test_peers.py b/tests/routes/test_peers.py new file mode 100644 index 00000000..95783583 --- /dev/null +++ b/tests/routes/test_peers.py @@ -0,0 +1,525 @@ +from nanoid import generate as generate_nanoid + + +def test_get_or_create_peer(client, sample_data): + test_workspace, _ = sample_data + name = str(generate_nanoid()) + response = client.post( + f"/v1/workspaces/{test_workspace.name}/peers", + json={"name": name, "metadata": {"peer_key": "peer_value"}}, + ) + assert response.status_code == 200 + data = response.json() + assert data["id"] == name + assert data["metadata"] == {"peer_key": "peer_value"} + assert "id" in data + + +def test_get_or_create_peer_with_configuration(client, sample_data): + """Test peer creation with configuration parameter""" + test_workspace, _ = sample_data + name = str(generate_nanoid()) + configuration = {"experimental": True, "beta": False} + + response = client.post( + f"/v1/workspaces/{test_workspace.name}/peers", + json={"name": name, "configuration": configuration}, + ) + assert response.status_code == 200 + data = response.json() + assert data["id"] == name + assert data["configuration"] == configuration + + +def test_get_or_create_peer_with_all_optional_params(client, sample_data): + """Test peer creation with all optional parameters""" + test_workspace, _ = sample_data + name = str(generate_nanoid()) + metadata = {"key": "value", "number": 42} + configuration = {"feature1": True, "feature2": False} + + response = client.post( + f"/v1/workspaces/{test_workspace.name}/peers", + json={"name": name, "metadata": metadata, "configuration": configuration}, + ) + assert response.status_code == 200 + data = response.json() + assert data["id"] == name + assert data["metadata"] == metadata + assert data["configuration"] == configuration + + +def test_get_or_create_existing_peer(client, sample_data): + test_workspace, _ = sample_data + name = str(generate_nanoid()) + + # Create the peer + response = client.post( + f"/v1/workspaces/{test_workspace.name}/peers", + json={"name": name, "metadata": {"peer_key": "peer_value"}}, + ) + assert response.status_code == 200 + peer1 = response.json() + + # Try to create the same peer again - should return existing peer + response = client.post( + f"/v1/workspaces/{test_workspace.name}/peers", + json={"name": name, "metadata": {"peer_key": "peer_value"}}, + ) + assert response.status_code == 200 + peer2 = response.json() + + # Both should be the same peer + assert peer1["id"] == peer2["id"] + assert peer1["metadata"] == peer2["metadata"] + + +def test_get_peers(client, sample_data): + test_workspace, _ = sample_data + + # Create a few peers with metadata + response = client.post( + f"/v1/workspaces/{test_workspace.name}/peers", + json={"name": str(generate_nanoid()), "metadata": {"peer_key": "peer_value"}}, + ) + response = client.post( + f"/v1/workspaces/{test_workspace.name}/peers", + json={"name": str(generate_nanoid()), "metadata": {"peer_key": "peer_value"}}, + ) + response = client.post( + f"/v1/workspaces/{test_workspace.name}/peers", + json={"name": str(generate_nanoid()), "metadata": {"peer_key": "peer_value2"}}, + ) + + # Get all peers + response = client.post( + f"/v1/workspaces/{test_workspace.name}/peers/list", + json={}, + ) + assert response.status_code == 200 + data = response.json() + assert "items" in data + assert len(data["items"]) > 0 + + # Get peers with filter + response = client.post( + f"/v1/workspaces/{test_workspace.name}/peers/list", + json={"filter": {"peer_key": "peer_value"}}, + ) + assert response.status_code == 200 + data = response.json() + assert "items" in data + assert len(data["items"]) >= 2 + assert data["items"][0]["metadata"]["peer_key"] == "peer_value" + + +def test_get_peers_with_empty_filter(client, sample_data): + """Test peer listing with empty filter object""" + test_workspace, _ = sample_data + + response = client.post( + f"/v1/workspaces/{test_workspace.name}/peers/list", json={"filter": {}} + ) + assert response.status_code == 200 + data = response.json() + assert "items" in data + assert isinstance(data["items"], list) + + +def test_get_peers_with_null_filter(client, sample_data): + """Test peer listing with null filter""" + test_workspace, _ = sample_data + + response = client.post( + f"/v1/workspaces/{test_workspace.name}/peers/list", json={"filter": None} + ) + assert response.status_code == 200 + data = response.json() + assert "items" in data + assert isinstance(data["items"], list) + + +def test_update_peer(client, sample_data): + test_workspace, test_peer = sample_data + response = client.put( + f"/v1/workspaces/{test_workspace.name}/peers/{test_peer.name}", + json={"metadata": {"new_key": "new_value"}}, + ) + assert response.status_code == 200 + data = response.json() + assert data["metadata"] == {"new_key": "new_value"} + + +def test_update_peer_with_configuration(client, sample_data): + """Test peer update with configuration parameter""" + test_workspace, test_peer = sample_data + configuration = {"new_feature": True, "legacy_feature": False} + + response = client.put( + f"/v1/workspaces/{test_workspace.name}/peers/{test_peer.name}", + json={"configuration": configuration}, + ) + assert response.status_code == 200 + data = response.json() + assert data["configuration"] == configuration + + +def test_update_peer_with_all_optional_params(client, sample_data): + """Test peer update with both metadata and configuration""" + test_workspace, test_peer = sample_data + metadata = {"updated_key": "updated_value", "count": 100} + configuration = {"experimental": True, "beta": True} + + response = client.put( + f"/v1/workspaces/{test_workspace.name}/peers/{test_peer.name}", + json={"metadata": metadata, "configuration": configuration}, + ) + assert response.status_code == 200 + data = response.json() + assert data["metadata"] == metadata + assert data["configuration"] == configuration + + +def test_update_peer_with_null_metadata(client, sample_data): + """Test peer update with null metadata""" + test_workspace, test_peer = sample_data + + # First set some metadata + client.put( + f"/v1/workspaces/{test_workspace.name}/peers/{test_peer.name}", + json={"metadata": {"temp": "value"}}, + ) + + # Then clear it with null + response = client.put( + f"/v1/workspaces/{test_workspace.name}/peers/{test_peer.name}", + json={"metadata": None}, + ) + assert response.status_code == 200 + data = response.json() + assert "metadata" in data + + +def test_update_peer_with_null_configuration(client, sample_data): + """Test peer update with null configuration""" + test_workspace, test_peer = sample_data + + response = client.put( + f"/v1/workspaces/{test_workspace.name}/peers/{test_peer.name}", + json={"configuration": None}, + ) + assert response.status_code == 200 + data = response.json() + assert "configuration" in data + + +def test_get_sessions_for_peer_no_sessions(client, sample_data): + test_workspace, test_peer = sample_data + + # Get sessions for the peer + response = client.post( + f"/v1/workspaces/{test_workspace.name}/peers/{test_peer.name}/sessions", + json={}, + ) + assert response.status_code == 200 + data = response.json() + assert "items" in data + + +def test_get_sessions_for_peer(client, sample_data): + test_workspace, test_peer = sample_data + + # Create session for the peer + session_name = str(generate_nanoid()) + create_response = client.post( + f"/v1/workspaces/{test_workspace.name}/sessions", + json={"id": session_name, "peer_names": {test_peer.name: {}}}, + ) + assert create_response.status_code == 200 + created_session = create_response.json() + assert created_session["id"] == session_name + + # Now get sessions for the peer and validate the session is returned + response = client.post( + f"/v1/workspaces/{test_workspace.name}/peers/{test_peer.name}/sessions", + ) + assert response.status_code == 200 + data = response.json() + assert "items" in data + # Check that the created session is in the returned items + session_ids = [item["id"] for item in data["items"]] + assert session_name in session_ids + assert len(data["items"]) == 1 + + +def test_get_sessions_for_peer_with_is_active_filter(client, sample_data): + """Test getting sessions for peer with is_active parameter""" + test_workspace, test_peer = sample_data + + # Create and then delete a session to have inactive session + session_name = str(generate_nanoid()) + client.post( + f"/v1/workspaces/{test_workspace.name}/sessions", + json={"id": session_name, "peer_names": {test_peer.name: {}}}, + ) + client.delete(f"/v1/workspaces/{test_workspace.name}/sessions/{session_name}") + + # Test getting inactive sessions + response = client.post( + f"/v1/workspaces/{test_workspace.name}/peers/{test_peer.name}/sessions", + json={"is_active": False}, + ) + assert response.status_code == 200 + data = response.json() + assert "items" in data + # Should find at least our deleted session + inactive_sessions = [s for s in data["items"] if not s["is_active"]] + assert len(inactive_sessions) > 0 + + +def test_get_sessions_for_peer_with_empty_filter(client, sample_data): + """Test getting sessions for peer with empty filter object""" + test_workspace, test_peer = sample_data + + response = client.post( + f"/v1/workspaces/{test_workspace.name}/peers/{test_peer.name}/sessions", + json={"filter": {}}, + ) + assert response.status_code == 200 + data = response.json() + assert "items" in data + assert isinstance(data["items"], list) + + +def test_create_and_get_messages_for_peer(client, sample_data): + test_workspace, test_peer = sample_data + + # Create messages for the peer + response = client.post( + f"/v1/workspaces/{test_workspace.name}/peers/{test_peer.name}/messages", + json={ + "messages": [ + { + "content": "Hello world", + "peer_id": test_peer.name, + "metadata": {"message_key": "message_value"}, + }, + { + "content": "Second message", + "peer_id": test_peer.name, + "metadata": {"message_key": "message_value2"}, + }, + ] + }, + ) + assert response.status_code == 200 + data = response.json() + assert len(data) == 2 + assert data[0]["content"] == "Hello world" + assert data[1]["content"] == "Second message" + assert data[0]["metadata"] == {"message_key": "message_value"} + + # Get messages for the peer + response = client.post( + f"/v1/workspaces/{test_workspace.name}/peers/{test_peer.name}/messages/list", + json={}, + ) + assert response.status_code == 200 + data = response.json() + assert "items" in data + assert len(data["items"]) == 2 + assert data["items"][0]["content"] == "Hello world" + assert data["items"][1]["content"] == "Second message" + assert data["items"][0]["metadata"] == {"message_key": "message_value"} + assert data["items"][1]["metadata"] == {"message_key": "message_value2"} + + +def test_get_messages_for_peer_with_reverse(client, sample_data): + """Test getting messages for peer with reverse parameter""" + test_workspace, test_peer = sample_data + + # Create messages + client.post( + f"/v1/workspaces/{test_workspace.name}/peers/{test_peer.name}/messages", + json={ + "messages": [ + {"content": "First message", "peer_id": test_peer.name}, + {"content": "Second message", "peer_id": test_peer.name}, + ] + }, + ) + + # Test normal order + response = client.post( + f"/v1/workspaces/{test_workspace.name}/peers/{test_peer.name}/messages/list", + json={}, + ) + assert response.status_code == 200 + normal_data = response.json() + + # Test reversed order + response = client.post( + f"/v1/workspaces/{test_workspace.name}/peers/{test_peer.name}/messages/list?reverse=true", + json={}, + ) + assert response.status_code == 200 + reversed_data = response.json() + + # Both should have items + assert len(normal_data["items"]) > 0 + assert len(reversed_data["items"]) > 0 + + +def test_get_messages_for_peer_with_empty_filter(client, sample_data): + """Test getting messages for peer with empty filter object""" + test_workspace, test_peer = sample_data + + response = client.post( + f"/v1/workspaces/{test_workspace.name}/peers/{test_peer.name}/messages/list", + json={"filter": {}}, + ) + assert response.status_code == 200 + data = response.json() + assert "items" in data + assert isinstance(data["items"], list) + + +def test_get_messages_for_peer_with_null_filter(client, sample_data): + """Test getting messages for peer with null filter""" + test_workspace, test_peer = sample_data + + response = client.post( + f"/v1/workspaces/{test_workspace.name}/peers/{test_peer.name}/messages/list", + json={"filter": None}, + ) + assert response.status_code == 200 + data = response.json() + assert "items" in data + assert isinstance(data["items"], list) + + +def test_chat(client, sample_data): + test_workspace, test_peer = sample_data + target_peer = str(generate_nanoid()) + + # Test chat endpoint + response = client.post( + f"/v1/workspaces/{test_workspace.name}/peers/{test_peer.name}/chat", + json={ + "queries": "Hello, how are you?", + "stream": False, + "target": target_peer, + }, + ) + assert response.status_code == 200 + data = response.json() + assert "content" in data + + +def test_chat_with_optional_params(client, sample_data): + """Test chat endpoint with optional parameters""" + test_workspace, test_peer = sample_data + + session_id = str(generate_nanoid()) + + # Create a session first + client.post( + f"/v1/workspaces/{test_workspace.name}/sessions", + json={"id": session_id, "peer_names": {test_peer.name: {}}}, + ) + + # Test chat without optional parameters + response = client.post( + f"/v1/workspaces/{test_workspace.name}/peers/{test_peer.name}/chat", + json={ + "queries": "Hello, how are you?", + "stream": False, + "session_id": session_id, + }, + ) + assert response.status_code == 200 + data = response.json() + assert "content" in data + + +def test_get_peer_representation_with_session(client, sample_data): + """Test peer representation with session_id parameter""" + test_workspace, test_peer = sample_data + session_id = str(generate_nanoid()) + + # Create a session first + client.post( + f"/v1/workspaces/{test_workspace.name}/sessions", + json={"id": session_id, "peer_names": {test_peer.name: {}}}, + ) + + # Test representation scoped to session + response = client.post( + f"/v1/workspaces/{test_workspace.name}/peers/{test_peer.name}/representation", + json={ + "session_id": session_id, + "queries": "Hello, how are you?", + }, + ) + assert response.status_code == 200 + + +def test_search_peer(client, sample_data): + """Test the peer search functionality""" + test_workspace, test_peer = sample_data + + # Add some messages to search through + client.post( + f"/v1/workspaces/{test_workspace.name}/peers/{test_peer.name}/messages", + json={ + "messages": [ + {"content": "Search this content", "peer_id": test_peer.name}, + {"content": "Another searchable message", "peer_id": test_peer.name}, + ] + }, + ) + + # Search with a query + response = client.post( + f"/v1/workspaces/{test_workspace.name}/peers/{test_peer.name}/search", + json="search query", + ) + assert response.status_code == 200 + data = response.json() + + # Response should have pagination structure + assert "items" in data + assert "total" in data + assert "page" in data + assert "size" in data + assert isinstance(data["items"], list) + + +def test_search_peer_empty_query(client, sample_data): + """Test peer search with empty query""" + test_workspace, test_peer = sample_data + + # Search with empty query + response = client.post( + f"/v1/workspaces/{test_workspace.name}/peers/{test_peer.name}/search", json="" + ) + assert response.status_code == 200 + data = response.json() + + # Response should still have proper pagination structure + assert "items" in data + assert isinstance(data["items"], list) + + +def test_search_peer_nonexistent(client, sample_data): + """Test searching a peer that doesn't exist""" + test_workspace, _ = sample_data + nonexistent_peer_id = str(generate_nanoid()) + + response = client.post( + f"/v1/workspaces/{test_workspace.name}/peers/{nonexistent_peer_id}/search", + json="test query", + ) + # This should probably return 404 or handle gracefully + # The exact behavior depends on the crud.search implementation + assert response.status_code in [200, 404, 422] diff --git a/tests/routes/test_scoped_api.py b/tests/routes/test_scoped_api.py index 8dbee588..bc154fdb 100644 --- a/tests/routes/test_scoped_api.py +++ b/tests/routes/test_scoped_api.py @@ -3,11 +3,11 @@ from nanoid import generate as generate_nanoid from src.security import JWTParams, create_jwt -def test_create_app_with_auth(auth_client): +def test_create_workspace_with_auth(auth_client): name = str(generate_nanoid()) response = auth_client.post( - "/v1/apps", json={"name": name, "metadata": {"key": "value"}} + "/v1/workspaces", json={"name": name, "metadata": {"key": "value"}} ) # Check expected behavior based on auth type @@ -26,7 +26,7 @@ def test_auth_response_time(auth_client): start_time = time.time() response = auth_client.post( - "/v1/apps", json={"name": name, "metadata": {"key": "value"}} + "/v1/workspaces", json={"name": name, "metadata": {"key": "value"}} ) end_time = time.time() @@ -43,18 +43,12 @@ def test_auth_response_time(auth_client): assert response.status_code == 200 -def test_get_or_create_app_with_auth(auth_client): +def test_get_or_create_workspace_with_auth(auth_client): name = str(generate_nanoid()) - # Should return a ResourceNotFoundException with 404 status - response = auth_client.get(f"/v1/apps/name/{name}") - if auth_client.auth_type != "admin": - assert response.status_code == 401 - return - - assert response.status_code == 404 - - response = auth_client.get(f"/v1/apps/get_or_create/{name}") + response = auth_client.post( + "/v1/workspaces", json={"name": name, "metadata": {"key": "value"}} + ) if auth_client.auth_type != "admin": assert response.status_code == 401 @@ -63,223 +57,160 @@ def test_get_or_create_app_with_auth(auth_client): assert response.status_code == 200 -def test_get_app_by_id_with_auth(auth_client, sample_data): - test_app, _ = sample_data +def test_get_workspace_with_auth(auth_client, sample_data): + test_workspace, _ = sample_data if auth_client.auth_type == "empty": - # For non-admin, include the app_id in the JWT + # For non-admin, include the workspace name in the JWT auth_client.headers["Authorization"] = ( - f"Bearer {create_jwt(JWTParams(ap=test_app.public_id))}" + f"Bearer {create_jwt(JWTParams(w=test_workspace.name))}" ) - response = auth_client.get(f"/v1/apps?app_id={test_app.public_id}") + response = auth_client.post("/v1/workspaces", json={"name": test_workspace.name}) - # Admin JWT or JWT with matching app_id should be allowed + # Admin JWT or JWT with matching workspace should be allowed if auth_client.auth_type in ["admin", "empty"]: assert response.status_code == 200 else: assert response.status_code == 401 -def test_get_app_from_token(auth_client, sample_data): - test_app, _ = sample_data +def test_update_workspace_with_auth(auth_client, sample_data): + test_workspace, _ = sample_data if auth_client.auth_type == "empty": - # For non-admin, include the app_id in the JWT + # For non-admin, include the workspace name in the JWT auth_client.headers["Authorization"] = ( - f"Bearer {create_jwt(JWTParams(ap=test_app.public_id))}" - ) - - response = auth_client.get("/v1/apps") - - if auth_client.auth_type == "empty": - assert response.status_code == 200 - assert response.json()["id"] == test_app.public_id - else: - assert response.status_code == 401 - - -def test_get_app_by_name_with_auth(auth_client, sample_data): - test_app, _ = sample_data - - if auth_client.auth_type == "empty": - # For non-admin, include the app_id in the JWT - # Note that this will still fail because name route requires admin - auth_client.headers["Authorization"] = ( - f"Bearer {create_jwt(JWTParams(ap=test_app.public_id))}" - ) - - response = auth_client.get(f"/v1/apps/name/{test_app.name}") - - # Only admin JWT should be allowed - if auth_client.auth_type == "admin": - assert response.status_code == 200 - else: - assert response.status_code == 401 - - -def test_update_app_with_auth(auth_client, sample_data): - test_app, _ = sample_data - - if auth_client.auth_type == "empty": - # For non-admin, include the app_id in the JWT - # Note that this will still fail because name route requires admin - auth_client.headers["Authorization"] = ( - f"Bearer {create_jwt(JWTParams(ap=test_app.public_id))}" + f"Bearer {create_jwt(JWTParams(w=test_workspace.name))}" ) new_name = str(generate_nanoid()) response = auth_client.put( - f"/v1/apps/{test_app.public_id}", + f"/v1/workspaces/{test_workspace.name}", json={"name": new_name, "metadata": {"new_key": "new_value"}}, ) - # Only admin JWT or JWT with matching app_id should be allowed + # Only admin JWT or JWT with matching workspace should be allowed if auth_client.auth_type in ["admin", "empty"]: assert response.status_code == 200 else: assert response.status_code == 401 -def test_update_app_with_wrong_auth(auth_client, sample_data): - test_app, _ = sample_data +def test_update_workspace_with_wrong_auth(auth_client, sample_data): + test_workspace, _ = sample_data - different_app = str(generate_nanoid()) + different_workspace = str(generate_nanoid()) if auth_client.auth_type == "empty": - # For non-admin, include the *wrong* app_id in the JWT + # For non-admin, include the *wrong* workspace name in the JWT auth_client.headers["Authorization"] = ( - f"Bearer {create_jwt(JWTParams(ap=different_app))}" + f"Bearer {create_jwt(JWTParams(w=different_workspace))}" ) new_name = str(generate_nanoid()) response = auth_client.put( - f"/v1/apps/{test_app.public_id}", + f"/v1/workspaces/{test_workspace.name}", json={"name": new_name, "metadata": {"new_key": "new_value"}}, ) - # Only admin JWT or JWT with matching app_id should be allowed + # Only admin JWT or JWT with matching workspace should be allowed if auth_client.auth_type == "admin": assert response.status_code == 200 else: - # wrong app_id should be rejected + # wrong workspace should be rejected assert response.status_code == 401 -def test_create_user_with_auth(auth_client, sample_data): - test_app, _ = sample_data +def test_create_peer_with_auth(auth_client, sample_data): + test_workspace, _ = sample_data if auth_client.auth_type == "empty": - # For non-admin, include the app_id in the JWT + # For non-admin, include the workspace name in the JWT auth_client.headers["Authorization"] = ( - f"Bearer {create_jwt(JWTParams(ap=test_app.public_id))}" + f"Bearer {create_jwt(JWTParams(w=test_workspace.name))}" ) name = str(generate_nanoid()) response = auth_client.post( - f"/v1/apps/{test_app.public_id}/users", - json={"name": name, "metadata": {"user_key": "user_value"}}, + f"/v1/workspaces/{test_workspace.name}/peers", + json={"name": name, "metadata": {"peer_key": "peer_value"}}, ) - # Only admin JWT or JWT with matching app_id should be allowed + # Only admin JWT or JWT with matching workspace should be allowed if auth_client.auth_type in ["admin", "empty"]: assert response.status_code == 200 else: assert response.status_code == 401 -def test_get_user_by_id_with_auth(auth_client, sample_data): - test_app, test_user = sample_data +def test_get_peer_by_name_with_auth(auth_client, sample_data): + test_workspace, test_peer = sample_data if auth_client.auth_type == "empty": - # For non-admin, include the app_id in the JWT + # For non-admin, include the workspace name in the JWT auth_client.headers["Authorization"] = ( - f"Bearer {create_jwt(JWTParams(ap=test_app.public_id))}" + f"Bearer {create_jwt(JWTParams(w=test_workspace.name))}" ) - response = auth_client.get( - f"/v1/apps/{test_app.public_id}/users?user_id={test_user.public_id}" + # Use POST /list endpoint to get peers + response = auth_client.post( + f"/v1/workspaces/{test_workspace.name}/peers/list", + json={"filter": {"name": test_peer.name}}, ) - # Admin JWT or JWT with matching app_id should be allowed + # Admin JWT or JWT with matching workspace should be allowed if auth_client.auth_type in ["admin", "empty"]: assert response.status_code == 200 else: assert response.status_code == 401 - # Test with user-scoped JWT + # Test with peer-scoped JWT if auth_client.auth_type == "empty": auth_client.headers["Authorization"] = ( - f"Bearer {create_jwt(JWTParams(us=test_user.public_id))}" + f"Bearer {create_jwt(JWTParams(p=test_peer.name))}" ) - response = auth_client.get( - f"/v1/apps/{test_app.public_id}/users?user_id={test_user.public_id}" + # Get specific peer using get_or_create endpoint + response = auth_client.post( + f"/v1/workspaces/{test_workspace.name}/peers", json={"name": test_peer.name} ) assert response.status_code == 200 - response2 = auth_client.get(f"/v1/apps/{test_app.public_id}/users") - assert response2.status_code == 200 - - print(response2.json()) - - assert response2.json()["id"] == test_user.public_id - - -def test_get_user_by_name_with_auth(auth_client, sample_data): - test_app, test_user = sample_data +def test_update_peer_with_auth(auth_client, sample_data): + test_workspace, test_peer = sample_data if auth_client.auth_type == "empty": - # For non-admin, include the app_id in the JWT + # For non-admin, include the workspace name in the JWT auth_client.headers["Authorization"] = ( - f"Bearer {create_jwt(JWTParams(ap=test_app.public_id))}" - ) - - response = auth_client.get( - f"/v1/apps/{test_app.public_id}/users/name/{test_user.name}" - ) - - # Admin JWT or JWT with matching app_id should be allowed - if auth_client.auth_type in ["admin", "empty"]: - assert response.status_code == 200 - else: - assert response.status_code == 401 - - -def test_update_user_with_auth(auth_client, sample_data): - test_app, test_user = sample_data - - if auth_client.auth_type == "empty": - # For non-admin, include the app_id in the JWT - auth_client.headers["Authorization"] = ( - f"Bearer {create_jwt(JWTParams(ap=test_app.public_id))}" + f"Bearer {create_jwt(JWTParams(w=test_workspace.name))}" ) new_name = str(generate_nanoid()) response = auth_client.put( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}", + f"/v1/workspaces/{test_workspace.name}/peers/{test_peer.name}", json={"name": new_name, "metadata": {"updated_key": "updated_value"}}, ) - # Admin JWT or JWT with matching app_id should be allowed + # Admin JWT or JWT with matching workspace should be allowed if auth_client.auth_type in ["admin", "empty"]: assert response.status_code == 200 else: assert response.status_code == 401 - # Test with user-scoped JWT + # Test with peer-scoped JWT if auth_client.auth_type == "empty": auth_client.headers["Authorization"] = ( - f"Bearer {create_jwt(JWTParams(us=test_user.public_id))}" + f"Bearer {create_jwt(JWTParams(p=test_peer.name, w=test_workspace.name))}" ) response = auth_client.put( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}", + f"/v1/workspaces/{test_workspace.name}/peers/{test_peer.name}", json={ "name": str(generate_nanoid()), - "metadata": {"user_key": "user_value"}, + "metadata": {"peer_key": "peer_value"}, }, ) @@ -287,51 +218,54 @@ def test_update_user_with_auth(auth_client, sample_data): def test_create_session_with_auth(auth_client, sample_data): - test_app, test_user = sample_data + test_workspace, test_peer = sample_data if auth_client.auth_type == "empty": - # For non-admin, include the app_id and user_id in the JWT + # For non-admin, include the workspace name in the JWT auth_client.headers["Authorization"] = ( - f"Bearer {create_jwt(JWTParams(ap=test_app.public_id, us=test_user.public_id))}" + f"Bearer {create_jwt(JWTParams(w=test_workspace.name))}" ) + session_name = str(generate_nanoid()) response = auth_client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions", - json={}, + f"/v1/workspaces/{test_workspace.name}/sessions", + json={"name": session_name, "peer_names": {test_peer.name: {}}}, ) - # Only admin JWT or JWT with matching app_id and user_id should be allowed + # Only admin JWT or JWT with matching workspace should be allowed if auth_client.auth_type in ["admin", "empty"]: assert response.status_code == 200 else: assert response.status_code == 401 - # Remove app_id from header and make sure user-scoped key works too + # Test with peer-scoped JWT if auth_client.auth_type == "empty": auth_client.headers["Authorization"] = ( - f"Bearer {create_jwt(JWTParams(us=test_user.public_id))}" + f"Bearer {create_jwt(JWTParams(p=test_peer.name))}" ) + session_name2 = str(generate_nanoid()) response = auth_client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions", - json={}, + f"/v1/workspaces/{test_workspace.name}/sessions", + json={"name": session_name2, "peer_names": {test_peer.name: {}}}, ) assert response.status_code == 200 -def test_get_session_by_id_with_auth(auth_client, sample_data): - test_app, test_user = sample_data +def test_get_session_by_name_with_auth(auth_client, sample_data): + test_workspace, test_peer = sample_data # First create a session if auth_client.auth_type == "empty": auth_client.headers["Authorization"] = ( - f"Bearer {create_jwt(JWTParams(ap=test_app.public_id, us=test_user.public_id))}" + f"Bearer {create_jwt(JWTParams(w=test_workspace.name))}" ) + session_name = str(generate_nanoid()) create_response = auth_client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions", - json={}, + f"/v1/workspaces/{test_workspace.name}/sessions", + json={"name": session_name, "peer_names": {test_peer.name: {}}}, ) if auth_client.auth_type not in ["admin", "empty"]: @@ -339,290 +273,65 @@ def test_get_session_by_id_with_auth(auth_client, sample_data): return assert create_response.status_code == 200 - session_id = create_response.json()["id"] - # Test with app and user scoped JWT - response = auth_client.get( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions?session_id={session_id}" + # Test with workspace scoped JWT - get the same session + response = auth_client.post( + f"/v1/workspaces/{test_workspace.name}/sessions", json={"name": session_name} ) assert response.status_code == 200 if auth_client.auth_type == "empty": # Test with session-scoped JWT auth_client.headers["Authorization"] = ( - f"Bearer {create_jwt(JWTParams(se=session_id))}" + f"Bearer {create_jwt(JWTParams(s=session_name))}" ) - response = auth_client.get( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions?session_id={session_id}" + response = auth_client.post( + f"/v1/workspaces/{test_workspace.name}/sessions", + json={"name": session_name}, ) assert response.status_code == 200 - response = auth_client.get( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions" - ) - - assert response.status_code == 200 - - assert response.json()["id"] == session_id - - # Test with wrong session_id (should be 401 since we have a session-scoped JWT) + # Test with wrong session_name (should be 401 since we have a session-scoped JWT) assert ( - auth_client.get( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions?session_id={generate_nanoid()}" + auth_client.post( + f"/v1/workspaces/{test_workspace.name}/sessions", + json={"name": generate_nanoid()}, ).status_code == 401 ) - # Test with user-scoped JWT + # Test with peer-scoped JWT auth_client.headers["Authorization"] = ( - f"Bearer {create_jwt(JWTParams(us=test_user.public_id))}" + f"Bearer {create_jwt(JWTParams(p=test_peer.name))}" ) assert ( - auth_client.get( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions?session_id={session_id}" - ).status_code - == 200 - ) - assert ( - auth_client.get( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions" - ).status_code - == 401 - ) - - # Test with app-scoped JWT - auth_client.headers["Authorization"] = ( - f"Bearer {create_jwt(JWTParams(ap=test_app.public_id))}" - ) - - assert ( - auth_client.get( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions?session_id={session_id}" + auth_client.post( + f"/v1/workspaces/{test_workspace.name}/sessions", + json={"name": session_name}, ).status_code == 200 ) - assert ( - auth_client.get( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions" - ).status_code - == 401 + # Test with workspace-scoped JWT + auth_client.headers["Authorization"] = ( + f"Bearer {create_jwt(JWTParams(w=test_workspace.name))}" ) - # Test with wrong session_id (should be 404 since we have an app-scoped JWT) assert ( - auth_client.get( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions?session_id={generate_nanoid()}" + auth_client.post( + f"/v1/workspaces/{test_workspace.name}/sessions", + json={"name": session_name}, + ).status_code + == 200 + ) + + # Test with wrong session_name using DELETE endpoint (should be 404 since session doesn't exist) + wrong_session_name = generate_nanoid() + assert ( + auth_client.delete( + f"/v1/workspaces/{test_workspace.name}/sessions/{wrong_session_name}" ).status_code == 404 ) - - -def test_create_collection(auth_client, sample_data) -> None: - test_app, test_user = sample_data - - if auth_client.auth_type == "empty": - # For non-admin, include the app_id and user_id in the JWT - auth_client.headers["Authorization"] = ( - f"Bearer {create_jwt(JWTParams(ap=test_app.public_id, us=test_user.public_id))}" - ) - - response = auth_client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections", - json={"name": "test_collection", "metadata": {}}, - ) - - # Only admin JWT or JWT with matching app_id and user_id should be allowed - if auth_client.auth_type in ["admin", "empty"]: - assert response.status_code == 200 - else: - assert response.status_code == 401 - - # Remove app_id from header and make sure user-scoped key works too - if auth_client.auth_type == "empty": - auth_client.headers["Authorization"] = ( - f"Bearer {create_jwt(JWTParams(us=test_user.public_id))}" - ) - - response = auth_client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections", - json={"name": "test_collection2", "metadata": {}}, - ) - - assert response.status_code == 200 - - # Remove user_id from header and make sure app-scoped key works too - if auth_client.auth_type == "empty": - auth_client.headers["Authorization"] = ( - f"Bearer {create_jwt(JWTParams(ap=test_app.public_id))}" - ) - - response = auth_client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections", - json={"name": "test_collection3", "metadata": {}}, - ) - - assert response.status_code == 200 - - -def test_get_collection_by_id_with_auth(auth_client, sample_data) -> None: - test_app, test_user = sample_data - - # First create a collection - if auth_client.auth_type == "empty": - auth_client.headers["Authorization"] = ( - f"Bearer {create_jwt(JWTParams(ap=test_app.public_id, us=test_user.public_id))}" - ) - - create_response = auth_client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections", - json={"name": "test_collection_get", "metadata": {}}, - ) - - if auth_client.auth_type not in ["admin", "empty"]: - assert create_response.status_code == 401 - return - - assert create_response.status_code == 200 - collection_id = create_response.json()["id"] - - # Test with app and user scoped JWT - response = auth_client.get( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections?collection_id={collection_id}" - ) - assert response.status_code == 200 - - # Test with collection-scoped JWT - if auth_client.auth_type == "empty": - auth_client.headers["Authorization"] = ( - f"Bearer {create_jwt(JWTParams(co=collection_id))}" - ) - - response = auth_client.get( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections?collection_id={collection_id}" - ) - assert response.status_code == 200 - - # Test auto resolution of ID - response2 = auth_client.get( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections" - ) - assert response2.status_code == 200 - assert response2.json()["id"] == collection_id - - -def test_get_collection_by_name_with_auth(auth_client, sample_data) -> None: - test_app, test_user = sample_data - collection_name = f"test_collection_{generate_nanoid()}" - - # First create a collection - if auth_client.auth_type == "empty": - auth_client.headers["Authorization"] = ( - f"Bearer {create_jwt(JWTParams(ap=test_app.public_id, us=test_user.public_id))}" - ) - - create_response = auth_client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections", - json={"name": collection_name, "metadata": {}}, - ) - - if auth_client.auth_type not in ["admin", "empty"]: - assert create_response.status_code == 401 - return - - assert create_response.status_code == 200 - - # Test with app and user scoped JWT - response = auth_client.get( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections/name/{collection_name}" - ) - assert response.status_code == 200 - - -def test_create_document_with_auth(auth_client, sample_data) -> None: - test_app, test_user = sample_data - - # First create a collection - if auth_client.auth_type == "empty": - auth_client.headers["Authorization"] = ( - f"Bearer {create_jwt(JWTParams(ap=test_app.public_id, us=test_user.public_id))}" - ) - - create_collection_response = auth_client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections", - json={"name": "test_collection_docs", "metadata": {}}, - ) - - if auth_client.auth_type not in ["admin", "empty"]: - assert create_collection_response.status_code == 401 - return - - assert create_collection_response.status_code == 200 - collection_id = create_collection_response.json()["id"] - - # Create document with app and user scoped JWT - response = auth_client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections/{collection_id}/documents", - json={"content": "Test document content", "metadata": {"doc_key": "doc_value"}}, - ) - assert response.status_code == 200 - - # Test with collection-scoped JWT - if auth_client.auth_type == "empty": - auth_client.headers["Authorization"] = ( - f"Bearer {create_jwt(JWTParams(co=collection_id))}" - ) - - response = auth_client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections/{collection_id}/documents", - json={"content": "Test document with collection JWT", "metadata": {}}, - ) - assert response.status_code == 200 - - -def test_get_document_with_auth(auth_client, sample_data) -> None: - test_app, test_user = sample_data - - # First create a collection and document - if auth_client.auth_type == "empty": - auth_client.headers["Authorization"] = ( - f"Bearer {create_jwt(JWTParams(ap=test_app.public_id, us=test_user.public_id))}" - ) - - create_collection_response = auth_client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections", - json={"name": "test_collection_get_doc", "metadata": {}}, - ) - - if auth_client.auth_type not in ["admin", "empty"]: - assert create_collection_response.status_code == 401 - return - - assert create_collection_response.status_code == 200 - collection_id = create_collection_response.json()["id"] - - create_doc_response = auth_client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections/{collection_id}/documents", - json={"content": "Test document for retrieval", "metadata": {}}, - ) - assert create_doc_response.status_code == 200 - document_id = create_doc_response.json()["id"] - - # Get document with app and user scoped JWT - response = auth_client.get( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections/{collection_id}/documents/{document_id}" - ) - assert response.status_code == 200 - - # Test with collection-scoped JWT - if auth_client.auth_type == "empty": - auth_client.headers["Authorization"] = ( - f"Bearer {create_jwt(JWTParams(co=collection_id))}" - ) - - response = auth_client.get( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections/{collection_id}/documents/{document_id}" - ) - assert response.status_code == 200 diff --git a/tests/routes/test_sessions.py b/tests/routes/test_sessions.py index 72447340..1478acfd 100644 --- a/tests/routes/test_sessions.py +++ b/tests/routes/test_sessions.py @@ -1,25 +1,53 @@ -import pytest - -from src import models # Import your SQLAlchemy models +from nanoid import generate as generate_nanoid -def test_create_session(client, sample_data): - test_app, test_user = sample_data +def test_get_or_create_session(client, sample_data): + # Test get or create session + test_workspace, test_peer = sample_data + + # Test creating a new session with no parameters response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions", - json={}, + f"/v1/workspaces/{test_workspace.name}/sessions", + json={"id": str(generate_nanoid())}, ) assert response.status_code == 200 data = response.json() + assert isinstance(data["id"], str) assert data["metadata"] == {} - assert "id" in data + assert data["workspace_id"] == test_workspace.name + + # Test creating a session with a specific id and peer_names (should get or create) + session_id = str(generate_nanoid()) + response2 = client.post( + f"/v1/workspaces/{test_workspace.name}/sessions", + json={"id": session_id, "peer_names": {test_peer.name: {}}}, + ) + assert response2.status_code == 200 + data2 = response2.json() + assert data2["id"] == session_id + assert data2["metadata"] == {} + assert data2["workspace_id"] == test_workspace.name + + # Test getting the same session again (should return the same session) + response3 = client.post( + f"/v1/workspaces/{test_workspace.name}/sessions", + json={"id": session_id, "peer_names": {test_peer.name: {}}}, + ) + assert response3.status_code == 200 + data3 = response3.json() + assert data3["id"] == session_id + assert data3["metadata"] == {} + assert data3["workspace_id"] == test_workspace.name def test_create_session_with_metadata(client, sample_data): - test_app, test_user = sample_data + test_workspace, test_peer = sample_data + session_id = str(generate_nanoid()) response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions", + f"/v1/workspaces/{test_workspace.name}/sessions", json={ + "id": session_id, + "peer_names": {test_peer.name: {}}, "metadata": {"session_key": "session_value"}, }, ) @@ -27,15 +55,111 @@ def test_create_session_with_metadata(client, sample_data): data = response.json() assert data["metadata"] == {"session_key": "session_value"} assert "id" in data + assert data["id"] == session_id + assert data["workspace_id"] == test_workspace.name -@pytest.mark.asyncio -async def test_get_sessions(client, db_session, sample_data): - test_app, test_user = sample_data - # Create a test session +def test_create_session_with_configuration(client, sample_data): + """Test session creation with configuration parameter""" + test_workspace, test_peer = sample_data + session_id = str(generate_nanoid()) + configuration = {"experimental_feature": True, "beta_mode": False} + response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions", + f"/v1/workspaces/{test_workspace.name}/sessions", json={ + "id": session_id, + "peer_names": {test_peer.name: {}}, + "configuration": configuration, + }, + ) + assert response.status_code == 200 + data = response.json() + assert data["configuration"] == configuration + assert data["id"] == session_id + assert data["workspace_id"] == test_workspace.name + + +def test_create_session_with_all_optional_params(client, sample_data): + """Test session creation with all optional parameters""" + test_workspace, test_peer = sample_data + session_id = str(generate_nanoid()) + metadata = {"key": "value", "number": 42} + configuration = {"feature1": True, "feature2": False} + + response = client.post( + f"/v1/workspaces/{test_workspace.name}/sessions", + json={ + "id": session_id, + "peer_names": {test_peer.name: {}}, + "metadata": metadata, + "configuration": configuration, + }, + ) + assert response.status_code == 200 + data = response.json() + assert data["metadata"] == metadata + assert data["configuration"] == configuration + assert data["id"] == session_id + assert data["workspace_id"] == test_workspace.name + + +def test_create_session_with_too_many_peers(client, sample_data, caplog): + test_workspace, test_peer = sample_data + # create 10 peers + peer_names = [test_peer.name] + for _ in range(10): + peer_name = str(generate_nanoid()) + response = client.post( + f"/v1/workspaces/{test_workspace.name}/peers", + json={"name": peer_name, "metadata": {}}, + ) + assert response.status_code == 200 + peer_names.append(peer_name) + + # create session with 11 peers + response = client.post( + f"/v1/workspaces/{test_workspace.name}/sessions", + json={ + "id": str(generate_nanoid()), + "peer_names": {peer_name: {} for peer_name in peer_names}, + }, + ) + assert response.status_code == 422 + assert "Failed to get or create session" in caplog.text + + session_response = client.post( + f"/v1/workspaces/{test_workspace.name}/sessions/list", + json={"filter": {"name": "test_session"}}, + ) + assert session_response.status_code == 200 + assert len(session_response.json()["items"]) == 0 + + # Remove one peer from our list + peer_names.pop() + # Attempt to create session with same name + response = client.post( + f"/v1/workspaces/{test_workspace.name}/sessions", + json={ + "id": "test_session", + "peer_names": {peer_name: {} for peer_name in peer_names}, + }, + ) + assert response.status_code == 200 + data = response.json() + assert data["id"] == "test_session" + assert data["workspace_id"] == test_workspace.name + + +def test_get_sessions(client, sample_data): + test_workspace, test_peer = sample_data + # Create a test session + session_id = str(generate_nanoid()) + response = client.post( + f"/v1/workspaces/{test_workspace.name}/sessions", + json={ + "id": session_id, + "peer_names": {test_peer.name: {}}, "metadata": {"test_key": "test_value"}, }, ) @@ -43,9 +167,9 @@ async def test_get_sessions(client, db_session, sample_data): data = response.json() assert data["metadata"] == {"test_key": "test_value"} assert "id" in data - + assert data["workspace_id"] == test_workspace.name response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions/list", + f"/v1/workspaces/{test_workspace.name}/sessions/list", json={"filter": {"test_key": "test_value"}}, ) assert response.status_code == 200 @@ -53,39 +177,62 @@ async def test_get_sessions(client, db_session, sample_data): assert "items" in data assert len(data["items"]) > 0 assert data["items"][0]["metadata"] == {"test_key": "test_value"} + assert data["items"][0]["workspace_id"] == test_workspace.name -@pytest.mark.asyncio -async def test_empty_update_session(client, db_session, sample_data): - test_app, test_user = sample_data - # Create a test session - test_session = models.Session( - user_id=test_user.public_id, h_metadata={}, app_id=test_app.public_id +def test_get_sessions_with_is_active_filter(client, sample_data): + """Test session listing with is_active parameter""" + test_workspace, test_peer = sample_data + + # Create and then delete a session to have inactive session + session_id = str(generate_nanoid()) + client.post( + f"/v1/workspaces/{test_workspace.name}/sessions", + json={"id": session_id, "peer_names": {test_peer.name: {}}}, ) - db_session.add(test_session) - await db_session.commit() + client.delete(f"/v1/workspaces/{test_workspace.name}/sessions/{session_id}") + + # Test getting inactive sessions + response = client.post( + f"/v1/workspaces/{test_workspace.name}/sessions/list", json={"is_active": False} + ) + assert response.status_code == 200 + data = response.json() + assert "items" in data + # Should find at least our deleted session + inactive_sessions = [s for s in data["items"] if not s["is_active"]] + assert len(inactive_sessions) > 0 + + +def test_get_sessions_with_empty_filter(client, sample_data): + """Test session listing with empty filter object""" + test_workspace, test_peer = sample_data + + response = client.post( + f"/v1/workspaces/{test_workspace.name}/sessions/list", json={"filter": {}} + ) + assert response.status_code == 200 + data = response.json() + assert "items" in data + assert isinstance(data["items"], list) + + +def test_update_delete_metadata(client, sample_data): + test_workspace, test_peer = sample_data + # Create a test session + session_id = str(generate_nanoid()) + response = client.post( + f"/v1/workspaces/{test_workspace.name}/sessions", + json={ + "id": session_id, + "peer_names": {test_peer.name: {}}, + "metadata": {"default": "value"}, + }, + ) + assert response.status_code == 200 response = client.put( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions/{test_session.public_id}", - json={}, - ) - assert response.status_code == 422 - - -@pytest.mark.asyncio -async def test_update_delete_metadata(client, db_session, sample_data): - test_app, test_user = sample_data - # Create a test session - test_session = models.Session( - user_id=test_user.public_id, - h_metadata={"default": "value"}, - app_id=test_app.public_id, - ) - db_session.add(test_session) - await db_session.commit() - - response = client.put( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions/{test_session.public_id}", + f"/v1/workspaces/{test_workspace.name}/sessions/{session_id}", json={"metadata": {}}, ) assert response.status_code == 200 @@ -93,18 +240,21 @@ async def test_update_delete_metadata(client, db_session, sample_data): assert data["metadata"] == {} -@pytest.mark.asyncio -async def test_update_session(client, db_session, sample_data): - test_app, test_user = sample_data +def test_update_session(client, sample_data): + test_workspace, test_peer = sample_data # Create a test session - test_session = models.Session( - user_id=test_user.public_id, h_metadata={}, app_id=test_app.public_id + session_id = str(generate_nanoid()) + response = client.post( + f"/v1/workspaces/{test_workspace.name}/sessions", + json={ + "id": session_id, + "peer_names": {test_peer.name: {}}, + }, ) - db_session.add(test_session) - await db_session.commit() + assert response.status_code == 200 response = client.put( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions/{test_session.public_id}", + f"/v1/workspaces/{test_workspace.name}/sessions/{session_id}", json={"metadata": {"new_key": "new_value"}}, ) assert response.status_code == 200 @@ -112,398 +262,546 @@ async def test_update_session(client, db_session, sample_data): assert data["metadata"] == {"new_key": "new_value"} -@pytest.mark.asyncio -async def test_delete_session(client, db_session, sample_data): - test_app, test_user = sample_data - # Create a test session - test_session = models.Session( - user_id=test_user.public_id, h_metadata={}, app_id=test_app.public_id +def test_update_session_with_configuration(client, sample_data): + """Test session update with configuration parameter""" + test_workspace, test_peer = sample_data + session_id = str(generate_nanoid()) + + # Create session + client.post( + f"/v1/workspaces/{test_workspace.name}/sessions", + json={"id": session_id, "peer_names": {test_peer.name: {}}}, ) - db_session.add(test_session) - await db_session.commit() + + # Update with configuration + configuration = {"new_feature": True, "legacy_feature": False} + response = client.put( + f"/v1/workspaces/{test_workspace.name}/sessions/{session_id}", + json={"metadata": {}, "configuration": configuration}, + ) + assert response.status_code == 200 + data = response.json() + assert data["configuration"] == configuration + + +def test_update_session_with_all_optional_params(client, sample_data): + """Test session update with both metadata and configuration""" + test_workspace, test_peer = sample_data + session_id = str(generate_nanoid()) + + # Create session + client.post( + f"/v1/workspaces/{test_workspace.name}/sessions", + json={"id": session_id, "peer_names": {test_peer.name: {}}}, + ) + + # Update with all params + metadata = {"updated_key": "updated_value"} + configuration = {"experimental": True, "beta": True} + response = client.put( + f"/v1/workspaces/{test_workspace.name}/sessions/{session_id}", + json={"metadata": metadata, "configuration": configuration}, + ) + assert response.status_code == 200 + data = response.json() + assert data["metadata"] == metadata + assert data["configuration"] == configuration + + +def test_update_session_with_null_configuration(client, sample_data): + """Test session update with null configuration""" + test_workspace, test_peer = sample_data + session_id = str(generate_nanoid()) + + # Create session with configuration + client.post( + f"/v1/workspaces/{test_workspace.name}/sessions", + json={ + "id": session_id, + "peer_names": {test_peer.name: {}}, + "configuration": {"temp": "value"}, + }, + ) + + # Update with null configuration + response = client.put( + f"/v1/workspaces/{test_workspace.name}/sessions/{session_id}", + json={"metadata": {}, "configuration": None}, + ) + assert response.status_code == 200 + data = response.json() + assert "configuration" in data + + +def test_delete_session(client, sample_data): + test_workspace, test_peer = sample_data + # Create a test session + session_id = str(generate_nanoid()) + response = client.post( + f"/v1/workspaces/{test_workspace.name}/sessions", + json={ + "id": session_id, + "peer_names": {test_peer.name: {}}, + }, + ) + assert response.status_code == 200 + response = client.delete( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions/{test_session.public_id}" + f"/v1/workspaces/{test_workspace.name}/sessions/{session_id}" ) assert response.status_code == 200 - response = client.get( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions?session_id={test_session.public_id}" + + # Check that session is marked as inactive + response = client.post( + f"/v1/workspaces/{test_workspace.name}/sessions/list", + json={"is_active": False}, ) data = response.json() - assert data["is_active"] is False + # Find our session in the inactive sessions + inactive_session = next((s for s in data["items"] if s["id"] == session_id), None) + assert inactive_session is not None + assert inactive_session["is_active"] is False -@pytest.mark.asyncio -async def test_clone_session(client, db_session, sample_data): - test_app, test_user = sample_data +def test_clone_session(client, sample_data): + test_workspace, test_peer = sample_data # Create a test session - test_session = models.Session( - user_id=test_user.public_id, - h_metadata={"test": "key"}, - app_id=test_app.public_id, + session_id = str(generate_nanoid()) + response = client.post( + f"/v1/workspaces/{test_workspace.name}/sessions", + json={ + "id": session_id, + "peer_names": {test_peer.name: {}}, + "metadata": {"test": "key"}, + }, ) - db_session.add(test_session) - await db_session.commit() + assert response.status_code == 200 - test_message = models.Message( - session_id=test_session.public_id, - content="Test message", - is_user=True, - h_metadata={"key": "value"}, - app_id=test_app.public_id, - user_id=test_user.public_id, + # Create some messages in the session + response = client.post( + f"/v1/workspaces/{test_workspace.name}/sessions/{session_id}/messages", + json={ + "messages": [ + { + "content": "Test message", + "peer_id": test_peer.name, + "metadata": {"key": "value"}, + }, + { + "content": "Test message 2", + "peer_id": test_peer.name, + "metadata": {"key": "value2"}, + }, + ] + }, ) - test_message2 = models.Message( - session_id=test_session.public_id, - content="Test message 2", - is_user=True, - h_metadata={"key": "value2"}, - app_id=test_app.public_id, - user_id=test_user.public_id, - ) - db_session.add(test_message) - db_session.add(test_message2) - await db_session.commit() + assert response.status_code == 200 response = client.get( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions/{test_session.public_id}/clone", + f"/v1/workspaces/{test_workspace.name}/sessions/{session_id}/clone", ) assert response.status_code == 200 data = response.json() assert data["metadata"] == {"test": "key"} - print(data) - + # Check messages were cloned response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions/{data['id']}/messages/list", + f"/v1/workspaces/{test_workspace.name}/sessions/{data['id']}/messages/list", json={}, ) assert response.status_code == 200 data = response.json() assert "items" in data - assert len(data["items"]) > 0 - assert len(data["items"]) == 2 + assert len(data["items"]) >= 2 assert data["items"][0]["content"] == "Test message" - assert data["items"][0]["is_user"] is True assert data["items"][0]["metadata"] == {"key": "value"} assert data["items"][1]["content"] == "Test message 2" - assert data["items"][1]["is_user"] is True assert data["items"][1]["metadata"] == {"key": "value2"} -@pytest.mark.asyncio -async def test_partial_clone_session(client, db_session, sample_data): - test_app, test_user = sample_data - # Create a test session - test_session = models.Session( - user_id=test_user.public_id, - h_metadata={"test": "key"}, - app_id=test_app.public_id, - ) - db_session.add(test_session) - await db_session.commit() +def test_clone_session_with_cutoff(client, sample_data): + """Test session cloning with message cutoff parameter""" + test_workspace, test_peer = sample_data + session_id = str(generate_nanoid()) - test_message = models.Message( - session_id=test_session.public_id, - content="Test message", - is_user=True, - h_metadata={"key": "value"}, - app_id=test_app.public_id, - user_id=test_user.public_id, - ) - test_message2 = models.Message( - session_id=test_session.public_id, - content="Test message 2", - is_user=True, - h_metadata={"key": "value2"}, - app_id=test_app.public_id, - user_id=test_user.public_id, + # Create session + client.post( + f"/v1/workspaces/{test_workspace.name}/sessions", + json={"id": session_id, "peer_names": {test_peer.name: {}}}, ) - test_message3 = models.Message( - session_id=test_session.public_id, - content="Test message 2", - is_user=True, - h_metadata={"key": "value2"}, - app_id=test_app.public_id, - user_id=test_user.public_id, + # Create messages + response = client.post( + f"/v1/workspaces/{test_workspace.name}/sessions/{session_id}/messages", + json={ + "messages": [ + {"content": "Message 1", "peer_id": test_peer.name}, + {"content": "Message 2", "peer_id": test_peer.name}, + ] + }, ) + assert response.status_code == 200 + messages_data = response.json() + # The response is a list of messages, not a paginated response + first_message_id = messages_data[0]["id"] - db_session.add(test_message) - db_session.add(test_message2) - db_session.add(test_message3) - await db_session.commit() - + # Clone with cutoff at first message response = client.get( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions/{test_session.public_id}/clone?message_id={test_message2.public_id}", + f"/v1/workspaces/{test_workspace.name}/sessions/{session_id}/clone?message_id={first_message_id}", ) assert response.status_code == 200 data = response.json() - assert data["metadata"] == {"test": "key"} + assert "id" in data + +def test_add_peers_to_session(client, sample_data): + test_workspace, test_peer = sample_data + # Create another peer + peer2_name = str(generate_nanoid()) response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions/{data['id']}/messages/list", - json={}, - ) - - data = response.json() - assert len(data["items"]) > 0 - assert len(data["items"]) == 2 - - assert data["items"][0]["content"] == "Test message" - assert data["items"][0]["is_user"] is True - assert data["items"][0]["metadata"] == {"key": "value"} - - assert data["items"][1]["content"] == "Test message 2" - assert data["items"][1]["is_user"] is True - assert data["items"][1]["metadata"] == {"key": "value2"} - - -@pytest.mark.asyncio -async def test_deep_clone_session(client, db_session, sample_data): - test_app, test_user = sample_data - # Create a test session - test_session = models.Session( - user_id=test_user.public_id, - h_metadata={"test": "key"}, - app_id=test_app.public_id, - ) - db_session.add(test_session) - await db_session.commit() - - test_message = models.Message( - session_id=test_session.public_id, - content="Test message", - is_user=True, - h_metadata={"key": "value"}, - app_id=test_app.public_id, - user_id=test_user.public_id, - ) - test_message2 = models.Message( - session_id=test_session.public_id, - content="Test message 2", - is_user=True, - h_metadata={"key": "value2"}, - app_id=test_app.public_id, - user_id=test_user.public_id, - ) - db_session.add(test_message) - db_session.add(test_message2) - await db_session.commit() - - test_metamessage_1 = models.Metamessage( - user_id=test_user.public_id, - session_id=test_session.public_id, - message_id=test_message.public_id, - content="Test Metamessage 1", - h_metadata={}, - label="test_type", - app_id=test_app.public_id, - ) - test_metamessage_2 = models.Metamessage( - user_id=test_user.public_id, - session_id=test_session.public_id, - message_id=test_message.public_id, - content="Test Metamessage 2", - h_metadata={}, - label="test_type", - app_id=test_app.public_id, - ) - test_metamessage_3 = models.Metamessage( - user_id=test_user.public_id, - session_id=test_session.public_id, - message_id=test_message2.public_id, - content="Test Metamessage 3", - h_metadata={}, - label="test_type", - app_id=test_app.public_id, - ) - test_metamessage_4 = models.Metamessage( - user_id=test_user.public_id, - session_id=test_session.public_id, - message_id=test_message2.public_id, - content="Test Metamessage 4", - h_metadata={}, - app_id=test_app.public_id, - label="test_type_2", - ) - - db_session.add(test_metamessage_1) - db_session.add(test_metamessage_2) - db_session.add(test_metamessage_3) - db_session.add(test_metamessage_4) - await db_session.commit() - - response = client.get( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions/{test_session.public_id}/clone?deep_copy=true", + f"/v1/workspaces/{test_workspace.name}/peers", + json={"name": peer2_name, "metadata": {}}, ) assert response.status_code == 200 - data = response.json() - assert data["metadata"] == {"test": "key"} - - cloned_session_id = data["id"] + # Create a test session + session_id = str(generate_nanoid()) response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions/{cloned_session_id}/messages/list", - json={}, + f"/v1/workspaces/{test_workspace.name}/sessions", + json={ + "id": session_id, + "peer_names": {test_peer.name: {}}, + }, ) + assert response.status_code == 200 + # Add another peer to the session + response = client.post( + f"/v1/workspaces/{test_workspace.name}/sessions/{session_id}/peers", + json={peer2_name: {}}, + ) + assert response.status_code == 200 + + +def test_get_session_peers(client, sample_data): + test_workspace, test_peer = sample_data + # Create another peer + peer2_name = str(generate_nanoid()) + response = client.post( + f"/v1/workspaces/{test_workspace.name}/peers", + json={"name": peer2_name, "metadata": {}}, + ) + assert response.status_code == 200 + + # Create a test session with multiple peers + session_id = str(generate_nanoid()) + response = client.post( + f"/v1/workspaces/{test_workspace.name}/sessions", + json={ + "id": session_id, + "peer_names": {test_peer.name: {}, peer2_name: {}}, + }, + ) + assert response.status_code == 200 + + # Get peers from the session + response = client.get( + f"/v1/workspaces/{test_workspace.name}/sessions/{session_id}/peers", + ) assert response.status_code == 200 data = response.json() assert "items" in data - assert len(data["items"]) > 0 assert len(data["items"]) == 2 + peer_names = [peer["id"] for peer in data["items"]] + assert test_peer.name in peer_names + assert peer2_name in peer_names - assert data["items"][0]["content"] == "Test message" - assert data["items"][0]["is_user"] is True - assert data["items"][0]["metadata"] == {"key": "value"} - - assert data["items"][1]["content"] == "Test message 2" - assert data["items"][1]["is_user"] is True - assert data["items"][1]["metadata"] == {"key": "value2"} +def test_set_session_peers(client, sample_data): + test_workspace, test_peer = sample_data + # Create another peer + peer2_name = str(generate_nanoid()) response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/metamessages/list", - json={"session_id": cloned_session_id}, + f"/v1/workspaces/{test_workspace.name}/peers", + json={"name": peer2_name, "metadata": {}}, ) - assert response.status_code == 200 - data = response.json() - assert len(data["items"]) > 0 - assert len(data["items"]) == 4 - assert data["items"][0]["content"] == "Test Metamessage 1" - assert data["items"][0]["label"] == "test_type" - assert data["items"][0]["metamessage_type"] == "test_type" - assert data["items"][0]["metadata"] == {} - assert data["items"][1]["content"] == "Test Metamessage 2" - assert data["items"][1]["label"] == "test_type" - assert data["items"][1]["metamessage_type"] == "test_type" - assert data["items"][1]["metadata"] == {} - assert data["items"][2]["content"] == "Test Metamessage 3" - assert data["items"][2]["label"] == "test_type" - assert data["items"][2]["metamessage_type"] == "test_type" - assert data["items"][2]["metadata"] == {} - assert data["items"][3]["content"] == "Test Metamessage 4" - assert data["items"][3]["label"] == "test_type_2" - assert data["items"][3]["metamessage_type"] == "test_type_2" - assert data["items"][3]["metadata"] == {} - -@pytest.mark.asyncio -async def test_partial_deep_clone_session(client, db_session, sample_data): - test_app, test_user = sample_data # Create a test session - test_session = models.Session( - user_id=test_user.public_id, - h_metadata={"test": "key"}, - app_id=test_app.public_id, - ) - db_session.add(test_session) - await db_session.commit() - - test_message = models.Message( - session_id=test_session.public_id, - content="Test message", - is_user=True, - h_metadata={"key": "value"}, - app_id=test_app.public_id, - user_id=test_user.public_id, - ) - test_message2 = models.Message( - session_id=test_session.public_id, - content="Test message 2", - is_user=True, - h_metadata={"key": "value2"}, - app_id=test_app.public_id, - user_id=test_user.public_id, - ) - db_session.add(test_message) - db_session.add(test_message2) - await db_session.commit() - - test_metamessage_1 = models.Metamessage( - user_id=test_user.public_id, - session_id=test_session.public_id, - message_id=test_message.public_id, - content="Test Metamessage 1", - h_metadata={}, - app_id=test_app.public_id, - label="test_type", - ) - test_metamessage_2 = models.Metamessage( - user_id=test_user.public_id, - session_id=test_session.public_id, - message_id=test_message.public_id, - content="Test Metamessage 2", - h_metadata={}, - app_id=test_app.public_id, - label="test_type", - ) - test_metamessage_3 = models.Metamessage( - user_id=test_user.public_id, - session_id=test_session.public_id, - message_id=test_message2.public_id, - content="Test Metamessage 3", - h_metadata={}, - app_id=test_app.public_id, - label="test_type", - ) - test_metamessage_4 = models.Metamessage( - user_id=test_user.public_id, - session_id=test_session.public_id, - message_id=test_message2.public_id, - content="Test Metamessage 4", - h_metadata={}, - app_id=test_app.public_id, - label="test_type_2", - ) - - db_session.add(test_metamessage_1) - db_session.add(test_metamessage_2) - db_session.add(test_metamessage_3) - db_session.add(test_metamessage_4) - await db_session.commit() - - response = client.get( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions/{test_session.public_id}/clone?deep_copy=true&message_id={test_message.public_id}", + session_id = str(generate_nanoid()) + response = client.post( + f"/v1/workspaces/{test_workspace.name}/sessions", + json={ + "id": session_id, + "peer_names": {test_peer.name: {}}, + }, ) assert response.status_code == 200 - data = response.json() - assert data["metadata"] == {"test": "key"} - cloned_session_id = data["id"] - - response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions/{cloned_session_id}/messages/list", - json={}, + # Set peers for the session (should replace existing peers) + response = client.put( + f"/v1/workspaces/{test_workspace.name}/sessions/{session_id}/peers", + json={peer2_name: {}}, ) + assert response.status_code == 200 + # Check that only the new peer is in the session + response = client.get( + f"/v1/workspaces/{test_workspace.name}/sessions/{session_id}/peers", + ) assert response.status_code == 200 data = response.json() assert "items" in data - assert len(data["items"]) > 0 assert len(data["items"]) == 1 + assert data["items"][0]["id"] == peer2_name - assert data["items"][0]["content"] == "Test message" - assert data["items"][0]["is_user"] is True - assert data["items"][0]["metadata"] == {"key": "value"} +def test_set_session_peers_with_limit(client, sample_data, caplog): + test_workspace, test_peer = sample_data + + # Create a test session with multiple peers + session_id = str(generate_nanoid()) response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/metamessages/list", - json={"session_id": cloned_session_id}, + f"/v1/workspaces/{test_workspace.name}/sessions", + json={ + "id": session_id, + }, ) + assert response.status_code == 200 + # create 10 peers + peer_names = [test_peer.name] + for _ in range(10): + peer_name = str(generate_nanoid()) + response = client.post( + f"/v1/workspaces/{test_workspace.name}/peers", + json={"name": peer_name, "metadata": {}}, + ) + assert response.status_code == 200 + peer_names.append(peer_name) + + # set peers with 11 peers (as a dict of peer_name: {}) + peers_dict = {peer_name: {} for peer_name in peer_names} + response = client.put( + f"/v1/workspaces/{test_workspace.name}/sessions/{session_id}/peers", + json=peers_dict, + ) + assert response.status_code == 404 + assert "Failed to set peers for session" in caplog.text + + +def test_remove_peers_from_session(client, sample_data): + test_workspace, test_peer = sample_data + # Create another peer + peer2_name = str(generate_nanoid()) + response = client.post( + f"/v1/workspaces/{test_workspace.name}/peers", + json={"name": peer2_name, "metadata": {}}, + ) + assert response.status_code == 200 + + # Create a test session with multiple peers + session_id = str(generate_nanoid()) + response = client.post( + f"/v1/workspaces/{test_workspace.name}/sessions", + json={ + "id": session_id, + "peer_names": {test_peer.name: {}, peer2_name: {}}, + }, + ) + assert response.status_code == 200 + + # Remove one peer from the session + response = client.request( + "DELETE", + f"/v1/workspaces/{test_workspace.name}/sessions/{session_id}/peers", + json=[test_peer.name], + ) + assert response.status_code == 200 + + # Check that only the remaining peer is in the session + response = client.get( + f"/v1/workspaces/{test_workspace.name}/sessions/{session_id}/peers", + ) assert response.status_code == 200 data = response.json() - assert len(data["items"]) > 0 - assert len(data["items"]) == 2 - assert data["items"][0]["content"] == "Test Metamessage 1" - assert data["items"][0]["label"] == "test_type" - assert data["items"][0]["metamessage_type"] == "test_type" - assert data["items"][0]["metadata"] == {} - assert data["items"][1]["content"] == "Test Metamessage 2" - assert data["items"][1]["label"] == "test_type" - assert data["items"][1]["metamessage_type"] == "test_type" - assert data["items"][1]["metadata"] == {} + assert "items" in data + assert len(data["items"]) == 1 + assert data["items"][0]["id"] == peer2_name + + +def test_get_session_context(client, sample_data): + """Test the session context endpoint""" + test_workspace, test_peer = sample_data + session_id = str(generate_nanoid()) + + # Create session + client.post( + f"/v1/workspaces/{test_workspace.name}/sessions", + json={"id": session_id, "peers": {test_peer.name: {}}}, + ) + + # Add some messages to have context + client.post( + f"/v1/workspaces/{test_workspace.name}/sessions/{session_id}/messages", + json={ + "messages": [ + {"content": "Test message 1", "peer_id": test_peer.name}, + {"content": "Test message 2", "peer_id": test_peer.name}, + ] + }, + ) + + # Get context + response = client.get( + f"/v1/workspaces/{test_workspace.name}/sessions/{session_id}/context", + ) + assert response.status_code == 200 + data = response.json() + # SessionContext schema serializes "name" as "id" + assert "id" in data + assert "messages" in data + assert "summary" in data + assert data["id"] == session_id + assert isinstance(data["messages"], list) + assert data["summary"] == "" # Default is empty when summary=False + + +def test_get_session_context_with_summary(client, sample_data): + """Test session context with summary parameter""" + test_workspace, test_peer = sample_data + session_id = str(generate_nanoid()) + + # Create session with messages + client.post( + f"/v1/workspaces/{test_workspace.name}/sessions", + json={"id": session_id, "peers": {test_peer.name: {}}}, + ) + + # Get context with summary + response = client.get( + f"/v1/workspaces/{test_workspace.name}/sessions/{session_id}/context?summary=true", + ) + assert response.status_code == 200 + data = response.json() + assert "summary" in data + + +def test_get_session_context_with_tokens(client, sample_data): + """Test session context with token limit parameter""" + test_workspace, test_peer = sample_data + session_id = str(generate_nanoid()) + + # Create session + client.post( + f"/v1/workspaces/{test_workspace.name}/sessions", + json={"id": session_id, "peers": {test_peer.name: {}}}, + ) + + # Get context with token limit + response = client.get( + f"/v1/workspaces/{test_workspace.name}/sessions/{session_id}/context?tokens=100", + ) + assert response.status_code == 200 + data = response.json() + assert "messages" in data + assert isinstance(data["messages"], list) + + +def test_get_session_context_with_all_params(client, sample_data): + """Test session context with both summary and tokens parameters""" + test_workspace, test_peer = sample_data + session_id = str(generate_nanoid()) + + # Create session + client.post( + f"/v1/workspaces/{test_workspace.name}/sessions", + json={"id": session_id, "peers": {test_peer.name: {}}}, + ) + + # Get context with all parameters + response = client.get( + f"/v1/workspaces/{test_workspace.name}/sessions/{session_id}/context?tokens=100&summary=true", + ) + assert response.status_code == 200 + data = response.json() + assert "messages" in data + assert "summary" in data + + +def test_search_session(client, sample_data): + """Test the session search functionality""" + test_workspace, test_peer = sample_data + session_id = str(generate_nanoid()) + + # Create session + client.post( + f"/v1/workspaces/{test_workspace.name}/sessions", + json={"id": session_id, "peers": {test_peer.name: {}}}, + ) + + # Add messages to search through + client.post( + f"/v1/workspaces/{test_workspace.name}/sessions/{session_id}/messages", + json={ + "messages": [ + {"content": "Search this content", "peer_id": test_peer.name}, + {"content": "Another message", "peer_id": test_peer.name}, + ] + }, + ) + + # Search with a query + response = client.post( + f"/v1/workspaces/{test_workspace.name}/sessions/{session_id}/search", + json="search query", + ) + assert response.status_code == 200 + data = response.json() + + # Response should have pagination structure + assert "items" in data + assert "total" in data + assert "page" in data + assert "size" in data + assert isinstance(data["items"], list) + + +def test_search_session_empty_query(client, sample_data): + """Test session search with empty query""" + test_workspace, test_peer = sample_data + session_id = str(generate_nanoid()) + + # Create session + client.post( + f"/v1/workspaces/{test_workspace.name}/sessions", + json={"id": session_id, "peer_names": {test_peer.name: {}}}, + ) + + # Search with empty query + response = client.post( + f"/v1/workspaces/{test_workspace.name}/sessions/{session_id}/search", json="" + ) + assert response.status_code == 200 + data = response.json() + + # Response should still have proper pagination structure + assert "items" in data + assert isinstance(data["items"], list) + + +def test_search_session_nonexistent(client, sample_data): + """Test searching a session that doesn't exist""" + test_workspace, _ = sample_data + nonexistent_session_id = str(generate_nanoid()) + + response = client.post( + f"/v1/workspaces/{test_workspace.name}/sessions/{nonexistent_session_id}/search", + json="test query", + ) + # This should probably return 404 or handle gracefully + # The exact behavior depends on the crud.search implementation + assert response.status_code in [200, 404, 422] diff --git a/tests/routes/test_users.py b/tests/routes/test_users.py deleted file mode 100644 index 6cce77e9..00000000 --- a/tests/routes/test_users.py +++ /dev/null @@ -1,88 +0,0 @@ -from nanoid import generate as generate_nanoid - - -def test_create_user(client, sample_data): - test_app, _ = sample_data - name = str(generate_nanoid()) - response = client.post( - f"/v1/apps/{test_app.public_id}/users", - json={"name": name, "metadata": {"user_key": "user_value"}}, - ) - assert response.status_code == 200 - data = response.json() - assert data["name"] == name - assert data["metadata"] == {"user_key": "user_value"} - assert "id" in data - - -def test_get_user_by_id(client, sample_data): - test_app, test_user = sample_data - response = client.get( - f"/v1/apps/{test_app.public_id}/users?user_id={test_user.public_id}" - ) - assert response.status_code == 200 - data = response.json() - assert data["name"] == test_user.name - assert data["id"] == str(test_user.public_id) - - -def test_get_user_by_name(client, sample_data): - test_app, test_user = sample_data - response = client.get(f"/v1/apps/{test_app.public_id}/users/name/{test_user.name}") - assert response.status_code == 200 - data = response.json() - assert data["name"] == test_user.name - assert data["id"] == str(test_user.public_id) - - -def test_get_or_create_user(client, sample_data): - test_app, _ = sample_data - name = str(generate_nanoid()) - response = client.get(f"/v1/apps/{test_app.public_id}/users/name/{name}") - assert response.status_code == 404 - response = client.get(f"/v1/apps/{test_app.public_id}/users/get_or_create/{name}") - assert response.status_code == 200 - data = response.json() - assert data["name"] == name - assert "id" in data - - -def test_get_users(client, sample_data): - test_app, _ = sample_data - response = client.post( - f"/v1/apps/{test_app.public_id}/users", - json={"name": str(generate_nanoid()), "metadata": {"user_key": "user_value"}}, - ) - response = client.post( - f"/v1/apps/{test_app.public_id}/users", - json={"name": str(generate_nanoid()), "metadata": {"user_key": "user_value"}}, - ) - response = client.post( - f"/v1/apps/{test_app.public_id}/users", - json={"name": str(generate_nanoid()), "metadata": {"user_key": "user_value2"}}, - ) - response = client.post( - f"/v1/apps/{test_app.public_id}/users/list", - json={"filter": {"user_key": "user_value"}}, - ) - assert response.status_code == 200 - data = response.json() - assert "items" in data - assert len(data["items"]) > 0 - assert len(data["items"]) == 2 - assert data["items"][0]["metadata"] == {"user_key": "user_value"} - - -def test_update_user(client, sample_data): - test_app, test_user = sample_data - new_name = str(generate_nanoid()) - response = client.put( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}", - json={"name": new_name, "metadata": {"new_key": "new_value"}}, - ) - assert response.status_code == 200 - data = response.json() - print(new_name) - print(data) - assert data["name"] == new_name - assert data["metadata"] == {"new_key": "new_value"} diff --git a/tests/routes/test_validation_api.py b/tests/routes/test_validation_api.py index c8b4d4c9..038dca89 100644 --- a/tests/routes/test_validation_api.py +++ b/tests/routes/test_validation_api.py @@ -1,9 +1,9 @@ from nanoid import generate as generate_nanoid -def test_app_validations_api(client): +def test_workspace_validations_api(client): # Test name too short - response = client.post("/v1/apps", json={"name": "", "metadata": {}}) + response = client.post("/v1/workspaces", json={"name": "", "metadata": {}}) assert response.status_code == 422 error = response.json()["detail"][0] assert error["loc"] == ["body", "name"] @@ -11,7 +11,7 @@ def test_app_validations_api(client): assert error["type"] == "string_too_short" # Test name too long - response = client.post("/v1/apps", json={"name": "a" * 101, "metadata": {}}) + response = client.post("/v1/workspaces", json={"name": "a" * 101, "metadata": {}}) assert response.status_code == 422 error = response.json()["detail"][0] assert error["loc"] == ["body", "name"] @@ -19,19 +19,21 @@ def test_app_validations_api(client): assert error["type"] == "string_too_long" # Test invalid metadata type - response = client.post("/v1/apps", json={"name": "test", "metadata": "not a dict"}) + response = client.post( + "/v1/workspaces", json={"name": "test", "metadata": "not a dict"} + ) assert response.status_code == 422 error = response.json()["detail"][0] assert error["loc"] == ["body", "metadata"] assert error["type"] == "dict_type" -def test_user_validations_api(client, sample_data): - test_app, _ = sample_data +def test_peer_validations_api(client, sample_data): + test_workspace, _ = sample_data # Test name too short response = client.post( - f"/v1/apps/{test_app.public_id}/users", json={"name": "", "metadata": {}} + f"/v1/workspaces/{test_workspace.name}/peers", json={"name": "", "metadata": {}} ) assert response.status_code == 422 error = response.json()["detail"][0] @@ -41,7 +43,8 @@ def test_user_validations_api(client, sample_data): # Test name too long response = client.post( - f"/v1/apps/{test_app.public_id}/users", json={"name": "a" * 101, "metadata": {}} + f"/v1/workspaces/{test_workspace.name}/peers", + json={"name": "a" * 101, "metadata": {}}, ) assert response.status_code == 422 error = response.json()["detail"][0] @@ -51,312 +54,49 @@ def test_user_validations_api(client, sample_data): def test_message_validations_api(client, sample_data): - test_app, test_user = sample_data + test_workspace, test_peer = sample_data # Create a test session first + session_id = str(generate_nanoid()) session_response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions", - json={"metadata": {}}, + f"/v1/workspaces/{test_workspace.name}/sessions", + json={"id": session_id, "peer_names": {test_peer.name: {}}}, ) - session_id = session_response.json()["id"] + assert session_response.status_code == 200 # Test content too long response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions/{session_id}/messages", - json={"content": "a" * 50001, "is_user": True, "metadata": {}}, + f"/v1/workspaces/{test_workspace.name}/sessions/{session_id}/messages", + json={ + "messages": [ + {"content": "a" * 50001, "peer_id": test_peer.name, "metadata": {}} + ] + }, ) assert response.status_code == 422 error = response.json()["detail"][0] - assert error["loc"] == ["body", "content"] + assert "content" in str(error["loc"]) assert error["msg"] == "String should have at most 50000 characters" assert error["type"] == "string_too_long" - # Test invalid is_user type - response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions/{session_id}/messages", - json={"content": "test", "is_user": "not a bool", "metadata": {}}, - ) - assert response.status_code == 422 - error = response.json()["detail"][0] - assert error["loc"] == ["body", "is_user"] - assert error["type"] == "bool_parsing" - - -def test_collection_validations_api(client, sample_data): - test_app, test_user = sample_data - - # Test name too short - response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections", - json={"name": "", "metadata": {}}, - ) - assert response.status_code == 422 - error = response.json()["detail"][0] - assert error["loc"] == ["body", "name"] - assert error["msg"] == "String should have at least 1 character" - assert error["type"] == "string_too_short" - - # Test name too long - response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections", - json={"name": "a" * 101, "metadata": {}}, - ) - assert response.status_code == 422 - error = response.json()["detail"][0] - assert error["loc"] == ["body", "name"] - assert error["msg"] == "String should have at most 100 characters" - assert error["type"] == "string_too_long" - - # Test 'honcho' name restriction - response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections", - json={"name": "honcho", "metadata": {}}, - ) - assert response.status_code == 422 - error = response.json()["detail"][0] - assert error["loc"] == ["body", "name"] - assert error["msg"] == "Value error, Collection name cannot be 'honcho'" - assert error["type"] == "value_error" - - -def test_document_validations_api(client, sample_data): - test_app, test_user = sample_data - # Create a collection first - collection_response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections", - json={"name": str(generate_nanoid()), "metadata": {}}, - ) - collection_id = collection_response.json()["id"] - - # Test content too short - response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections/{collection_id}/documents", - json={"content": "", "metadata": {}}, - ) - assert response.status_code == 422 - error = response.json()["detail"][0] - assert error["loc"] == ["body", "content"] - assert error["msg"] == "String should have at least 1 character" - assert error["type"] == "string_too_short" - - # Test content too long - response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections/{collection_id}/documents", - json={"content": "a" * 100001, "metadata": {}}, - ) - assert response.status_code == 422 - error = response.json()["detail"][0] - assert error["loc"] == ["body", "content"] - assert error["msg"] == "String should have at most 100000 characters" - assert error["type"] == "string_too_long" - - -def test_document_query_validations_api(client, sample_data): - test_app, test_user = sample_data - # Create a collection first - collection_response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections", - json={"name": str(generate_nanoid()), "metadata": {}}, - ) - collection_id = collection_response.json()["id"] - - # Test query too short - response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections/{collection_id}/documents/query", - json={"query": "", "top_k": 5}, - ) - assert response.status_code == 422 - error = response.json()["detail"][0] - assert error["loc"] == ["body", "query"] - assert error["msg"] == "String should have at least 1 character" - assert error["type"] == "string_too_short" - - # Test query too long - response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections/{collection_id}/documents/query", - json={"query": "a" * 1001, "top_k": 5}, - ) - assert response.status_code == 422 - error = response.json()["detail"][0] - assert error["loc"] == ["body", "query"] - assert error["msg"] == "String should have at most 1000 characters" - assert error["type"] == "string_too_long" - - # Test top_k too small - response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections/{collection_id}/documents/query", - json={"query": "test", "top_k": 0}, - ) - assert response.status_code == 422 - error = response.json()["detail"][0] - assert error["loc"] == ["body", "top_k"] - assert error["msg"] == "Input should be greater than or equal to 1" - assert error["type"] == "greater_than_equal" - - # Test top_k too large - response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections/{collection_id}/documents/query", - json={"query": "test", "top_k": 51}, - ) - assert response.status_code == 422 - error = response.json()["detail"][0] - assert error["loc"] == ["body", "top_k"] - assert error["msg"] == "Input should be less than or equal to 50" - assert error["type"] == "less_than_equal" - - -def test_message_batch_validations_api(client, sample_data): - test_app, test_user = sample_data - # Create a test session first - session_response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions", - json={"metadata": {}}, - ) - session_id = session_response.json()["id"] - - # Test batch too large - messages = [ - {"content": f"test message {i}", "is_user": True, "metadata": {}} - for i in range(101) # Create 101 messages - ] - - response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions/{session_id}/messages/batch", - json={"messages": messages}, - ) - assert response.status_code == 422 - error = response.json()["detail"][0] - assert error["loc"] == ["body", "messages"] - assert "List should have at most 100 items after validation" in error["msg"] - assert error["type"] == "too_long" - - -def test_metamessage_validations_api(client, sample_data): - test_app, test_user = sample_data - # Create session and message first - session_response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions", - json={"metadata": {}}, - ) - session_id = session_response.json()["id"] - - message_response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions/{session_id}/messages", - json={"content": "test message", "is_user": True, "metadata": {}}, - ) - message_id = message_response.json()["id"] - - # Test label too short - response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/metamessages", - json={ - "label": "", - "content": "test content", - "session_id": session_id, - "message_id": message_id, - "metadata": {}, - }, - ) - assert response.status_code == 422 - error = response.json()["detail"][0] - assert error["loc"] == ["body", "label"] - assert error["msg"] == "String should have at least 1 character" - assert error["type"] == "string_too_short" - - # Test label too long - response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/metamessages", - json={ - "label": "a" * 51, - "content": "test content", - "session_id": session_id, - "message_id": message_id, - "metadata": {}, - }, - ) - assert response.status_code == 422 - error = response.json()["detail"][0] - assert error["loc"] == ["body", "label"] - assert error["msg"] == "String should have at most 50 characters" - assert error["type"] == "string_too_long" - - # Test content too long - response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/metamessages", - json={ - "label": "test_type", - "content": "a" * 50001, - "message_id": message_id, - "session_id": session_id, - "metadata": {}, - }, - ) - assert response.status_code == 422 - error = response.json()["detail"][0] - assert error["loc"] == ["body", "content"] - assert error["msg"] == "String should have at most 50000 characters" - assert error["type"] == "string_too_long" - - -def test_collection_update_validations_api(client, sample_data): - test_app, test_user = sample_data - # Create a collection first - collection_response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections", - json={"name": str(generate_nanoid()), "metadata": {}}, - ) - collection_id = collection_response.json()["id"] - - # Test honcho name in update - response = client.put( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections/{collection_id}", - json={"name": "honcho", "metadata": {}}, - ) - assert response.status_code == 422 - error = response.json()["detail"][0] - assert error["loc"] == ["body", "name"] - assert error["msg"] == "Value error, Collection name cannot be 'honcho'" - assert error["type"] == "value_error" - - -def test_document_update_validations_api(client, sample_data): - test_app, test_user = sample_data - # Create collection and document first - collection_response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections", - json={"name": str(generate_nanoid()), "metadata": {}}, - ) - collection_id = collection_response.json()["id"] - - document_response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections/{collection_id}/documents", - json={"content": "test content", "metadata": {}}, - ) - document_id = document_response.json()["id"] - - # Test content too long in update - response = client.put( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections/{collection_id}/documents/{document_id}", - json={"content": "a" * 100001, "metadata": {}}, - ) - assert response.status_code == 422 - error = response.json()["detail"][0] - assert error["loc"] == ["body", "content"] - assert error["msg"] == "String should have at most 100000 characters" - assert error["type"] == "string_too_long" - def test_session_validations_api(client, sample_data): - test_app, test_user = sample_data + test_workspace, test_peer = sample_data # Create a test session first + session_id = str(generate_nanoid()) session_response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions", - json={"metadata": {}}, + f"/v1/workspaces/{test_workspace.name}/sessions", + json={ + "id": session_id, + "peer_names": {test_peer.name: {}}, + "metadata": {"test_key": "test_value"}, + "configuration": {"test_flag": "test_value"}, + }, ) - session_id = session_response.json()["id"] + assert session_response.status_code == 200 # Test invalid metadata type response = client.put( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions/{session_id}", + f"/v1/workspaces/{test_workspace.name}/sessions/{session_id}", json={"metadata": "not a dict"}, ) assert response.status_code == 422 @@ -365,91 +105,49 @@ def test_session_validations_api(client, sample_data): assert error["type"] == "dict_type" # Test empty update + # This should work but not change the session's metadata or configuration response = client.put( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions/{session_id}", + f"/v1/workspaces/{test_workspace.name}/sessions/{session_id}", json={}, ) - assert response.status_code == 422 + assert response.status_code == 200 - -def test_agent_query_validations_api(client, sample_data, monkeypatch): - # Mock the functions in agent.py that are causing the database issues - - # Create a mock collection with a public_id - class MockCollection: - def __init__(self): - self.public_id = "mock_collection_id" - - # Mock collection retrieval/creation function - async def mock_get_or_create_collection(*args, **kwargs): - return MockCollection() - - async def mock_chat_history(*args, **kwargs): - return "Mock chat history", [], [] - - async def mock_get_long_term_facts(*args, **kwargs): - return ["Mock fact 1", "Mock fact 2"] - - async def mock_run_tom_inference(*args, **kwargs): - return "Mock TOM inference" - - async def mock_generate_user_representation(*args, **kwargs): - return "Mock user representation" - - # Mock the Dialectic.call method - async def mock_dialectic_call(self): - # Create a mock response that will work with line 300 in agent.py: - # return schemas.AgentChat(content=response[0]["text"]) - return [{"text": "Mock response"}] - - # Mock the Dialectic.stream method - def mock_dialectic_stream(self): - class MockStream: - def __enter__(self): - return self - - def __exit__(self, *args): - pass - - @property - def text_stream(self): - yield "Mock streamed response" - - return MockStream() - - # Apply the monkeypatches - monkeypatch.setattr( - "src.crud.get_or_create_user_protected_collection", - mock_get_or_create_collection, + # Test that the session's metadata and configuration are not changed + response = client.post( + f"/v1/workspaces/{test_workspace.name}/sessions", + json={ + "id": session_id, + }, ) - monkeypatch.setattr("src.utils.history.get_summarized_history", mock_chat_history) - monkeypatch.setattr("src.agent.get_long_term_facts", mock_get_long_term_facts) - monkeypatch.setattr("src.agent.run_tom_inference", mock_run_tom_inference) - monkeypatch.setattr( - "src.agent.generate_user_representation", mock_generate_user_representation - ) - monkeypatch.setattr("src.agent.Dialectic.call", mock_dialectic_call) - monkeypatch.setattr("src.agent.Dialectic.stream", mock_dialectic_stream) + assert response.status_code == 200 + data = response.json() + assert data["metadata"] == {"test_key": "test_value"} + assert data["configuration"] == {"test_flag": "test_value"} - test_app, test_user = sample_data - # Create a session first since agent queries are likely session-based + +def test_agent_query_validations_api(client, sample_data): + test_workspace, test_peer = sample_data + # Create a session first since agent queries are session-based + session_id = str(generate_nanoid()) session_response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions", - json={"metadata": {}}, + f"/v1/workspaces/{test_workspace.name}/sessions", + json={"id": session_id, "peer_names": {test_peer.name: {}}}, ) - session_id = session_response.json()["id"] + assert session_response.status_code == 200 # Test valid string query (under 10000 chars) response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions/{session_id}/chat", - json={"queries": "a" * 9999}, + f"/v1/workspaces/{test_workspace.name}/peers/{test_peer.name}/chat", + params={"session_id": session_id, "target": "test_target"}, + json={"queries": "a" * 9999, "stream": False}, ) assert response.status_code == 200 # Test string query too long (over 10000 chars) response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions/{session_id}/chat", - json={"queries": "a" * 10001}, + f"/v1/workspaces/{test_workspace.name}/peers/{test_peer.name}/chat", + params={"session_id": session_id, "target": "test_target"}, + json={"queries": "a" * 10001, "stream": False}, ) assert response.status_code == 422 error = response.json()["detail"][0] @@ -459,15 +157,17 @@ def test_agent_query_validations_api(client, sample_data, monkeypatch): # Test valid list query (under 25 items, each under 10000 chars) response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions/{session_id}/chat", - json={"queries": ["a" * 9999 for _ in range(25)]}, + f"/v1/workspaces/{test_workspace.name}/peers/{test_peer.name}/chat", + params={"session_id": session_id, "target": "test_target"}, + json={"queries": ["a" * 9999 for _ in range(25)], "stream": False}, ) assert response.status_code == 200 # Test list too long (over 25 items) response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions/{session_id}/chat", - json={"queries": ["test" for _ in range(26)]}, + f"/v1/workspaces/{test_workspace.name}/peers/{test_peer.name}/chat", + params={"session_id": session_id, "target": "test_target"}, + json={"queries": ["test" for _ in range(26)], "stream": False}, ) assert response.status_code == 422 error = response.json()["detail"][0] @@ -476,8 +176,9 @@ def test_agent_query_validations_api(client, sample_data, monkeypatch): # Test list item too long (item over 10000 chars) response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions/{session_id}/chat", - json={"queries": ["a" * 10001]}, + f"/v1/workspaces/{test_workspace.name}/peers/{test_peer.name}/chat", + params={"session_id": session_id, "target": "test_target"}, + json={"queries": ["a" * 10001], "stream": False}, ) assert response.status_code == 422 error = response.json()["detail"][0] @@ -487,73 +188,56 @@ def test_agent_query_validations_api(client, sample_data, monkeypatch): # Test that strings over 20 chars are allowed response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions/{session_id}/chat", - json={"queries": "a" * 100}, # 100 chars should be fine + f"/v1/workspaces/{test_workspace.name}/peers/{test_peer.name}/chat", + params={"session_id": session_id, "target": "test_target"}, + json={"queries": "a" * 100, "stream": False}, # 100 chars should be fine ) assert response.status_code == 200 def test_required_field_validations_api(client, sample_data): - test_app, test_user = sample_data + test_workspace, test_peer = sample_data + session_id = str(generate_nanoid()) session_response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions", - json={"metadata": {}}, + f"/v1/workspaces/{test_workspace.name}/sessions", + json={"id": session_id, "peer_names": {test_peer.name: {}}}, ) - session_id = session_response.json()["id"] + assert session_response.status_code == 200 # Test missing required content in message response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions/{session_id}/messages", - json={"is_user": True, "metadata": {}}, + f"/v1/workspaces/{test_workspace.name}/sessions/{session_id}/messages", + json={"messages": [{"peer_id": test_peer.name, "metadata": {}}]}, ) assert response.status_code == 422 error = response.json()["detail"][0] - assert error["loc"] == ["body", "content"] + assert "content" in str(error["loc"]) assert error["type"] == "missing" - # Test missing required is_user in message + # Test missing required peer_id in message response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions/{session_id}/messages", - json={"content": "test", "metadata": {}}, + f"/v1/workspaces/{test_workspace.name}/sessions/{session_id}/messages", + json={"messages": [{"content": "test", "metadata": {}}]}, ) assert response.status_code == 422 error = response.json()["detail"][0] - assert error["loc"] == ["body", "is_user"] - assert error["type"] == "missing" - - # Test missing required name in collection - response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections", - json={"metadata": {}}, - ) - assert response.status_code == 422 - error = response.json()["detail"][0] - assert error["loc"] == ["body", "name"] + assert "peer_id" in str(error["loc"]) or "peer_name" in str(error["loc"]) assert error["type"] == "missing" def test_filter_validations_api(client, sample_data): - test_app, test_user = sample_data + test_workspace, test_peer = sample_data # Create a session first + session_id = str(generate_nanoid()) session_response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions", - json={"metadata": {}}, + f"/v1/workspaces/{test_workspace.name}/sessions", + json={"id": session_id, "peer_names": {test_peer.name: {}}}, ) - session_id = session_response.json()["id"] + assert session_response.status_code == 200 # Test invalid filter type in message list (at session level) response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/sessions/{session_id}/messages/list", - json={"filter": "not a dict"}, - ) - assert response.status_code == 422 - error = response.json()["detail"][0] - assert error["loc"] == ["body", "filter"] - assert error["type"] == "dict_type" - - # Test invalid filter type in collection list (at user level) - response = client.post( - f"/v1/apps/{test_app.public_id}/users/{test_user.public_id}/collections/list", + f"/v1/workspaces/{test_workspace.name}/sessions/{session_id}/messages/list", json={"filter": "not a dict"}, ) assert response.status_code == 422 diff --git a/tests/routes/test_workspaces.py b/tests/routes/test_workspaces.py new file mode 100644 index 00000000..d77f4e42 --- /dev/null +++ b/tests/routes/test_workspaces.py @@ -0,0 +1,248 @@ +import pytest +from nanoid import generate as generate_nanoid + + +def test_get_or_create_workspace(client): + name = str(generate_nanoid()) + + # This should create the workspace using POST /v1/workspaces + response = client.post("/v1/workspaces", json={"name": name}) + assert response.status_code == 200 + data = response.json() + assert data["id"] == name + assert "id" in data + + +def test_get_or_create_workspace_with_configuration(client): + """Test workspace creation with configuration parameter""" + name = str(generate_nanoid()) + configuration = {"feature1": True, "feature2": False} + + response = client.post( + "/v1/workspaces", json={"name": name, "configuration": configuration} + ) + assert response.status_code == 200 + data = response.json() + assert data["id"] == name + assert data["configuration"] == configuration + + +def test_get_or_create_workspace_with_all_optional_params(client): + """Test workspace creation with all optional parameters""" + name = str(generate_nanoid()) + metadata = {"key": "value", "number": 42} + configuration = {"experimental": True, "beta": False} + + response = client.post( + "/v1/workspaces", + json={"name": name, "metadata": metadata, "configuration": configuration}, + ) + assert response.status_code == 200 + data = response.json() + assert data["id"] == name + assert data["metadata"] == metadata + assert data["configuration"] == configuration + + +def test_get_or_create_existing_workspace(client): + name = str(generate_nanoid()) + + # Create the workspace + response = client.post( + "/v1/workspaces", json={"name": name, "metadata": {"key": "value"}} + ) + assert response.status_code == 200 + workspace1 = response.json() + + # Try to create the same workspace again - should return existing workspace + response = client.post( + "/v1/workspaces", json={"name": name, "metadata": {"key": "value"}} + ) + assert response.status_code == 200 + workspace2 = response.json() + + # Both should be the same workspace + assert workspace1["id"] == workspace2["id"] + assert workspace1["metadata"] == workspace2["metadata"] + + +@pytest.mark.asyncio +async def test_get_all_workspaces(client, db_session, sample_data): + # create a test workspace with metadata + response = client.post( + "/v1/workspaces", + json={ + "name": "test_workspace", + "metadata": {"test_key": "test_value"}, + }, + ) + + response = client.post( + "/v1/workspaces/list", + json={}, + ) + assert response.status_code == 200 + data = response.json() + assert "items" in data + assert len(data["items"]) > 0 + + response = client.post( + "/v1/workspaces/list", + json={"filter": {"test_key": "test_value"}}, + ) + assert response.status_code == 200 + data = response.json() + assert "items" in data + assert len(data["items"]) > 0 + assert data["items"][0]["metadata"] == {"test_key": "test_value"} + + +@pytest.mark.asyncio +async def test_get_all_workspaces_with_empty_filter(client, db_session, sample_data): + """Test workspace listing with empty filter object""" + response = client.post("/v1/workspaces/list", json={"filter": {}}) + assert response.status_code == 200 + data = response.json() + assert "items" in data + assert isinstance(data["items"], list) + + +@pytest.mark.asyncio +async def test_get_all_workspaces_with_null_filter(client, db_session, sample_data): + """Test workspace listing with null filter""" + response = client.post("/v1/workspaces/list", json={"filter": None}) + assert response.status_code == 200 + data = response.json() + assert "items" in data + assert isinstance(data["items"], list) + + +def test_update_workspace(client, sample_data): + test_workspace, _ = sample_data + _new_name = str(generate_nanoid()) + response = client.put( + f"/v1/workspaces/{test_workspace.name}", + json={"metadata": {"new_key": "new_value"}}, + ) + assert response.status_code == 200 + data = response.json() + assert data["metadata"] == {"new_key": "new_value"} + + +def test_update_workspace_with_configuration(client, sample_data): + """Test workspace update with configuration parameter""" + test_workspace, _ = sample_data + configuration = {"new_feature": True, "legacy_feature": False} + + response = client.put( + f"/v1/workspaces/{test_workspace.name}", json={"configuration": configuration} + ) + assert response.status_code == 200 + data = response.json() + assert data["configuration"] == configuration + + +def test_update_workspace_with_all_optional_params(client, sample_data): + """Test workspace update with both metadata and configuration""" + test_workspace, _ = sample_data + metadata = {"updated_key": "updated_value", "count": 100} + configuration = {"experimental": True, "beta": True} + + response = client.put( + f"/v1/workspaces/{test_workspace.name}", + json={"metadata": metadata, "configuration": configuration}, + ) + assert response.status_code == 200 + data = response.json() + assert data["metadata"] == metadata + assert data["configuration"] == configuration + + +def test_update_workspace_with_null_metadata(client, sample_data): + """Test workspace update with null metadata (should clear metadata)""" + test_workspace, _ = sample_data + + # First set some metadata + client.put( + f"/v1/workspaces/{test_workspace.name}", json={"metadata": {"temp": "value"}} + ) + + # Then clear it with null + response = client.put( + f"/v1/workspaces/{test_workspace.name}", json={"metadata": None} + ) + assert response.status_code == 200 + data = response.json() + # The behavior might be to keep existing metadata or clear it - + # adjust this assertion based on actual behavior + assert "metadata" in data + + +def test_update_workspace_with_null_configuration(client, sample_data): + """Test workspace update with null configuration""" + test_workspace, _ = sample_data + + response = client.put( + f"/v1/workspaces/{test_workspace.name}", json={"configuration": None} + ) + assert response.status_code == 200 + data = response.json() + assert "configuration" in data + + +def test_create_duplicate_workspace_name(client): + # Create an workspace + name = str(generate_nanoid()) + response = client.post("/v1/workspaces", json={"name": name}) + assert response.status_code == 200 + + # Try to create another workspace with the same name - should return existing workspace + response = client.post("/v1/workspaces", json={"name": name}) + + # Should return the existing workspace with 200 status (get_or_create behavior) + assert response.status_code == 200 + data = response.json() + assert data["id"] == name + + +def test_search_workspace(client, sample_data): + """Test the workspace search functionality""" + test_workspace, _ = sample_data + + # Test search with a query + response = client.post( + f"/v1/workspaces/{test_workspace.name}/search", json="test search query" + ) + assert response.status_code == 200 + data = response.json() + + # Response should have pagination structure + assert "items" in data + assert "total" in data + assert "page" in data + assert "size" in data + assert isinstance(data["items"], list) + + +def test_search_workspace_empty_query(client, sample_data): + """Test the workspace search with empty query""" + test_workspace, _ = sample_data + + # Test search with empty query + response = client.post(f"/v1/workspaces/{test_workspace.name}/search", json="") + assert response.status_code == 200 + data = response.json() + + # Response should still have proper pagination structure + assert "items" in data + assert isinstance(data["items"], list) + + +def test_search_workspace_nonexistent(client): + """Test searching a workspace that doesn't exist""" + nonexistent_workspace_id = str(generate_nanoid()) + + response = client.post( + f"/v1/workspaces/{nonexistent_workspace_id}/search", json="test query" + ) + assert response.status_code == 200 diff --git a/tests/test_schema_validations.py b/tests/test_schema_validations.py index 92bd984c..9850cd75 100644 --- a/tests/test_schema_validations.py +++ b/tests/test_schema_validations.py @@ -2,153 +2,90 @@ import pytest from pydantic import ValidationError from src.schemas import ( - AppCreate, - CollectionCreate, DocumentCreate, - DocumentQuery, - MessageBatchCreate, MessageCreate, - MetamessageCreate, - UserCreate, + PeerCreate, + SessionCreate, + WorkspaceCreate, ) -class TestAppValidations: - def test_valid_app_create(self): - app = AppCreate(name="test", metadata={}) - assert app.name == "test" - assert app.metadata == {} +class TestWorkspaceValidations: + def test_valid_workspace_create(self): + workspace = WorkspaceCreate(name="test", metadata={}) + assert workspace.name == "test" + assert workspace.metadata == {} def test_app_name_too_short(self): with pytest.raises(ValidationError) as exc_info: - AppCreate(name="", metadata={}) + WorkspaceCreate(name="", metadata={}) error_dict = exc_info.value.errors()[0] assert error_dict["type"] == "string_too_short" def test_app_name_too_long(self): with pytest.raises(ValidationError) as exc_info: - AppCreate(name="a" * 101, metadata={}) + WorkspaceCreate(name="a" * 101, metadata={}) error_dict = exc_info.value.errors()[0] assert error_dict["type"] == "string_too_long" def test_app_invalid_metadata_type(self): with pytest.raises(ValidationError) as exc_info: - AppCreate(name="test", metadata="not a dict") + WorkspaceCreate(name="test", metadata="not a dict") error_dict = exc_info.value.errors()[0] assert error_dict["type"] == "dict_type" -class TestUserValidations: - def test_valid_user_create(self): - user = UserCreate(name="test", metadata={}) - assert user.name == "test" - assert user.metadata == {} +class TestPeerValidations: + def test_valid_peer_create(self): + peer = PeerCreate(name="test", metadata={}) + assert peer.name == "test" + assert peer.metadata == {} - def test_user_name_too_short(self): + def test_valid_peer_create_configuration(self): + peer = PeerCreate(name="test", metadata={}, configuration={"test": True}) + assert peer.name == "test" + assert peer.metadata == {} + assert peer.configuration == {"test": True} + + def test_peer_name_too_short(self): with pytest.raises(ValidationError) as exc_info: - UserCreate(name="", metadata={}) + PeerCreate(name="", metadata={}) error_dict = exc_info.value.errors()[0] assert error_dict["type"] == "string_too_short" - def test_user_name_too_long(self): + def test_peer_name_too_long(self): with pytest.raises(ValidationError) as exc_info: - UserCreate(name="a" * 101, metadata={}) + PeerCreate(name="a" * 101, metadata={}) error_dict = exc_info.value.errors()[0] assert error_dict["type"] == "string_too_long" +class TestSessionValidations: + def test_valid_session_create(self): + session = SessionCreate(name="test", metadata={}) + assert session.name == "test" + assert session.metadata == {} + + def test_session_name_too_short(self): + with pytest.raises(ValidationError) as exc_info: + SessionCreate(name="", metadata={}) + error_dict = exc_info.value.errors()[0] + assert error_dict["type"] == "string_too_short" + + class TestMessageValidations: def test_valid_message_create(self): - msg = MessageCreate(content="test", is_user=True, metadata={}) + msg = MessageCreate(content="test", peer_id="12345", metadata={}) assert msg.content == "test" - assert msg.is_user is True + assert msg.peer_name == "12345" assert msg.metadata == {} def test_message_content_too_long(self): with pytest.raises(ValidationError) as exc_info: - MessageCreate(content="a" * 50001, is_user=True, metadata={}) + MessageCreate(content="a" * 50001, peer_id="12345", metadata={}) error_dict = exc_info.value.errors()[0] assert error_dict["type"] == "string_too_long" - def test_message_invalid_is_user_type(self): - with pytest.raises(ValidationError) as exc_info: - MessageCreate(content="test", is_user="not a bool", metadata={}) - error_dict = exc_info.value.errors()[0] - assert error_dict["type"] == "bool_parsing" - - -class TestMetamessageValidations: - def test_valid_metamessage_create(self): - meta = MetamessageCreate( - label="test", - content="test content", - message_id="123", - metadata={}, - ) - assert meta.label == "test" - assert meta.content == "test content" - assert meta.message_id == "123" - - def test_label_too_short(self): - with pytest.raises(ValidationError) as exc_info: - MetamessageCreate( - label="", - content="test", - message_id="123", - metadata={}, - ) - error_dict = exc_info.value.errors(include_input=False)[0] - assert error_dict["type"] == "string_too_short" - assert error_dict["loc"] == ('label',) - - def test_label_too_long(self): - with pytest.raises(ValidationError) as exc_info: - MetamessageCreate( - label="a" * 51, - content="test", - message_id="123", - metadata={}, - ) - error_dict = exc_info.value.errors(include_input=False)[0] - assert error_dict["type"] == "string_too_long" - assert error_dict["loc"] == ('label',) - - def test_metamessage_content_too_long(self): - with pytest.raises(ValidationError) as exc_info: - MetamessageCreate( - label="test", - content="a" * 50001, - message_id="123", - metadata={}, - ) - error_dict = exc_info.value.errors()[0] - assert error_dict["type"] == "string_too_long" - - -class TestCollectionValidations: - def test_valid_collection_create(self): - collection = CollectionCreate(name="test", metadata={}) - assert collection.name == "test" - assert collection.metadata == {} - - def test_collection_name_too_short(self): - with pytest.raises(ValidationError) as exc_info: - CollectionCreate(name="", metadata={}) - error_dict = exc_info.value.errors()[0] - assert error_dict["type"] == "string_too_short" - - def test_collection_name_too_long(self): - with pytest.raises(ValidationError) as exc_info: - CollectionCreate(name="a" * 101, metadata={}) - error_dict = exc_info.value.errors()[0] - assert error_dict["type"] == "string_too_long" - - def test_collection_name_honcho(self): - with pytest.raises(ValidationError) as exc_info: - CollectionCreate(name="honcho", metadata={}) - error_dict = exc_info.value.errors()[0] - assert error_dict["type"] == "value_error" - class TestDocumentValidations: def test_valid_document_create(self): @@ -167,53 +104,3 @@ class TestDocumentValidations: DocumentCreate(content="a" * 100001, metadata={}) error_dict = exc_info.value.errors()[0] assert error_dict["type"] == "string_too_long" - - -class TestDocumentQueryValidations: - def test_valid_document_query(self): - query = DocumentQuery(query="test query", top_k=5) - assert query.query == "test query" - assert query.top_k == 5 - - def test_query_too_short(self): - with pytest.raises(ValidationError) as exc_info: - DocumentQuery(query="", top_k=5) - error_dict = exc_info.value.errors()[0] - assert error_dict["type"] == "string_too_short" - - def test_query_too_long(self): - with pytest.raises(ValidationError) as exc_info: - DocumentQuery(query="a" * 1001, top_k=5) - error_dict = exc_info.value.errors()[0] - assert error_dict["type"] == "string_too_long" - - def test_top_k_too_small(self): - with pytest.raises(ValidationError) as exc_info: - DocumentQuery(query="test", top_k=0) - error_dict = exc_info.value.errors()[0] - assert error_dict["type"] == "greater_than_equal" - - def test_top_k_too_large(self): - with pytest.raises(ValidationError) as exc_info: - DocumentQuery(query="test", top_k=51) - error_dict = exc_info.value.errors()[0] - assert error_dict["type"] == "less_than_equal" - - -class TestMessageBatchValidations: - def test_valid_message_batch(self): - batch = MessageBatchCreate( - messages=[MessageCreate(content="test", is_user=True, metadata={})] - ) - assert len(batch.messages) == 1 - - def test_message_batch_too_large(self): - with pytest.raises(ValidationError) as exc_info: - MessageBatchCreate( - messages=[ - MessageCreate(content="test", is_user=True, metadata={}) - for _ in range(101) - ] - ) - error_dict = exc_info.value.errors()[0] - assert error_dict["type"] == "too_long" diff --git a/tests/utils/test_model_client.py b/tests/utils/test_model_client.py index 2b51fc50..d43924c1 100644 --- a/tests/utils/test_model_client.py +++ b/tests/utils/test_model_client.py @@ -101,9 +101,11 @@ def test_unsupported_provider_initialization(): def test_missing_api_key_initialization(): """Test initialization without required API key.""" - with patch.dict(os.environ, {}, clear=True): - with pytest.raises(ValueError, match="API key is required"): - ModelClient() + with ( + patch.dict(os.environ, {}, clear=True), + pytest.raises(ValueError, match="API key is required"), + ): + ModelClient() # Message Creation Tests diff --git a/uv.lock b/uv.lock index 632b21ad..069bbb54 100644 --- a/uv.lock +++ b/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 1 +revision = 2 requires-python = ">=3.9" resolution-markers = [ "python_full_version >= '3.10'", @@ -15,18 +15,18 @@ dependencies = [ { name = "sqlalchemy" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e6/57/e314c31b261d1e8a5a5f1908065b4ff98270a778ce7579bd4254477209a7/alembic-1.15.2.tar.gz", hash = "sha256:1c72391bbdeffccfe317eefba686cb9a3c078005478885413b95c3b26c57a8a7", size = 1925573 } +sdist = { url = "https://files.pythonhosted.org/packages/e6/57/e314c31b261d1e8a5a5f1908065b4ff98270a778ce7579bd4254477209a7/alembic-1.15.2.tar.gz", hash = "sha256:1c72391bbdeffccfe317eefba686cb9a3c078005478885413b95c3b26c57a8a7", size = 1925573, upload_time = "2025-03-28T13:52:00.443Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/41/18/d89a443ed1ab9bcda16264716f809c663866d4ca8de218aa78fd50b38ead/alembic-1.15.2-py3-none-any.whl", hash = "sha256:2e76bd916d547f6900ec4bb5a90aeac1485d2c92536923d0b138c02b126edc53", size = 231911 }, + { url = "https://files.pythonhosted.org/packages/41/18/d89a443ed1ab9bcda16264716f809c663866d4ca8de218aa78fd50b38ead/alembic-1.15.2-py3-none-any.whl", hash = "sha256:2e76bd916d547f6900ec4bb5a90aeac1485d2c92536923d0b138c02b126edc53", size = 231911, upload_time = "2025-03-28T13:52:02.218Z" }, ] [[package]] name = "annotated-types" version = "0.7.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload_time = "2024-05-20T21:33:25.928Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 }, + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload_time = "2024-05-20T21:33:24.1Z" }, ] [[package]] @@ -42,9 +42,9 @@ dependencies = [ { name = "sniffio" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/86/e3/a88c8494ce4d1a88252b9e053607e885f9b14d0a32273d47b727cbee4228/anthropic-0.49.0.tar.gz", hash = "sha256:c09e885b0f674b9119b4f296d8508907f6cff0009bc20d5cf6b35936c40b4398", size = 210016 } +sdist = { url = "https://files.pythonhosted.org/packages/86/e3/a88c8494ce4d1a88252b9e053607e885f9b14d0a32273d47b727cbee4228/anthropic-0.49.0.tar.gz", hash = "sha256:c09e885b0f674b9119b4f296d8508907f6cff0009bc20d5cf6b35936c40b4398", size = 210016, upload_time = "2025-02-28T19:35:47.01Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/76/74/5d90ad14d55fbe3f9c474fdcb6e34b4bed99e3be8efac98734a5ddce88c1/anthropic-0.49.0-py3-none-any.whl", hash = "sha256:bbc17ad4e7094988d2fa86b87753ded8dce12498f4b85fe5810f208f454a8375", size = 243368 }, + { url = "https://files.pythonhosted.org/packages/76/74/5d90ad14d55fbe3f9c474fdcb6e34b4bed99e3be8efac98734a5ddce88c1/anthropic-0.49.0-py3-none-any.whl", hash = "sha256:bbc17ad4e7094988d2fa86b87753ded8dce12498f4b85fe5810f208f454a8375", size = 243368, upload_time = "2025-02-28T19:35:44.963Z" }, ] [[package]] @@ -57,119 +57,119 @@ dependencies = [ { name = "sniffio" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949 } +sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949, upload_time = "2025-03-17T00:02:54.77Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916 }, + { url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916, upload_time = "2025-03-17T00:02:52.713Z" }, ] [[package]] name = "attrs" version = "25.3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032 } +sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032, upload_time = "2025-03-13T11:10:22.779Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815 }, + { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815, upload_time = "2025-03-13T11:10:21.14Z" }, ] [[package]] name = "backoff" version = "2.2.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/47/d7/5bbeb12c44d7c4f2fb5b56abce497eb5ed9f34d85701de869acedd602619/backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba", size = 17001 } +sdist = { url = "https://files.pythonhosted.org/packages/47/d7/5bbeb12c44d7c4f2fb5b56abce497eb5ed9f34d85701de869acedd602619/backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba", size = 17001, upload_time = "2022-10-05T19:19:32.061Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/df/73/b6e24bd22e6720ca8ee9a85a0c4a2971af8497d8f3193fa05390cbd46e09/backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8", size = 15148 }, + { url = "https://files.pythonhosted.org/packages/df/73/b6e24bd22e6720ca8ee9a85a0c4a2971af8497d8f3193fa05390cbd46e09/backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8", size = 15148, upload_time = "2022-10-05T19:19:30.546Z" }, ] [[package]] name = "cachetools" version = "5.5.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6c/81/3747dad6b14fa2cf53fcf10548cf5aea6913e96fab41a3c198676f8948a5/cachetools-5.5.2.tar.gz", hash = "sha256:1a661caa9175d26759571b2e19580f9d6393969e5dfca11fdb1f947a23e640d4", size = 28380 } +sdist = { url = "https://files.pythonhosted.org/packages/6c/81/3747dad6b14fa2cf53fcf10548cf5aea6913e96fab41a3c198676f8948a5/cachetools-5.5.2.tar.gz", hash = "sha256:1a661caa9175d26759571b2e19580f9d6393969e5dfca11fdb1f947a23e640d4", size = 28380, upload_time = "2025-02-20T21:01:19.524Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/72/76/20fa66124dbe6be5cafeb312ece67de6b61dd91a0247d1ea13db4ebb33c2/cachetools-5.5.2-py3-none-any.whl", hash = "sha256:d26a22bcc62eb95c3beabd9f1ee5e820d3d2704fe2967cbe350e20c8ffcd3f0a", size = 10080 }, + { url = "https://files.pythonhosted.org/packages/72/76/20fa66124dbe6be5cafeb312ece67de6b61dd91a0247d1ea13db4ebb33c2/cachetools-5.5.2-py3-none-any.whl", hash = "sha256:d26a22bcc62eb95c3beabd9f1ee5e820d3d2704fe2967cbe350e20c8ffcd3f0a", size = 10080, upload_time = "2025-02-20T21:01:16.647Z" }, ] [[package]] name = "certifi" version = "2025.1.31" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1c/ab/c9f1e32b7b1bf505bf26f0ef697775960db7932abeb7b516de930ba2705f/certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", size = 167577 } +sdist = { url = "https://files.pythonhosted.org/packages/1c/ab/c9f1e32b7b1bf505bf26f0ef697775960db7932abeb7b516de930ba2705f/certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", size = 167577, upload_time = "2025-01-31T02:16:47.166Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393 }, + { url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393, upload_time = "2025-01-31T02:16:45.015Z" }, ] [[package]] name = "charset-normalizer" version = "3.4.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/16/b0/572805e227f01586461c80e0fd25d65a2115599cc9dad142fee4b747c357/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", size = 123188 } +sdist = { url = "https://files.pythonhosted.org/packages/16/b0/572805e227f01586461c80e0fd25d65a2115599cc9dad142fee4b747c357/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", size = 123188, upload_time = "2024-12-24T18:12:35.43Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0d/58/5580c1716040bc89206c77d8f74418caf82ce519aae06450393ca73475d1/charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de", size = 198013 }, - { url = "https://files.pythonhosted.org/packages/d0/11/00341177ae71c6f5159a08168bcb98c6e6d196d372c94511f9f6c9afe0c6/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176", size = 141285 }, - { url = "https://files.pythonhosted.org/packages/01/09/11d684ea5819e5a8f5100fb0b38cf8d02b514746607934134d31233e02c8/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037", size = 151449 }, - { url = "https://files.pythonhosted.org/packages/08/06/9f5a12939db324d905dc1f70591ae7d7898d030d7662f0d426e2286f68c9/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f", size = 143892 }, - { url = "https://files.pythonhosted.org/packages/93/62/5e89cdfe04584cb7f4d36003ffa2936681b03ecc0754f8e969c2becb7e24/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a", size = 146123 }, - { url = "https://files.pythonhosted.org/packages/a9/ac/ab729a15c516da2ab70a05f8722ecfccc3f04ed7a18e45c75bbbaa347d61/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a", size = 147943 }, - { url = "https://files.pythonhosted.org/packages/03/d2/3f392f23f042615689456e9a274640c1d2e5dd1d52de36ab8f7955f8f050/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247", size = 142063 }, - { url = "https://files.pythonhosted.org/packages/f2/e3/e20aae5e1039a2cd9b08d9205f52142329f887f8cf70da3650326670bddf/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408", size = 150578 }, - { url = "https://files.pythonhosted.org/packages/8d/af/779ad72a4da0aed925e1139d458adc486e61076d7ecdcc09e610ea8678db/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb", size = 153629 }, - { url = "https://files.pythonhosted.org/packages/c2/b6/7aa450b278e7aa92cf7732140bfd8be21f5f29d5bf334ae987c945276639/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d", size = 150778 }, - { url = "https://files.pythonhosted.org/packages/39/f4/d9f4f712d0951dcbfd42920d3db81b00dd23b6ab520419626f4023334056/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807", size = 146453 }, - { url = "https://files.pythonhosted.org/packages/49/2b/999d0314e4ee0cff3cb83e6bc9aeddd397eeed693edb4facb901eb8fbb69/charset_normalizer-3.4.1-cp310-cp310-win32.whl", hash = "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f", size = 95479 }, - { url = "https://files.pythonhosted.org/packages/2d/ce/3cbed41cff67e455a386fb5e5dd8906cdda2ed92fbc6297921f2e4419309/charset_normalizer-3.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f", size = 102790 }, - { url = "https://files.pythonhosted.org/packages/72/80/41ef5d5a7935d2d3a773e3eaebf0a9350542f2cab4eac59a7a4741fbbbbe/charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125", size = 194995 }, - { url = "https://files.pythonhosted.org/packages/7a/28/0b9fefa7b8b080ec492110af6d88aa3dea91c464b17d53474b6e9ba5d2c5/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1", size = 139471 }, - { url = "https://files.pythonhosted.org/packages/71/64/d24ab1a997efb06402e3fc07317e94da358e2585165930d9d59ad45fcae2/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3", size = 149831 }, - { url = "https://files.pythonhosted.org/packages/37/ed/be39e5258e198655240db5e19e0b11379163ad7070962d6b0c87ed2c4d39/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd", size = 142335 }, - { url = "https://files.pythonhosted.org/packages/88/83/489e9504711fa05d8dde1574996408026bdbdbd938f23be67deebb5eca92/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00", size = 143862 }, - { url = "https://files.pythonhosted.org/packages/c6/c7/32da20821cf387b759ad24627a9aca289d2822de929b8a41b6241767b461/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12", size = 145673 }, - { url = "https://files.pythonhosted.org/packages/68/85/f4288e96039abdd5aeb5c546fa20a37b50da71b5cf01e75e87f16cd43304/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77", size = 140211 }, - { url = "https://files.pythonhosted.org/packages/28/a3/a42e70d03cbdabc18997baf4f0227c73591a08041c149e710045c281f97b/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146", size = 148039 }, - { url = "https://files.pythonhosted.org/packages/85/e4/65699e8ab3014ecbe6f5c71d1a55d810fb716bbfd74f6283d5c2aa87febf/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd", size = 151939 }, - { url = "https://files.pythonhosted.org/packages/b1/82/8e9fe624cc5374193de6860aba3ea8070f584c8565ee77c168ec13274bd2/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6", size = 149075 }, - { url = "https://files.pythonhosted.org/packages/3d/7b/82865ba54c765560c8433f65e8acb9217cb839a9e32b42af4aa8e945870f/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8", size = 144340 }, - { url = "https://files.pythonhosted.org/packages/b5/b6/9674a4b7d4d99a0d2df9b215da766ee682718f88055751e1e5e753c82db0/charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b", size = 95205 }, - { url = "https://files.pythonhosted.org/packages/1e/ab/45b180e175de4402dcf7547e4fb617283bae54ce35c27930a6f35b6bef15/charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76", size = 102441 }, - { url = "https://files.pythonhosted.org/packages/0a/9a/dd1e1cdceb841925b7798369a09279bd1cf183cef0f9ddf15a3a6502ee45/charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545", size = 196105 }, - { url = "https://files.pythonhosted.org/packages/d3/8c/90bfabf8c4809ecb648f39794cf2a84ff2e7d2a6cf159fe68d9a26160467/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7", size = 140404 }, - { url = "https://files.pythonhosted.org/packages/ad/8f/e410d57c721945ea3b4f1a04b74f70ce8fa800d393d72899f0a40526401f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757", size = 150423 }, - { url = "https://files.pythonhosted.org/packages/f0/b8/e6825e25deb691ff98cf5c9072ee0605dc2acfca98af70c2d1b1bc75190d/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa", size = 143184 }, - { url = "https://files.pythonhosted.org/packages/3e/a2/513f6cbe752421f16d969e32f3583762bfd583848b763913ddab8d9bfd4f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d", size = 145268 }, - { url = "https://files.pythonhosted.org/packages/74/94/8a5277664f27c3c438546f3eb53b33f5b19568eb7424736bdc440a88a31f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616", size = 147601 }, - { url = "https://files.pythonhosted.org/packages/7c/5f/6d352c51ee763623a98e31194823518e09bfa48be2a7e8383cf691bbb3d0/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b", size = 141098 }, - { url = "https://files.pythonhosted.org/packages/78/d4/f5704cb629ba5ab16d1d3d741396aec6dc3ca2b67757c45b0599bb010478/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d", size = 149520 }, - { url = "https://files.pythonhosted.org/packages/c5/96/64120b1d02b81785f222b976c0fb79a35875457fa9bb40827678e54d1bc8/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a", size = 152852 }, - { url = "https://files.pythonhosted.org/packages/84/c9/98e3732278a99f47d487fd3468bc60b882920cef29d1fa6ca460a1fdf4e6/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9", size = 150488 }, - { url = "https://files.pythonhosted.org/packages/13/0e/9c8d4cb99c98c1007cc11eda969ebfe837bbbd0acdb4736d228ccaabcd22/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1", size = 146192 }, - { url = "https://files.pythonhosted.org/packages/b2/21/2b6b5b860781a0b49427309cb8670785aa543fb2178de875b87b9cc97746/charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35", size = 95550 }, - { url = "https://files.pythonhosted.org/packages/21/5b/1b390b03b1d16c7e382b561c5329f83cc06623916aab983e8ab9239c7d5c/charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f", size = 102785 }, - { url = "https://files.pythonhosted.org/packages/38/94/ce8e6f63d18049672c76d07d119304e1e2d7c6098f0841b51c666e9f44a0/charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda", size = 195698 }, - { url = "https://files.pythonhosted.org/packages/24/2e/dfdd9770664aae179a96561cc6952ff08f9a8cd09a908f259a9dfa063568/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313", size = 140162 }, - { url = "https://files.pythonhosted.org/packages/24/4e/f646b9093cff8fc86f2d60af2de4dc17c759de9d554f130b140ea4738ca6/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9", size = 150263 }, - { url = "https://files.pythonhosted.org/packages/5e/67/2937f8d548c3ef6e2f9aab0f6e21001056f692d43282b165e7c56023e6dd/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b", size = 142966 }, - { url = "https://files.pythonhosted.org/packages/52/ed/b7f4f07de100bdb95c1756d3a4d17b90c1a3c53715c1a476f8738058e0fa/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11", size = 144992 }, - { url = "https://files.pythonhosted.org/packages/96/2c/d49710a6dbcd3776265f4c923bb73ebe83933dfbaa841c5da850fe0fd20b/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f", size = 147162 }, - { url = "https://files.pythonhosted.org/packages/b4/41/35ff1f9a6bd380303dea55e44c4933b4cc3c4850988927d4082ada230273/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd", size = 140972 }, - { url = "https://files.pythonhosted.org/packages/fb/43/c6a0b685fe6910d08ba971f62cd9c3e862a85770395ba5d9cad4fede33ab/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2", size = 149095 }, - { url = "https://files.pythonhosted.org/packages/4c/ff/a9a504662452e2d2878512115638966e75633519ec11f25fca3d2049a94a/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886", size = 152668 }, - { url = "https://files.pythonhosted.org/packages/6c/71/189996b6d9a4b932564701628af5cee6716733e9165af1d5e1b285c530ed/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601", size = 150073 }, - { url = "https://files.pythonhosted.org/packages/e4/93/946a86ce20790e11312c87c75ba68d5f6ad2208cfb52b2d6a2c32840d922/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd", size = 145732 }, - { url = "https://files.pythonhosted.org/packages/cd/e5/131d2fb1b0dddafc37be4f3a2fa79aa4c037368be9423061dccadfd90091/charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407", size = 95391 }, - { url = "https://files.pythonhosted.org/packages/27/f2/4f9a69cc7712b9b5ad8fdb87039fd89abba997ad5cbe690d1835d40405b0/charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971", size = 102702 }, - { url = "https://files.pythonhosted.org/packages/7f/c0/b913f8f02836ed9ab32ea643c6fe4d3325c3d8627cf6e78098671cafff86/charset_normalizer-3.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b97e690a2118911e39b4042088092771b4ae3fc3aa86518f84b8cf6888dbdb41", size = 197867 }, - { url = "https://files.pythonhosted.org/packages/0f/6c/2bee440303d705b6fb1e2ec789543edec83d32d258299b16eed28aad48e0/charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78baa6d91634dfb69ec52a463534bc0df05dbd546209b79a3880a34487f4b84f", size = 141385 }, - { url = "https://files.pythonhosted.org/packages/3d/04/cb42585f07f6f9fd3219ffb6f37d5a39b4fd2db2355b23683060029c35f7/charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a2bc9f351a75ef49d664206d51f8e5ede9da246602dc2d2726837620ea034b2", size = 151367 }, - { url = "https://files.pythonhosted.org/packages/54/54/2412a5b093acb17f0222de007cc129ec0e0df198b5ad2ce5699355269dfe/charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75832c08354f595c760a804588b9357d34ec00ba1c940c15e31e96d902093770", size = 143928 }, - { url = "https://files.pythonhosted.org/packages/5a/6d/e2773862b043dcf8a221342954f375392bb2ce6487bcd9f2c1b34e1d6781/charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0af291f4fe114be0280cdd29d533696a77b5b49cfde5467176ecab32353395c4", size = 146203 }, - { url = "https://files.pythonhosted.org/packages/b9/f8/ca440ef60d8f8916022859885f231abb07ada3c347c03d63f283bec32ef5/charset_normalizer-3.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0167ddc8ab6508fe81860a57dd472b2ef4060e8d378f0cc555707126830f2537", size = 148082 }, - { url = "https://files.pythonhosted.org/packages/04/d2/42fd330901aaa4b805a1097856c2edf5095e260a597f65def493f4b8c833/charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2a75d49014d118e4198bcee5ee0a6f25856b29b12dbf7cd012791f8a6cc5c496", size = 142053 }, - { url = "https://files.pythonhosted.org/packages/9e/af/3a97a4fa3c53586f1910dadfc916e9c4f35eeada36de4108f5096cb7215f/charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:363e2f92b0f0174b2f8238240a1a30142e3db7b957a5dd5689b0e75fb717cc78", size = 150625 }, - { url = "https://files.pythonhosted.org/packages/26/ae/23d6041322a3556e4da139663d02fb1b3c59a23ab2e2b56432bd2ad63ded/charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ab36c8eb7e454e34e60eb55ca5d241a5d18b2c6244f6827a30e451c42410b5f7", size = 153549 }, - { url = "https://files.pythonhosted.org/packages/94/22/b8f2081c6a77cb20d97e57e0b385b481887aa08019d2459dc2858ed64871/charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:4c0907b1928a36d5a998d72d64d8eaa7244989f7aaaf947500d3a800c83a3fd6", size = 150945 }, - { url = "https://files.pythonhosted.org/packages/c7/0b/c5ec5092747f801b8b093cdf5610e732b809d6cb11f4c51e35fc28d1d389/charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:04432ad9479fa40ec0f387795ddad4437a2b50417c69fa275e212933519ff294", size = 146595 }, - { url = "https://files.pythonhosted.org/packages/0c/5a/0b59704c38470df6768aa154cc87b1ac7c9bb687990a1559dc8765e8627e/charset_normalizer-3.4.1-cp39-cp39-win32.whl", hash = "sha256:3bed14e9c89dcb10e8f3a29f9ccac4955aebe93c71ae803af79265c9ca5644c5", size = 95453 }, - { url = "https://files.pythonhosted.org/packages/85/2d/a9790237cb4d01a6d57afadc8573c8b73c609ade20b80f4cda30802009ee/charset_normalizer-3.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:49402233c892a461407c512a19435d1ce275543138294f7ef013f0b63d5d3765", size = 102811 }, - { url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767 }, + { url = "https://files.pythonhosted.org/packages/0d/58/5580c1716040bc89206c77d8f74418caf82ce519aae06450393ca73475d1/charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de", size = 198013, upload_time = "2024-12-24T18:09:43.671Z" }, + { url = "https://files.pythonhosted.org/packages/d0/11/00341177ae71c6f5159a08168bcb98c6e6d196d372c94511f9f6c9afe0c6/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176", size = 141285, upload_time = "2024-12-24T18:09:48.113Z" }, + { url = "https://files.pythonhosted.org/packages/01/09/11d684ea5819e5a8f5100fb0b38cf8d02b514746607934134d31233e02c8/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037", size = 151449, upload_time = "2024-12-24T18:09:50.845Z" }, + { url = "https://files.pythonhosted.org/packages/08/06/9f5a12939db324d905dc1f70591ae7d7898d030d7662f0d426e2286f68c9/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f", size = 143892, upload_time = "2024-12-24T18:09:52.078Z" }, + { url = "https://files.pythonhosted.org/packages/93/62/5e89cdfe04584cb7f4d36003ffa2936681b03ecc0754f8e969c2becb7e24/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a", size = 146123, upload_time = "2024-12-24T18:09:54.575Z" }, + { url = "https://files.pythonhosted.org/packages/a9/ac/ab729a15c516da2ab70a05f8722ecfccc3f04ed7a18e45c75bbbaa347d61/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a", size = 147943, upload_time = "2024-12-24T18:09:57.324Z" }, + { url = "https://files.pythonhosted.org/packages/03/d2/3f392f23f042615689456e9a274640c1d2e5dd1d52de36ab8f7955f8f050/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247", size = 142063, upload_time = "2024-12-24T18:09:59.794Z" }, + { url = "https://files.pythonhosted.org/packages/f2/e3/e20aae5e1039a2cd9b08d9205f52142329f887f8cf70da3650326670bddf/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408", size = 150578, upload_time = "2024-12-24T18:10:02.357Z" }, + { url = "https://files.pythonhosted.org/packages/8d/af/779ad72a4da0aed925e1139d458adc486e61076d7ecdcc09e610ea8678db/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb", size = 153629, upload_time = "2024-12-24T18:10:03.678Z" }, + { url = "https://files.pythonhosted.org/packages/c2/b6/7aa450b278e7aa92cf7732140bfd8be21f5f29d5bf334ae987c945276639/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d", size = 150778, upload_time = "2024-12-24T18:10:06.197Z" }, + { url = "https://files.pythonhosted.org/packages/39/f4/d9f4f712d0951dcbfd42920d3db81b00dd23b6ab520419626f4023334056/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807", size = 146453, upload_time = "2024-12-24T18:10:08.848Z" }, + { url = "https://files.pythonhosted.org/packages/49/2b/999d0314e4ee0cff3cb83e6bc9aeddd397eeed693edb4facb901eb8fbb69/charset_normalizer-3.4.1-cp310-cp310-win32.whl", hash = "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f", size = 95479, upload_time = "2024-12-24T18:10:10.044Z" }, + { url = "https://files.pythonhosted.org/packages/2d/ce/3cbed41cff67e455a386fb5e5dd8906cdda2ed92fbc6297921f2e4419309/charset_normalizer-3.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f", size = 102790, upload_time = "2024-12-24T18:10:11.323Z" }, + { url = "https://files.pythonhosted.org/packages/72/80/41ef5d5a7935d2d3a773e3eaebf0a9350542f2cab4eac59a7a4741fbbbbe/charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125", size = 194995, upload_time = "2024-12-24T18:10:12.838Z" }, + { url = "https://files.pythonhosted.org/packages/7a/28/0b9fefa7b8b080ec492110af6d88aa3dea91c464b17d53474b6e9ba5d2c5/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1", size = 139471, upload_time = "2024-12-24T18:10:14.101Z" }, + { url = "https://files.pythonhosted.org/packages/71/64/d24ab1a997efb06402e3fc07317e94da358e2585165930d9d59ad45fcae2/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3", size = 149831, upload_time = "2024-12-24T18:10:15.512Z" }, + { url = "https://files.pythonhosted.org/packages/37/ed/be39e5258e198655240db5e19e0b11379163ad7070962d6b0c87ed2c4d39/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd", size = 142335, upload_time = "2024-12-24T18:10:18.369Z" }, + { url = "https://files.pythonhosted.org/packages/88/83/489e9504711fa05d8dde1574996408026bdbdbd938f23be67deebb5eca92/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00", size = 143862, upload_time = "2024-12-24T18:10:19.743Z" }, + { url = "https://files.pythonhosted.org/packages/c6/c7/32da20821cf387b759ad24627a9aca289d2822de929b8a41b6241767b461/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12", size = 145673, upload_time = "2024-12-24T18:10:21.139Z" }, + { url = "https://files.pythonhosted.org/packages/68/85/f4288e96039abdd5aeb5c546fa20a37b50da71b5cf01e75e87f16cd43304/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77", size = 140211, upload_time = "2024-12-24T18:10:22.382Z" }, + { url = "https://files.pythonhosted.org/packages/28/a3/a42e70d03cbdabc18997baf4f0227c73591a08041c149e710045c281f97b/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146", size = 148039, upload_time = "2024-12-24T18:10:24.802Z" }, + { url = "https://files.pythonhosted.org/packages/85/e4/65699e8ab3014ecbe6f5c71d1a55d810fb716bbfd74f6283d5c2aa87febf/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd", size = 151939, upload_time = "2024-12-24T18:10:26.124Z" }, + { url = "https://files.pythonhosted.org/packages/b1/82/8e9fe624cc5374193de6860aba3ea8070f584c8565ee77c168ec13274bd2/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6", size = 149075, upload_time = "2024-12-24T18:10:30.027Z" }, + { url = "https://files.pythonhosted.org/packages/3d/7b/82865ba54c765560c8433f65e8acb9217cb839a9e32b42af4aa8e945870f/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8", size = 144340, upload_time = "2024-12-24T18:10:32.679Z" }, + { url = "https://files.pythonhosted.org/packages/b5/b6/9674a4b7d4d99a0d2df9b215da766ee682718f88055751e1e5e753c82db0/charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b", size = 95205, upload_time = "2024-12-24T18:10:34.724Z" }, + { url = "https://files.pythonhosted.org/packages/1e/ab/45b180e175de4402dcf7547e4fb617283bae54ce35c27930a6f35b6bef15/charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76", size = 102441, upload_time = "2024-12-24T18:10:37.574Z" }, + { url = "https://files.pythonhosted.org/packages/0a/9a/dd1e1cdceb841925b7798369a09279bd1cf183cef0f9ddf15a3a6502ee45/charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545", size = 196105, upload_time = "2024-12-24T18:10:38.83Z" }, + { url = "https://files.pythonhosted.org/packages/d3/8c/90bfabf8c4809ecb648f39794cf2a84ff2e7d2a6cf159fe68d9a26160467/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7", size = 140404, upload_time = "2024-12-24T18:10:44.272Z" }, + { url = "https://files.pythonhosted.org/packages/ad/8f/e410d57c721945ea3b4f1a04b74f70ce8fa800d393d72899f0a40526401f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757", size = 150423, upload_time = "2024-12-24T18:10:45.492Z" }, + { url = "https://files.pythonhosted.org/packages/f0/b8/e6825e25deb691ff98cf5c9072ee0605dc2acfca98af70c2d1b1bc75190d/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa", size = 143184, upload_time = "2024-12-24T18:10:47.898Z" }, + { url = "https://files.pythonhosted.org/packages/3e/a2/513f6cbe752421f16d969e32f3583762bfd583848b763913ddab8d9bfd4f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d", size = 145268, upload_time = "2024-12-24T18:10:50.589Z" }, + { url = "https://files.pythonhosted.org/packages/74/94/8a5277664f27c3c438546f3eb53b33f5b19568eb7424736bdc440a88a31f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616", size = 147601, upload_time = "2024-12-24T18:10:52.541Z" }, + { url = "https://files.pythonhosted.org/packages/7c/5f/6d352c51ee763623a98e31194823518e09bfa48be2a7e8383cf691bbb3d0/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b", size = 141098, upload_time = "2024-12-24T18:10:53.789Z" }, + { url = "https://files.pythonhosted.org/packages/78/d4/f5704cb629ba5ab16d1d3d741396aec6dc3ca2b67757c45b0599bb010478/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d", size = 149520, upload_time = "2024-12-24T18:10:55.048Z" }, + { url = "https://files.pythonhosted.org/packages/c5/96/64120b1d02b81785f222b976c0fb79a35875457fa9bb40827678e54d1bc8/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a", size = 152852, upload_time = "2024-12-24T18:10:57.647Z" }, + { url = "https://files.pythonhosted.org/packages/84/c9/98e3732278a99f47d487fd3468bc60b882920cef29d1fa6ca460a1fdf4e6/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9", size = 150488, upload_time = "2024-12-24T18:10:59.43Z" }, + { url = "https://files.pythonhosted.org/packages/13/0e/9c8d4cb99c98c1007cc11eda969ebfe837bbbd0acdb4736d228ccaabcd22/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1", size = 146192, upload_time = "2024-12-24T18:11:00.676Z" }, + { url = "https://files.pythonhosted.org/packages/b2/21/2b6b5b860781a0b49427309cb8670785aa543fb2178de875b87b9cc97746/charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35", size = 95550, upload_time = "2024-12-24T18:11:01.952Z" }, + { url = "https://files.pythonhosted.org/packages/21/5b/1b390b03b1d16c7e382b561c5329f83cc06623916aab983e8ab9239c7d5c/charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f", size = 102785, upload_time = "2024-12-24T18:11:03.142Z" }, + { url = "https://files.pythonhosted.org/packages/38/94/ce8e6f63d18049672c76d07d119304e1e2d7c6098f0841b51c666e9f44a0/charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda", size = 195698, upload_time = "2024-12-24T18:11:05.834Z" }, + { url = "https://files.pythonhosted.org/packages/24/2e/dfdd9770664aae179a96561cc6952ff08f9a8cd09a908f259a9dfa063568/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313", size = 140162, upload_time = "2024-12-24T18:11:07.064Z" }, + { url = "https://files.pythonhosted.org/packages/24/4e/f646b9093cff8fc86f2d60af2de4dc17c759de9d554f130b140ea4738ca6/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9", size = 150263, upload_time = "2024-12-24T18:11:08.374Z" }, + { url = "https://files.pythonhosted.org/packages/5e/67/2937f8d548c3ef6e2f9aab0f6e21001056f692d43282b165e7c56023e6dd/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b", size = 142966, upload_time = "2024-12-24T18:11:09.831Z" }, + { url = "https://files.pythonhosted.org/packages/52/ed/b7f4f07de100bdb95c1756d3a4d17b90c1a3c53715c1a476f8738058e0fa/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11", size = 144992, upload_time = "2024-12-24T18:11:12.03Z" }, + { url = "https://files.pythonhosted.org/packages/96/2c/d49710a6dbcd3776265f4c923bb73ebe83933dfbaa841c5da850fe0fd20b/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f", size = 147162, upload_time = "2024-12-24T18:11:13.372Z" }, + { url = "https://files.pythonhosted.org/packages/b4/41/35ff1f9a6bd380303dea55e44c4933b4cc3c4850988927d4082ada230273/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd", size = 140972, upload_time = "2024-12-24T18:11:14.628Z" }, + { url = "https://files.pythonhosted.org/packages/fb/43/c6a0b685fe6910d08ba971f62cd9c3e862a85770395ba5d9cad4fede33ab/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2", size = 149095, upload_time = "2024-12-24T18:11:17.672Z" }, + { url = "https://files.pythonhosted.org/packages/4c/ff/a9a504662452e2d2878512115638966e75633519ec11f25fca3d2049a94a/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886", size = 152668, upload_time = "2024-12-24T18:11:18.989Z" }, + { url = "https://files.pythonhosted.org/packages/6c/71/189996b6d9a4b932564701628af5cee6716733e9165af1d5e1b285c530ed/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601", size = 150073, upload_time = "2024-12-24T18:11:21.507Z" }, + { url = "https://files.pythonhosted.org/packages/e4/93/946a86ce20790e11312c87c75ba68d5f6ad2208cfb52b2d6a2c32840d922/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd", size = 145732, upload_time = "2024-12-24T18:11:22.774Z" }, + { url = "https://files.pythonhosted.org/packages/cd/e5/131d2fb1b0dddafc37be4f3a2fa79aa4c037368be9423061dccadfd90091/charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407", size = 95391, upload_time = "2024-12-24T18:11:24.139Z" }, + { url = "https://files.pythonhosted.org/packages/27/f2/4f9a69cc7712b9b5ad8fdb87039fd89abba997ad5cbe690d1835d40405b0/charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971", size = 102702, upload_time = "2024-12-24T18:11:26.535Z" }, + { url = "https://files.pythonhosted.org/packages/7f/c0/b913f8f02836ed9ab32ea643c6fe4d3325c3d8627cf6e78098671cafff86/charset_normalizer-3.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b97e690a2118911e39b4042088092771b4ae3fc3aa86518f84b8cf6888dbdb41", size = 197867, upload_time = "2024-12-24T18:12:10.438Z" }, + { url = "https://files.pythonhosted.org/packages/0f/6c/2bee440303d705b6fb1e2ec789543edec83d32d258299b16eed28aad48e0/charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78baa6d91634dfb69ec52a463534bc0df05dbd546209b79a3880a34487f4b84f", size = 141385, upload_time = "2024-12-24T18:12:11.847Z" }, + { url = "https://files.pythonhosted.org/packages/3d/04/cb42585f07f6f9fd3219ffb6f37d5a39b4fd2db2355b23683060029c35f7/charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a2bc9f351a75ef49d664206d51f8e5ede9da246602dc2d2726837620ea034b2", size = 151367, upload_time = "2024-12-24T18:12:13.177Z" }, + { url = "https://files.pythonhosted.org/packages/54/54/2412a5b093acb17f0222de007cc129ec0e0df198b5ad2ce5699355269dfe/charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75832c08354f595c760a804588b9357d34ec00ba1c940c15e31e96d902093770", size = 143928, upload_time = "2024-12-24T18:12:14.497Z" }, + { url = "https://files.pythonhosted.org/packages/5a/6d/e2773862b043dcf8a221342954f375392bb2ce6487bcd9f2c1b34e1d6781/charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0af291f4fe114be0280cdd29d533696a77b5b49cfde5467176ecab32353395c4", size = 146203, upload_time = "2024-12-24T18:12:15.731Z" }, + { url = "https://files.pythonhosted.org/packages/b9/f8/ca440ef60d8f8916022859885f231abb07ada3c347c03d63f283bec32ef5/charset_normalizer-3.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0167ddc8ab6508fe81860a57dd472b2ef4060e8d378f0cc555707126830f2537", size = 148082, upload_time = "2024-12-24T18:12:18.641Z" }, + { url = "https://files.pythonhosted.org/packages/04/d2/42fd330901aaa4b805a1097856c2edf5095e260a597f65def493f4b8c833/charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2a75d49014d118e4198bcee5ee0a6f25856b29b12dbf7cd012791f8a6cc5c496", size = 142053, upload_time = "2024-12-24T18:12:20.036Z" }, + { url = "https://files.pythonhosted.org/packages/9e/af/3a97a4fa3c53586f1910dadfc916e9c4f35eeada36de4108f5096cb7215f/charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:363e2f92b0f0174b2f8238240a1a30142e3db7b957a5dd5689b0e75fb717cc78", size = 150625, upload_time = "2024-12-24T18:12:22.804Z" }, + { url = "https://files.pythonhosted.org/packages/26/ae/23d6041322a3556e4da139663d02fb1b3c59a23ab2e2b56432bd2ad63ded/charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ab36c8eb7e454e34e60eb55ca5d241a5d18b2c6244f6827a30e451c42410b5f7", size = 153549, upload_time = "2024-12-24T18:12:24.163Z" }, + { url = "https://files.pythonhosted.org/packages/94/22/b8f2081c6a77cb20d97e57e0b385b481887aa08019d2459dc2858ed64871/charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:4c0907b1928a36d5a998d72d64d8eaa7244989f7aaaf947500d3a800c83a3fd6", size = 150945, upload_time = "2024-12-24T18:12:25.415Z" }, + { url = "https://files.pythonhosted.org/packages/c7/0b/c5ec5092747f801b8b093cdf5610e732b809d6cb11f4c51e35fc28d1d389/charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:04432ad9479fa40ec0f387795ddad4437a2b50417c69fa275e212933519ff294", size = 146595, upload_time = "2024-12-24T18:12:28.03Z" }, + { url = "https://files.pythonhosted.org/packages/0c/5a/0b59704c38470df6768aa154cc87b1ac7c9bb687990a1559dc8765e8627e/charset_normalizer-3.4.1-cp39-cp39-win32.whl", hash = "sha256:3bed14e9c89dcb10e8f3a29f9ccac4955aebe93c71ae803af79265c9ca5644c5", size = 95453, upload_time = "2024-12-24T18:12:29.569Z" }, + { url = "https://files.pythonhosted.org/packages/85/2d/a9790237cb4d01a6d57afadc8573c8b73c609ade20b80f4cda30802009ee/charset_normalizer-3.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:49402233c892a461407c512a19435d1ce275543138294f7ef013f0b63d5d3765", size = 102811, upload_time = "2024-12-24T18:12:30.83Z" }, + { url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767, upload_time = "2024-12-24T18:12:32.852Z" }, ] [[package]] @@ -179,106 +179,106 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593 } +sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593, upload_time = "2024-12-21T18:38:44.339Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188 }, + { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188, upload_time = "2024-12-21T18:38:41.666Z" }, ] [[package]] name = "colorama" version = "0.4.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload_time = "2022-10-25T02:36:22.414Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload_time = "2022-10-25T02:36:20.889Z" }, ] [[package]] name = "coverage" version = "7.8.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/19/4f/2251e65033ed2ce1e68f00f91a0294e0f80c80ae8c3ebbe2f12828c4cd53/coverage-7.8.0.tar.gz", hash = "sha256:7a3d62b3b03b4b6fd41a085f3574874cf946cb4604d2b4d3e8dca8cd570ca501", size = 811872 } +sdist = { url = "https://files.pythonhosted.org/packages/19/4f/2251e65033ed2ce1e68f00f91a0294e0f80c80ae8c3ebbe2f12828c4cd53/coverage-7.8.0.tar.gz", hash = "sha256:7a3d62b3b03b4b6fd41a085f3574874cf946cb4604d2b4d3e8dca8cd570ca501", size = 811872, upload_time = "2025-03-30T20:36:45.376Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/78/01/1c5e6ee4ebaaa5e079db933a9a45f61172048c7efa06648445821a201084/coverage-7.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2931f66991175369859b5fd58529cd4b73582461877ecfd859b6549869287ffe", size = 211379 }, - { url = "https://files.pythonhosted.org/packages/e9/16/a463389f5ff916963471f7c13585e5f38c6814607306b3cb4d6b4cf13384/coverage-7.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:52a523153c568d2c0ef8826f6cc23031dc86cffb8c6aeab92c4ff776e7951b28", size = 211814 }, - { url = "https://files.pythonhosted.org/packages/b8/b1/77062b0393f54d79064dfb72d2da402657d7c569cfbc724d56ac0f9c67ed/coverage-7.8.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c8a5c139aae4c35cbd7cadca1df02ea8cf28a911534fc1b0456acb0b14234f3", size = 240937 }, - { url = "https://files.pythonhosted.org/packages/d7/54/c7b00a23150083c124e908c352db03bcd33375494a4beb0c6d79b35448b9/coverage-7.8.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a26c0c795c3e0b63ec7da6efded5f0bc856d7c0b24b2ac84b4d1d7bc578d676", size = 238849 }, - { url = "https://files.pythonhosted.org/packages/f7/ec/a6b7cfebd34e7b49f844788fda94713035372b5200c23088e3bbafb30970/coverage-7.8.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:821f7bcbaa84318287115d54becb1915eece6918136c6f91045bb84e2f88739d", size = 239986 }, - { url = "https://files.pythonhosted.org/packages/21/8c/c965ecef8af54e6d9b11bfbba85d4f6a319399f5f724798498387f3209eb/coverage-7.8.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a321c61477ff8ee705b8a5fed370b5710c56b3a52d17b983d9215861e37b642a", size = 239896 }, - { url = "https://files.pythonhosted.org/packages/40/83/070550273fb4c480efa8381735969cb403fa8fd1626d74865bfaf9e4d903/coverage-7.8.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:ed2144b8a78f9d94d9515963ed273d620e07846acd5d4b0a642d4849e8d91a0c", size = 238613 }, - { url = "https://files.pythonhosted.org/packages/07/76/fbb2540495b01d996d38e9f8897b861afed356be01160ab4e25471f4fed1/coverage-7.8.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:042e7841a26498fff7a37d6fda770d17519982f5b7d8bf5278d140b67b61095f", size = 238909 }, - { url = "https://files.pythonhosted.org/packages/a3/7e/76d604db640b7d4a86e5dd730b73e96e12a8185f22b5d0799025121f4dcb/coverage-7.8.0-cp310-cp310-win32.whl", hash = "sha256:f9983d01d7705b2d1f7a95e10bbe4091fabc03a46881a256c2787637b087003f", size = 213948 }, - { url = "https://files.pythonhosted.org/packages/5c/a7/f8ce4aafb4a12ab475b56c76a71a40f427740cf496c14e943ade72e25023/coverage-7.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:5a570cd9bd20b85d1a0d7b009aaf6c110b52b5755c17be6962f8ccd65d1dbd23", size = 214844 }, - { url = "https://files.pythonhosted.org/packages/2b/77/074d201adb8383addae5784cb8e2dac60bb62bfdf28b2b10f3a3af2fda47/coverage-7.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e7ac22a0bb2c7c49f441f7a6d46c9c80d96e56f5a8bc6972529ed43c8b694e27", size = 211493 }, - { url = "https://files.pythonhosted.org/packages/a9/89/7a8efe585750fe59b48d09f871f0e0c028a7b10722b2172dfe021fa2fdd4/coverage-7.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bf13d564d310c156d1c8e53877baf2993fb3073b2fc9f69790ca6a732eb4bfea", size = 211921 }, - { url = "https://files.pythonhosted.org/packages/e9/ef/96a90c31d08a3f40c49dbe897df4f1fd51fb6583821a1a1c5ee30cc8f680/coverage-7.8.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5761c70c017c1b0d21b0815a920ffb94a670c8d5d409d9b38857874c21f70d7", size = 244556 }, - { url = "https://files.pythonhosted.org/packages/89/97/dcd5c2ce72cee9d7b0ee8c89162c24972fb987a111b92d1a3d1d19100c61/coverage-7.8.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5ff52d790c7e1628241ffbcaeb33e07d14b007b6eb00a19320c7b8a7024c040", size = 242245 }, - { url = "https://files.pythonhosted.org/packages/b2/7b/b63cbb44096141ed435843bbb251558c8e05cc835c8da31ca6ffb26d44c0/coverage-7.8.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d39fc4817fd67b3915256af5dda75fd4ee10621a3d484524487e33416c6f3543", size = 244032 }, - { url = "https://files.pythonhosted.org/packages/97/e3/7fa8c2c00a1ef530c2a42fa5df25a6971391f92739d83d67a4ee6dcf7a02/coverage-7.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b44674870709017e4b4036e3d0d6c17f06a0e6d4436422e0ad29b882c40697d2", size = 243679 }, - { url = "https://files.pythonhosted.org/packages/4f/b3/e0a59d8df9150c8a0c0841d55d6568f0a9195692136c44f3d21f1842c8f6/coverage-7.8.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8f99eb72bf27cbb167b636eb1726f590c00e1ad375002230607a844d9e9a2318", size = 241852 }, - { url = "https://files.pythonhosted.org/packages/9b/82/db347ccd57bcef150c173df2ade97976a8367a3be7160e303e43dd0c795f/coverage-7.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b571bf5341ba8c6bc02e0baeaf3b061ab993bf372d982ae509807e7f112554e9", size = 242389 }, - { url = "https://files.pythonhosted.org/packages/21/f6/3f7d7879ceb03923195d9ff294456241ed05815281f5254bc16ef71d6a20/coverage-7.8.0-cp311-cp311-win32.whl", hash = "sha256:e75a2ad7b647fd8046d58c3132d7eaf31b12d8a53c0e4b21fa9c4d23d6ee6d3c", size = 213997 }, - { url = "https://files.pythonhosted.org/packages/28/87/021189643e18ecf045dbe1e2071b2747901f229df302de01c998eeadf146/coverage-7.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:3043ba1c88b2139126fc72cb48574b90e2e0546d4c78b5299317f61b7f718b78", size = 214911 }, - { url = "https://files.pythonhosted.org/packages/aa/12/4792669473297f7973518bec373a955e267deb4339286f882439b8535b39/coverage-7.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bbb5cc845a0292e0c520656d19d7ce40e18d0e19b22cb3e0409135a575bf79fc", size = 211684 }, - { url = "https://files.pythonhosted.org/packages/be/e1/2a4ec273894000ebedd789e8f2fc3813fcaf486074f87fd1c5b2cb1c0a2b/coverage-7.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4dfd9a93db9e78666d178d4f08a5408aa3f2474ad4d0e0378ed5f2ef71640cb6", size = 211935 }, - { url = "https://files.pythonhosted.org/packages/f8/3a/7b14f6e4372786709a361729164125f6b7caf4024ce02e596c4a69bccb89/coverage-7.8.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f017a61399f13aa6d1039f75cd467be388d157cd81f1a119b9d9a68ba6f2830d", size = 245994 }, - { url = "https://files.pythonhosted.org/packages/54/80/039cc7f1f81dcbd01ea796d36d3797e60c106077e31fd1f526b85337d6a1/coverage-7.8.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0915742f4c82208ebf47a2b154a5334155ed9ef9fe6190674b8a46c2fb89cb05", size = 242885 }, - { url = "https://files.pythonhosted.org/packages/10/e0/dc8355f992b6cc2f9dcd5ef6242b62a3f73264893bc09fbb08bfcab18eb4/coverage-7.8.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a40fcf208e021eb14b0fac6bdb045c0e0cab53105f93ba0d03fd934c956143a", size = 245142 }, - { url = "https://files.pythonhosted.org/packages/43/1b/33e313b22cf50f652becb94c6e7dae25d8f02e52e44db37a82de9ac357e8/coverage-7.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a1f406a8e0995d654b2ad87c62caf6befa767885301f3b8f6f73e6f3c31ec3a6", size = 244906 }, - { url = "https://files.pythonhosted.org/packages/05/08/c0a8048e942e7f918764ccc99503e2bccffba1c42568693ce6955860365e/coverage-7.8.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:77af0f6447a582fdc7de5e06fa3757a3ef87769fbb0fdbdeba78c23049140a47", size = 243124 }, - { url = "https://files.pythonhosted.org/packages/5b/62/ea625b30623083c2aad645c9a6288ad9fc83d570f9adb913a2abdba562dd/coverage-7.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f2d32f95922927186c6dbc8bc60df0d186b6edb828d299ab10898ef3f40052fe", size = 244317 }, - { url = "https://files.pythonhosted.org/packages/62/cb/3871f13ee1130a6c8f020e2f71d9ed269e1e2124aa3374d2180ee451cee9/coverage-7.8.0-cp312-cp312-win32.whl", hash = "sha256:769773614e676f9d8e8a0980dd7740f09a6ea386d0f383db6821df07d0f08545", size = 214170 }, - { url = "https://files.pythonhosted.org/packages/88/26/69fe1193ab0bfa1eb7a7c0149a066123611baba029ebb448500abd8143f9/coverage-7.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:e5d2b9be5b0693cf21eb4ce0ec8d211efb43966f6657807f6859aab3814f946b", size = 214969 }, - { url = "https://files.pythonhosted.org/packages/f3/21/87e9b97b568e223f3438d93072479c2f36cc9b3f6b9f7094b9d50232acc0/coverage-7.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ac46d0c2dd5820ce93943a501ac5f6548ea81594777ca585bf002aa8854cacd", size = 211708 }, - { url = "https://files.pythonhosted.org/packages/75/be/882d08b28a0d19c9c4c2e8a1c6ebe1f79c9c839eb46d4fca3bd3b34562b9/coverage-7.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:771eb7587a0563ca5bb6f622b9ed7f9d07bd08900f7589b4febff05f469bea00", size = 211981 }, - { url = "https://files.pythonhosted.org/packages/7a/1d/ce99612ebd58082fbe3f8c66f6d8d5694976c76a0d474503fa70633ec77f/coverage-7.8.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42421e04069fb2cbcbca5a696c4050b84a43b05392679d4068acbe65449b5c64", size = 245495 }, - { url = "https://files.pythonhosted.org/packages/dc/8d/6115abe97df98db6b2bd76aae395fcc941d039a7acd25f741312ced9a78f/coverage-7.8.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:554fec1199d93ab30adaa751db68acec2b41c5602ac944bb19187cb9a41a8067", size = 242538 }, - { url = "https://files.pythonhosted.org/packages/cb/74/2f8cc196643b15bc096d60e073691dadb3dca48418f08bc78dd6e899383e/coverage-7.8.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aaeb00761f985007b38cf463b1d160a14a22c34eb3f6a39d9ad6fc27cb73008", size = 244561 }, - { url = "https://files.pythonhosted.org/packages/22/70/c10c77cd77970ac965734fe3419f2c98665f6e982744a9bfb0e749d298f4/coverage-7.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:581a40c7b94921fffd6457ffe532259813fc68eb2bdda60fa8cc343414ce3733", size = 244633 }, - { url = "https://files.pythonhosted.org/packages/38/5a/4f7569d946a07c952688debee18c2bb9ab24f88027e3d71fd25dbc2f9dca/coverage-7.8.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f319bae0321bc838e205bf9e5bc28f0a3165f30c203b610f17ab5552cff90323", size = 242712 }, - { url = "https://files.pythonhosted.org/packages/bb/a1/03a43b33f50475a632a91ea8c127f7e35e53786dbe6781c25f19fd5a65f8/coverage-7.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:04bfec25a8ef1c5f41f5e7e5c842f6b615599ca8ba8391ec33a9290d9d2db3a3", size = 244000 }, - { url = "https://files.pythonhosted.org/packages/6a/89/ab6c43b1788a3128e4d1b7b54214548dcad75a621f9d277b14d16a80d8a1/coverage-7.8.0-cp313-cp313-win32.whl", hash = "sha256:dd19608788b50eed889e13a5d71d832edc34fc9dfce606f66e8f9f917eef910d", size = 214195 }, - { url = "https://files.pythonhosted.org/packages/12/12/6bf5f9a8b063d116bac536a7fb594fc35cb04981654cccb4bbfea5dcdfa0/coverage-7.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:a9abbccd778d98e9c7e85038e35e91e67f5b520776781d9a1e2ee9d400869487", size = 214998 }, - { url = "https://files.pythonhosted.org/packages/2a/e6/1e9df74ef7a1c983a9c7443dac8aac37a46f1939ae3499424622e72a6f78/coverage-7.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:18c5ae6d061ad5b3e7eef4363fb27a0576012a7447af48be6c75b88494c6cf25", size = 212541 }, - { url = "https://files.pythonhosted.org/packages/04/51/c32174edb7ee49744e2e81c4b1414ac9df3dacfcb5b5f273b7f285ad43f6/coverage-7.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:95aa6ae391a22bbbce1b77ddac846c98c5473de0372ba5c463480043a07bff42", size = 212767 }, - { url = "https://files.pythonhosted.org/packages/e9/8f/f454cbdb5212f13f29d4a7983db69169f1937e869a5142bce983ded52162/coverage-7.8.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e013b07ba1c748dacc2a80e69a46286ff145935f260eb8c72df7185bf048f502", size = 256997 }, - { url = "https://files.pythonhosted.org/packages/e6/74/2bf9e78b321216d6ee90a81e5c22f912fc428442c830c4077b4a071db66f/coverage-7.8.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d766a4f0e5aa1ba056ec3496243150698dc0481902e2b8559314368717be82b1", size = 252708 }, - { url = "https://files.pythonhosted.org/packages/92/4d/50d7eb1e9a6062bee6e2f92e78b0998848a972e9afad349b6cdde6fa9e32/coverage-7.8.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad80e6b4a0c3cb6f10f29ae4c60e991f424e6b14219d46f1e7d442b938ee68a4", size = 255046 }, - { url = "https://files.pythonhosted.org/packages/40/9e/71fb4e7402a07c4198ab44fc564d09d7d0ffca46a9fb7b0a7b929e7641bd/coverage-7.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b87eb6fc9e1bb8f98892a2458781348fa37e6925f35bb6ceb9d4afd54ba36c73", size = 256139 }, - { url = "https://files.pythonhosted.org/packages/49/1a/78d37f7a42b5beff027e807c2843185961fdae7fe23aad5a4837c93f9d25/coverage-7.8.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d1ba00ae33be84066cfbe7361d4e04dec78445b2b88bdb734d0d1cbab916025a", size = 254307 }, - { url = "https://files.pythonhosted.org/packages/58/e9/8fb8e0ff6bef5e170ee19d59ca694f9001b2ec085dc99b4f65c128bb3f9a/coverage-7.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f3c38e4e5ccbdc9198aecc766cedbb134b2d89bf64533973678dfcf07effd883", size = 255116 }, - { url = "https://files.pythonhosted.org/packages/56/b0/d968ecdbe6fe0a863de7169bbe9e8a476868959f3af24981f6a10d2b6924/coverage-7.8.0-cp313-cp313t-win32.whl", hash = "sha256:379fe315e206b14e21db5240f89dc0774bdd3e25c3c58c2c733c99eca96f1ada", size = 214909 }, - { url = "https://files.pythonhosted.org/packages/87/e9/d6b7ef9fecf42dfb418d93544af47c940aa83056c49e6021a564aafbc91f/coverage-7.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2e4b6b87bb0c846a9315e3ab4be2d52fac905100565f4b92f02c445c8799e257", size = 216068 }, - { url = "https://files.pythonhosted.org/packages/60/0c/5da94be095239814bf2730a28cffbc48d6df4304e044f80d39e1ae581997/coverage-7.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fa260de59dfb143af06dcf30c2be0b200bed2a73737a8a59248fcb9fa601ef0f", size = 211377 }, - { url = "https://files.pythonhosted.org/packages/d5/cb/b9e93ebf193a0bb89dbcd4f73d7b0e6ecb7c1b6c016671950e25f041835e/coverage-7.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:96121edfa4c2dfdda409877ea8608dd01de816a4dc4a0523356067b305e4e17a", size = 211803 }, - { url = "https://files.pythonhosted.org/packages/78/1a/cdbfe9e1bb14d3afcaf6bb6e1b9ba76c72666e329cd06865bbd241efd652/coverage-7.8.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b8af63b9afa1031c0ef05b217faa598f3069148eeee6bb24b79da9012423b82", size = 240561 }, - { url = "https://files.pythonhosted.org/packages/59/04/57f1223f26ac018d7ce791bfa65b0c29282de3e041c1cd3ed430cfeac5a5/coverage-7.8.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:89b1f4af0d4afe495cd4787a68e00f30f1d15939f550e869de90a86efa7e0814", size = 238488 }, - { url = "https://files.pythonhosted.org/packages/b7/b1/0f25516ae2a35e265868670384feebe64e7857d9cffeeb3887b0197e2ba2/coverage-7.8.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94ec0be97723ae72d63d3aa41961a0b9a6f5a53ff599813c324548d18e3b9e8c", size = 239589 }, - { url = "https://files.pythonhosted.org/packages/e0/a4/99d88baac0d1d5a46ceef2dd687aac08fffa8795e4c3e71b6f6c78e14482/coverage-7.8.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8a1d96e780bdb2d0cbb297325711701f7c0b6f89199a57f2049e90064c29f6bd", size = 239366 }, - { url = "https://files.pythonhosted.org/packages/ea/9e/1db89e135feb827a868ed15f8fc857160757f9cab140ffee21342c783ceb/coverage-7.8.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:f1d8a2a57b47142b10374902777e798784abf400a004b14f1b0b9eaf1e528ba4", size = 237591 }, - { url = "https://files.pythonhosted.org/packages/1b/6d/ac4d6fdfd0e201bc82d1b08adfacb1e34b40d21a22cdd62cfaf3c1828566/coverage-7.8.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:cf60dd2696b457b710dd40bf17ad269d5f5457b96442f7f85722bdb16fa6c899", size = 238572 }, - { url = "https://files.pythonhosted.org/packages/25/5e/917cbe617c230f7f1745b6a13e780a3a1cd1cf328dbcd0fd8d7ec52858cd/coverage-7.8.0-cp39-cp39-win32.whl", hash = "sha256:be945402e03de47ba1872cd5236395e0f4ad635526185a930735f66710e1bd3f", size = 213966 }, - { url = "https://files.pythonhosted.org/packages/bd/93/72b434fe550135869f9ea88dd36068af19afce666db576e059e75177e813/coverage-7.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:90e7fbc6216ecaffa5a880cdc9c77b7418c1dcb166166b78dbc630d07f278cc3", size = 214852 }, - { url = "https://files.pythonhosted.org/packages/c4/f1/1da77bb4c920aa30e82fa9b6ea065da3467977c2e5e032e38e66f1c57ffd/coverage-7.8.0-pp39.pp310.pp311-none-any.whl", hash = "sha256:b8194fb8e50d556d5849753de991d390c5a1edeeba50f68e3a9253fbd8bf8ccd", size = 203443 }, - { url = "https://files.pythonhosted.org/packages/59/f1/4da7717f0063a222db253e7121bd6a56f6fb1ba439dcc36659088793347c/coverage-7.8.0-py3-none-any.whl", hash = "sha256:dbf364b4c5e7bae9250528167dfe40219b62e2d573c854d74be213e1e52069f7", size = 203435 }, + { url = "https://files.pythonhosted.org/packages/78/01/1c5e6ee4ebaaa5e079db933a9a45f61172048c7efa06648445821a201084/coverage-7.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2931f66991175369859b5fd58529cd4b73582461877ecfd859b6549869287ffe", size = 211379, upload_time = "2025-03-30T20:34:53.904Z" }, + { url = "https://files.pythonhosted.org/packages/e9/16/a463389f5ff916963471f7c13585e5f38c6814607306b3cb4d6b4cf13384/coverage-7.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:52a523153c568d2c0ef8826f6cc23031dc86cffb8c6aeab92c4ff776e7951b28", size = 211814, upload_time = "2025-03-30T20:34:56.959Z" }, + { url = "https://files.pythonhosted.org/packages/b8/b1/77062b0393f54d79064dfb72d2da402657d7c569cfbc724d56ac0f9c67ed/coverage-7.8.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c8a5c139aae4c35cbd7cadca1df02ea8cf28a911534fc1b0456acb0b14234f3", size = 240937, upload_time = "2025-03-30T20:34:58.751Z" }, + { url = "https://files.pythonhosted.org/packages/d7/54/c7b00a23150083c124e908c352db03bcd33375494a4beb0c6d79b35448b9/coverage-7.8.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a26c0c795c3e0b63ec7da6efded5f0bc856d7c0b24b2ac84b4d1d7bc578d676", size = 238849, upload_time = "2025-03-30T20:35:00.521Z" }, + { url = "https://files.pythonhosted.org/packages/f7/ec/a6b7cfebd34e7b49f844788fda94713035372b5200c23088e3bbafb30970/coverage-7.8.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:821f7bcbaa84318287115d54becb1915eece6918136c6f91045bb84e2f88739d", size = 239986, upload_time = "2025-03-30T20:35:02.307Z" }, + { url = "https://files.pythonhosted.org/packages/21/8c/c965ecef8af54e6d9b11bfbba85d4f6a319399f5f724798498387f3209eb/coverage-7.8.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a321c61477ff8ee705b8a5fed370b5710c56b3a52d17b983d9215861e37b642a", size = 239896, upload_time = "2025-03-30T20:35:04.141Z" }, + { url = "https://files.pythonhosted.org/packages/40/83/070550273fb4c480efa8381735969cb403fa8fd1626d74865bfaf9e4d903/coverage-7.8.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:ed2144b8a78f9d94d9515963ed273d620e07846acd5d4b0a642d4849e8d91a0c", size = 238613, upload_time = "2025-03-30T20:35:05.889Z" }, + { url = "https://files.pythonhosted.org/packages/07/76/fbb2540495b01d996d38e9f8897b861afed356be01160ab4e25471f4fed1/coverage-7.8.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:042e7841a26498fff7a37d6fda770d17519982f5b7d8bf5278d140b67b61095f", size = 238909, upload_time = "2025-03-30T20:35:07.76Z" }, + { url = "https://files.pythonhosted.org/packages/a3/7e/76d604db640b7d4a86e5dd730b73e96e12a8185f22b5d0799025121f4dcb/coverage-7.8.0-cp310-cp310-win32.whl", hash = "sha256:f9983d01d7705b2d1f7a95e10bbe4091fabc03a46881a256c2787637b087003f", size = 213948, upload_time = "2025-03-30T20:35:09.144Z" }, + { url = "https://files.pythonhosted.org/packages/5c/a7/f8ce4aafb4a12ab475b56c76a71a40f427740cf496c14e943ade72e25023/coverage-7.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:5a570cd9bd20b85d1a0d7b009aaf6c110b52b5755c17be6962f8ccd65d1dbd23", size = 214844, upload_time = "2025-03-30T20:35:10.734Z" }, + { url = "https://files.pythonhosted.org/packages/2b/77/074d201adb8383addae5784cb8e2dac60bb62bfdf28b2b10f3a3af2fda47/coverage-7.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e7ac22a0bb2c7c49f441f7a6d46c9c80d96e56f5a8bc6972529ed43c8b694e27", size = 211493, upload_time = "2025-03-30T20:35:12.286Z" }, + { url = "https://files.pythonhosted.org/packages/a9/89/7a8efe585750fe59b48d09f871f0e0c028a7b10722b2172dfe021fa2fdd4/coverage-7.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bf13d564d310c156d1c8e53877baf2993fb3073b2fc9f69790ca6a732eb4bfea", size = 211921, upload_time = "2025-03-30T20:35:14.18Z" }, + { url = "https://files.pythonhosted.org/packages/e9/ef/96a90c31d08a3f40c49dbe897df4f1fd51fb6583821a1a1c5ee30cc8f680/coverage-7.8.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5761c70c017c1b0d21b0815a920ffb94a670c8d5d409d9b38857874c21f70d7", size = 244556, upload_time = "2025-03-30T20:35:15.616Z" }, + { url = "https://files.pythonhosted.org/packages/89/97/dcd5c2ce72cee9d7b0ee8c89162c24972fb987a111b92d1a3d1d19100c61/coverage-7.8.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5ff52d790c7e1628241ffbcaeb33e07d14b007b6eb00a19320c7b8a7024c040", size = 242245, upload_time = "2025-03-30T20:35:18.648Z" }, + { url = "https://files.pythonhosted.org/packages/b2/7b/b63cbb44096141ed435843bbb251558c8e05cc835c8da31ca6ffb26d44c0/coverage-7.8.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d39fc4817fd67b3915256af5dda75fd4ee10621a3d484524487e33416c6f3543", size = 244032, upload_time = "2025-03-30T20:35:20.131Z" }, + { url = "https://files.pythonhosted.org/packages/97/e3/7fa8c2c00a1ef530c2a42fa5df25a6971391f92739d83d67a4ee6dcf7a02/coverage-7.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b44674870709017e4b4036e3d0d6c17f06a0e6d4436422e0ad29b882c40697d2", size = 243679, upload_time = "2025-03-30T20:35:21.636Z" }, + { url = "https://files.pythonhosted.org/packages/4f/b3/e0a59d8df9150c8a0c0841d55d6568f0a9195692136c44f3d21f1842c8f6/coverage-7.8.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8f99eb72bf27cbb167b636eb1726f590c00e1ad375002230607a844d9e9a2318", size = 241852, upload_time = "2025-03-30T20:35:23.525Z" }, + { url = "https://files.pythonhosted.org/packages/9b/82/db347ccd57bcef150c173df2ade97976a8367a3be7160e303e43dd0c795f/coverage-7.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b571bf5341ba8c6bc02e0baeaf3b061ab993bf372d982ae509807e7f112554e9", size = 242389, upload_time = "2025-03-30T20:35:25.09Z" }, + { url = "https://files.pythonhosted.org/packages/21/f6/3f7d7879ceb03923195d9ff294456241ed05815281f5254bc16ef71d6a20/coverage-7.8.0-cp311-cp311-win32.whl", hash = "sha256:e75a2ad7b647fd8046d58c3132d7eaf31b12d8a53c0e4b21fa9c4d23d6ee6d3c", size = 213997, upload_time = "2025-03-30T20:35:26.914Z" }, + { url = "https://files.pythonhosted.org/packages/28/87/021189643e18ecf045dbe1e2071b2747901f229df302de01c998eeadf146/coverage-7.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:3043ba1c88b2139126fc72cb48574b90e2e0546d4c78b5299317f61b7f718b78", size = 214911, upload_time = "2025-03-30T20:35:28.498Z" }, + { url = "https://files.pythonhosted.org/packages/aa/12/4792669473297f7973518bec373a955e267deb4339286f882439b8535b39/coverage-7.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bbb5cc845a0292e0c520656d19d7ce40e18d0e19b22cb3e0409135a575bf79fc", size = 211684, upload_time = "2025-03-30T20:35:29.959Z" }, + { url = "https://files.pythonhosted.org/packages/be/e1/2a4ec273894000ebedd789e8f2fc3813fcaf486074f87fd1c5b2cb1c0a2b/coverage-7.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4dfd9a93db9e78666d178d4f08a5408aa3f2474ad4d0e0378ed5f2ef71640cb6", size = 211935, upload_time = "2025-03-30T20:35:31.912Z" }, + { url = "https://files.pythonhosted.org/packages/f8/3a/7b14f6e4372786709a361729164125f6b7caf4024ce02e596c4a69bccb89/coverage-7.8.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f017a61399f13aa6d1039f75cd467be388d157cd81f1a119b9d9a68ba6f2830d", size = 245994, upload_time = "2025-03-30T20:35:33.455Z" }, + { url = "https://files.pythonhosted.org/packages/54/80/039cc7f1f81dcbd01ea796d36d3797e60c106077e31fd1f526b85337d6a1/coverage-7.8.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0915742f4c82208ebf47a2b154a5334155ed9ef9fe6190674b8a46c2fb89cb05", size = 242885, upload_time = "2025-03-30T20:35:35.354Z" }, + { url = "https://files.pythonhosted.org/packages/10/e0/dc8355f992b6cc2f9dcd5ef6242b62a3f73264893bc09fbb08bfcab18eb4/coverage-7.8.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a40fcf208e021eb14b0fac6bdb045c0e0cab53105f93ba0d03fd934c956143a", size = 245142, upload_time = "2025-03-30T20:35:37.121Z" }, + { url = "https://files.pythonhosted.org/packages/43/1b/33e313b22cf50f652becb94c6e7dae25d8f02e52e44db37a82de9ac357e8/coverage-7.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a1f406a8e0995d654b2ad87c62caf6befa767885301f3b8f6f73e6f3c31ec3a6", size = 244906, upload_time = "2025-03-30T20:35:39.07Z" }, + { url = "https://files.pythonhosted.org/packages/05/08/c0a8048e942e7f918764ccc99503e2bccffba1c42568693ce6955860365e/coverage-7.8.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:77af0f6447a582fdc7de5e06fa3757a3ef87769fbb0fdbdeba78c23049140a47", size = 243124, upload_time = "2025-03-30T20:35:40.598Z" }, + { url = "https://files.pythonhosted.org/packages/5b/62/ea625b30623083c2aad645c9a6288ad9fc83d570f9adb913a2abdba562dd/coverage-7.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f2d32f95922927186c6dbc8bc60df0d186b6edb828d299ab10898ef3f40052fe", size = 244317, upload_time = "2025-03-30T20:35:42.204Z" }, + { url = "https://files.pythonhosted.org/packages/62/cb/3871f13ee1130a6c8f020e2f71d9ed269e1e2124aa3374d2180ee451cee9/coverage-7.8.0-cp312-cp312-win32.whl", hash = "sha256:769773614e676f9d8e8a0980dd7740f09a6ea386d0f383db6821df07d0f08545", size = 214170, upload_time = "2025-03-30T20:35:44.216Z" }, + { url = "https://files.pythonhosted.org/packages/88/26/69fe1193ab0bfa1eb7a7c0149a066123611baba029ebb448500abd8143f9/coverage-7.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:e5d2b9be5b0693cf21eb4ce0ec8d211efb43966f6657807f6859aab3814f946b", size = 214969, upload_time = "2025-03-30T20:35:45.797Z" }, + { url = "https://files.pythonhosted.org/packages/f3/21/87e9b97b568e223f3438d93072479c2f36cc9b3f6b9f7094b9d50232acc0/coverage-7.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ac46d0c2dd5820ce93943a501ac5f6548ea81594777ca585bf002aa8854cacd", size = 211708, upload_time = "2025-03-30T20:35:47.417Z" }, + { url = "https://files.pythonhosted.org/packages/75/be/882d08b28a0d19c9c4c2e8a1c6ebe1f79c9c839eb46d4fca3bd3b34562b9/coverage-7.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:771eb7587a0563ca5bb6f622b9ed7f9d07bd08900f7589b4febff05f469bea00", size = 211981, upload_time = "2025-03-30T20:35:49.002Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1d/ce99612ebd58082fbe3f8c66f6d8d5694976c76a0d474503fa70633ec77f/coverage-7.8.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42421e04069fb2cbcbca5a696c4050b84a43b05392679d4068acbe65449b5c64", size = 245495, upload_time = "2025-03-30T20:35:51.073Z" }, + { url = "https://files.pythonhosted.org/packages/dc/8d/6115abe97df98db6b2bd76aae395fcc941d039a7acd25f741312ced9a78f/coverage-7.8.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:554fec1199d93ab30adaa751db68acec2b41c5602ac944bb19187cb9a41a8067", size = 242538, upload_time = "2025-03-30T20:35:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/cb/74/2f8cc196643b15bc096d60e073691dadb3dca48418f08bc78dd6e899383e/coverage-7.8.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aaeb00761f985007b38cf463b1d160a14a22c34eb3f6a39d9ad6fc27cb73008", size = 244561, upload_time = "2025-03-30T20:35:54.658Z" }, + { url = "https://files.pythonhosted.org/packages/22/70/c10c77cd77970ac965734fe3419f2c98665f6e982744a9bfb0e749d298f4/coverage-7.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:581a40c7b94921fffd6457ffe532259813fc68eb2bdda60fa8cc343414ce3733", size = 244633, upload_time = "2025-03-30T20:35:56.221Z" }, + { url = "https://files.pythonhosted.org/packages/38/5a/4f7569d946a07c952688debee18c2bb9ab24f88027e3d71fd25dbc2f9dca/coverage-7.8.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f319bae0321bc838e205bf9e5bc28f0a3165f30c203b610f17ab5552cff90323", size = 242712, upload_time = "2025-03-30T20:35:57.801Z" }, + { url = "https://files.pythonhosted.org/packages/bb/a1/03a43b33f50475a632a91ea8c127f7e35e53786dbe6781c25f19fd5a65f8/coverage-7.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:04bfec25a8ef1c5f41f5e7e5c842f6b615599ca8ba8391ec33a9290d9d2db3a3", size = 244000, upload_time = "2025-03-30T20:35:59.378Z" }, + { url = "https://files.pythonhosted.org/packages/6a/89/ab6c43b1788a3128e4d1b7b54214548dcad75a621f9d277b14d16a80d8a1/coverage-7.8.0-cp313-cp313-win32.whl", hash = "sha256:dd19608788b50eed889e13a5d71d832edc34fc9dfce606f66e8f9f917eef910d", size = 214195, upload_time = "2025-03-30T20:36:01.005Z" }, + { url = "https://files.pythonhosted.org/packages/12/12/6bf5f9a8b063d116bac536a7fb594fc35cb04981654cccb4bbfea5dcdfa0/coverage-7.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:a9abbccd778d98e9c7e85038e35e91e67f5b520776781d9a1e2ee9d400869487", size = 214998, upload_time = "2025-03-30T20:36:03.006Z" }, + { url = "https://files.pythonhosted.org/packages/2a/e6/1e9df74ef7a1c983a9c7443dac8aac37a46f1939ae3499424622e72a6f78/coverage-7.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:18c5ae6d061ad5b3e7eef4363fb27a0576012a7447af48be6c75b88494c6cf25", size = 212541, upload_time = "2025-03-30T20:36:04.638Z" }, + { url = "https://files.pythonhosted.org/packages/04/51/c32174edb7ee49744e2e81c4b1414ac9df3dacfcb5b5f273b7f285ad43f6/coverage-7.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:95aa6ae391a22bbbce1b77ddac846c98c5473de0372ba5c463480043a07bff42", size = 212767, upload_time = "2025-03-30T20:36:06.503Z" }, + { url = "https://files.pythonhosted.org/packages/e9/8f/f454cbdb5212f13f29d4a7983db69169f1937e869a5142bce983ded52162/coverage-7.8.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e013b07ba1c748dacc2a80e69a46286ff145935f260eb8c72df7185bf048f502", size = 256997, upload_time = "2025-03-30T20:36:08.137Z" }, + { url = "https://files.pythonhosted.org/packages/e6/74/2bf9e78b321216d6ee90a81e5c22f912fc428442c830c4077b4a071db66f/coverage-7.8.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d766a4f0e5aa1ba056ec3496243150698dc0481902e2b8559314368717be82b1", size = 252708, upload_time = "2025-03-30T20:36:09.781Z" }, + { url = "https://files.pythonhosted.org/packages/92/4d/50d7eb1e9a6062bee6e2f92e78b0998848a972e9afad349b6cdde6fa9e32/coverage-7.8.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad80e6b4a0c3cb6f10f29ae4c60e991f424e6b14219d46f1e7d442b938ee68a4", size = 255046, upload_time = "2025-03-30T20:36:11.409Z" }, + { url = "https://files.pythonhosted.org/packages/40/9e/71fb4e7402a07c4198ab44fc564d09d7d0ffca46a9fb7b0a7b929e7641bd/coverage-7.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b87eb6fc9e1bb8f98892a2458781348fa37e6925f35bb6ceb9d4afd54ba36c73", size = 256139, upload_time = "2025-03-30T20:36:13.86Z" }, + { url = "https://files.pythonhosted.org/packages/49/1a/78d37f7a42b5beff027e807c2843185961fdae7fe23aad5a4837c93f9d25/coverage-7.8.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d1ba00ae33be84066cfbe7361d4e04dec78445b2b88bdb734d0d1cbab916025a", size = 254307, upload_time = "2025-03-30T20:36:16.074Z" }, + { url = "https://files.pythonhosted.org/packages/58/e9/8fb8e0ff6bef5e170ee19d59ca694f9001b2ec085dc99b4f65c128bb3f9a/coverage-7.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f3c38e4e5ccbdc9198aecc766cedbb134b2d89bf64533973678dfcf07effd883", size = 255116, upload_time = "2025-03-30T20:36:18.033Z" }, + { url = "https://files.pythonhosted.org/packages/56/b0/d968ecdbe6fe0a863de7169bbe9e8a476868959f3af24981f6a10d2b6924/coverage-7.8.0-cp313-cp313t-win32.whl", hash = "sha256:379fe315e206b14e21db5240f89dc0774bdd3e25c3c58c2c733c99eca96f1ada", size = 214909, upload_time = "2025-03-30T20:36:19.644Z" }, + { url = "https://files.pythonhosted.org/packages/87/e9/d6b7ef9fecf42dfb418d93544af47c940aa83056c49e6021a564aafbc91f/coverage-7.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2e4b6b87bb0c846a9315e3ab4be2d52fac905100565f4b92f02c445c8799e257", size = 216068, upload_time = "2025-03-30T20:36:21.282Z" }, + { url = "https://files.pythonhosted.org/packages/60/0c/5da94be095239814bf2730a28cffbc48d6df4304e044f80d39e1ae581997/coverage-7.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fa260de59dfb143af06dcf30c2be0b200bed2a73737a8a59248fcb9fa601ef0f", size = 211377, upload_time = "2025-03-30T20:36:23.298Z" }, + { url = "https://files.pythonhosted.org/packages/d5/cb/b9e93ebf193a0bb89dbcd4f73d7b0e6ecb7c1b6c016671950e25f041835e/coverage-7.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:96121edfa4c2dfdda409877ea8608dd01de816a4dc4a0523356067b305e4e17a", size = 211803, upload_time = "2025-03-30T20:36:25.74Z" }, + { url = "https://files.pythonhosted.org/packages/78/1a/cdbfe9e1bb14d3afcaf6bb6e1b9ba76c72666e329cd06865bbd241efd652/coverage-7.8.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b8af63b9afa1031c0ef05b217faa598f3069148eeee6bb24b79da9012423b82", size = 240561, upload_time = "2025-03-30T20:36:27.548Z" }, + { url = "https://files.pythonhosted.org/packages/59/04/57f1223f26ac018d7ce791bfa65b0c29282de3e041c1cd3ed430cfeac5a5/coverage-7.8.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:89b1f4af0d4afe495cd4787a68e00f30f1d15939f550e869de90a86efa7e0814", size = 238488, upload_time = "2025-03-30T20:36:29.175Z" }, + { url = "https://files.pythonhosted.org/packages/b7/b1/0f25516ae2a35e265868670384feebe64e7857d9cffeeb3887b0197e2ba2/coverage-7.8.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94ec0be97723ae72d63d3aa41961a0b9a6f5a53ff599813c324548d18e3b9e8c", size = 239589, upload_time = "2025-03-30T20:36:30.876Z" }, + { url = "https://files.pythonhosted.org/packages/e0/a4/99d88baac0d1d5a46ceef2dd687aac08fffa8795e4c3e71b6f6c78e14482/coverage-7.8.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8a1d96e780bdb2d0cbb297325711701f7c0b6f89199a57f2049e90064c29f6bd", size = 239366, upload_time = "2025-03-30T20:36:32.563Z" }, + { url = "https://files.pythonhosted.org/packages/ea/9e/1db89e135feb827a868ed15f8fc857160757f9cab140ffee21342c783ceb/coverage-7.8.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:f1d8a2a57b47142b10374902777e798784abf400a004b14f1b0b9eaf1e528ba4", size = 237591, upload_time = "2025-03-30T20:36:34.721Z" }, + { url = "https://files.pythonhosted.org/packages/1b/6d/ac4d6fdfd0e201bc82d1b08adfacb1e34b40d21a22cdd62cfaf3c1828566/coverage-7.8.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:cf60dd2696b457b710dd40bf17ad269d5f5457b96442f7f85722bdb16fa6c899", size = 238572, upload_time = "2025-03-30T20:36:36.805Z" }, + { url = "https://files.pythonhosted.org/packages/25/5e/917cbe617c230f7f1745b6a13e780a3a1cd1cf328dbcd0fd8d7ec52858cd/coverage-7.8.0-cp39-cp39-win32.whl", hash = "sha256:be945402e03de47ba1872cd5236395e0f4ad635526185a930735f66710e1bd3f", size = 213966, upload_time = "2025-03-30T20:36:38.551Z" }, + { url = "https://files.pythonhosted.org/packages/bd/93/72b434fe550135869f9ea88dd36068af19afce666db576e059e75177e813/coverage-7.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:90e7fbc6216ecaffa5a880cdc9c77b7418c1dcb166166b78dbc630d07f278cc3", size = 214852, upload_time = "2025-03-30T20:36:40.209Z" }, + { url = "https://files.pythonhosted.org/packages/c4/f1/1da77bb4c920aa30e82fa9b6ea065da3467977c2e5e032e38e66f1c57ffd/coverage-7.8.0-pp39.pp310.pp311-none-any.whl", hash = "sha256:b8194fb8e50d556d5849753de991d390c5a1edeeba50f68e3a9253fbd8bf8ccd", size = 203443, upload_time = "2025-03-30T20:36:41.959Z" }, + { url = "https://files.pythonhosted.org/packages/59/f1/4da7717f0063a222db253e7121bd6a56f6fb1ba439dcc36659088793347c/coverage-7.8.0-py3-none-any.whl", hash = "sha256:dbf364b4c5e7bae9250528167dfe40219b62e2d573c854d74be213e1e52069f7", size = 203435, upload_time = "2025-03-30T20:36:43.61Z" }, ] [[package]] name = "distro" version = "1.9.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722 } +sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722, upload_time = "2023-12-24T09:54:32.31Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277 }, + { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload_time = "2023-12-24T09:54:30.421Z" }, ] [[package]] name = "dnspython" version = "2.7.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b5/4a/263763cb2ba3816dd94b08ad3a33d5fdae34ecb856678773cc40a3605829/dnspython-2.7.0.tar.gz", hash = "sha256:ce9c432eda0dc91cf618a5cedf1a4e142651196bbcd2c80e89ed5a907e5cfaf1", size = 345197 } +sdist = { url = "https://files.pythonhosted.org/packages/b5/4a/263763cb2ba3816dd94b08ad3a33d5fdae34ecb856678773cc40a3605829/dnspython-2.7.0.tar.gz", hash = "sha256:ce9c432eda0dc91cf618a5cedf1a4e142651196bbcd2c80e89ed5a907e5cfaf1", size = 345197, upload_time = "2024-10-05T20:14:59.362Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/68/1b/e0a87d256e40e8c888847551b20a017a6b98139178505dc7ffb96f04e954/dnspython-2.7.0-py3-none-any.whl", hash = "sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86", size = 313632 }, + { url = "https://files.pythonhosted.org/packages/68/1b/e0a87d256e40e8c888847551b20a017a6b98139178505dc7ffb96f04e954/dnspython-2.7.0-py3-none-any.whl", hash = "sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86", size = 313632, upload_time = "2024-10-05T20:14:57.687Z" }, ] [[package]] @@ -289,18 +289,18 @@ dependencies = [ { name = "dnspython" }, { name = "idna" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/48/ce/13508a1ec3f8bb981ae4ca79ea40384becc868bfae97fd1c942bb3a001b1/email_validator-2.2.0.tar.gz", hash = "sha256:cb690f344c617a714f22e66ae771445a1ceb46821152df8e165c5f9a364582b7", size = 48967 } +sdist = { url = "https://files.pythonhosted.org/packages/48/ce/13508a1ec3f8bb981ae4ca79ea40384becc868bfae97fd1c942bb3a001b1/email_validator-2.2.0.tar.gz", hash = "sha256:cb690f344c617a714f22e66ae771445a1ceb46821152df8e165c5f9a364582b7", size = 48967, upload_time = "2024-06-20T11:30:30.034Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d7/ee/bf0adb559ad3c786f12bcbc9296b3f5675f529199bef03e2df281fa1fadb/email_validator-2.2.0-py3-none-any.whl", hash = "sha256:561977c2d73ce3611850a06fa56b414621e0c8faa9d66f2611407d87465da631", size = 33521 }, + { url = "https://files.pythonhosted.org/packages/d7/ee/bf0adb559ad3c786f12bcbc9296b3f5675f529199bef03e2df281fa1fadb/email_validator-2.2.0-py3-none-any.whl", hash = "sha256:561977c2d73ce3611850a06fa56b414621e0c8faa9d66f2611407d87465da631", size = 33521, upload_time = "2024-06-20T11:30:28.248Z" }, ] [[package]] name = "exceptiongroup" version = "1.2.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/09/35/2495c4ac46b980e4ca1f6ad6db102322ef3ad2410b79fdde159a4b0f3b92/exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc", size = 28883 } +sdist = { url = "https://files.pythonhosted.org/packages/09/35/2495c4ac46b980e4ca1f6ad6db102322ef3ad2410b79fdde159a4b0f3b92/exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc", size = 28883, upload_time = "2024-07-12T22:26:00.161Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453 }, + { url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453, upload_time = "2024-07-12T22:25:58.476Z" }, ] [[package]] @@ -312,9 +312,9 @@ dependencies = [ { name = "starlette" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f4/55/ae499352d82338331ca1e28c7f4a63bfd09479b16395dce38cf50a39e2c2/fastapi-0.115.12.tar.gz", hash = "sha256:1e2c2a2646905f9e83d32f04a3f86aff4a286669c6c950ca95b5fd68c2602681", size = 295236 } +sdist = { url = "https://files.pythonhosted.org/packages/f4/55/ae499352d82338331ca1e28c7f4a63bfd09479b16395dce38cf50a39e2c2/fastapi-0.115.12.tar.gz", hash = "sha256:1e2c2a2646905f9e83d32f04a3f86aff4a286669c6c950ca95b5fd68c2602681", size = 295236, upload_time = "2025-03-23T22:55:43.822Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/50/b3/b51f09c2ba432a576fe63758bddc81f78f0c6309d9e5c10d194313bf021e/fastapi-0.115.12-py3-none-any.whl", hash = "sha256:e94613d6c05e27be7ffebdd6ea5f388112e5e430c8f7d6494a9d1d88d43e814d", size = 95164 }, + { url = "https://files.pythonhosted.org/packages/50/b3/b51f09c2ba432a576fe63758bddc81f78f0c6309d9e5c10d194313bf021e/fastapi-0.115.12-py3-none-any.whl", hash = "sha256:e94613d6c05e27be7ffebdd6ea5f388112e5e430c8f7d6494a9d1d88d43e814d", size = 95164, upload_time = "2025-03-23T22:55:42.101Z" }, ] [package.optional-dependencies] @@ -336,9 +336,9 @@ dependencies = [ { name = "typer" }, { name = "uvicorn", extra = ["standard"] }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fe/73/82a5831fbbf8ed75905bacf5b2d9d3dfd6f04d6968b29fe6f72a5ae9ceb1/fastapi_cli-0.0.7.tar.gz", hash = "sha256:02b3b65956f526412515907a0793c9094abd4bfb5457b389f645b0ea6ba3605e", size = 16753 } +sdist = { url = "https://files.pythonhosted.org/packages/fe/73/82a5831fbbf8ed75905bacf5b2d9d3dfd6f04d6968b29fe6f72a5ae9ceb1/fastapi_cli-0.0.7.tar.gz", hash = "sha256:02b3b65956f526412515907a0793c9094abd4bfb5457b389f645b0ea6ba3605e", size = 16753, upload_time = "2024-12-15T14:28:10.028Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a1/e6/5daefc851b514ce2287d8f5d358ae4341089185f78f3217a69d0ce3a390c/fastapi_cli-0.0.7-py3-none-any.whl", hash = "sha256:d549368ff584b2804336c61f192d86ddea080c11255f375959627911944804f4", size = 10705 }, + { url = "https://files.pythonhosted.org/packages/a1/e6/5daefc851b514ce2287d8f5d358ae4341089185f78f3217a69d0ce3a390c/fastapi_cli-0.0.7-py3-none-any.whl", hash = "sha256:d549368ff584b2804336c61f192d86ddea080c11255f375959627911944804f4", size = 10705, upload_time = "2024-12-15T14:28:06.18Z" }, ] [package.optional-dependencies] @@ -354,9 +354,9 @@ dependencies = [ { name = "pydantic" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/83/4d/6e695098ce33b3394cf26b335ca4ec6407f9421cfdf9ad6a38775580db21/fastapi_pagination-0.12.34.tar.gz", hash = "sha256:05ee8c0bc572072160f7f30900bfd87869e1880c87bc5797922fec2e49e65f11", size = 26901 } +sdist = { url = "https://files.pythonhosted.org/packages/83/4d/6e695098ce33b3394cf26b335ca4ec6407f9421cfdf9ad6a38775580db21/fastapi_pagination-0.12.34.tar.gz", hash = "sha256:05ee8c0bc572072160f7f30900bfd87869e1880c87bc5797922fec2e49e65f11", size = 26901, upload_time = "2024-12-21T15:38:25.842Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/72/f3/a0a1e7efd88dba07743665d1f6f88008c9a1e174458581ecaa53b8699a7c/fastapi_pagination-0.12.34-py3-none-any.whl", hash = "sha256:089d1078aae1784395b4dbd923d0c8246641ddcc291c5ec6d92a30edb92ecbdd", size = 43308 }, + { url = "https://files.pythonhosted.org/packages/72/f3/a0a1e7efd88dba07743665d1f6f88008c9a1e174458581ecaa53b8699a7c/fastapi_pagination-0.12.34-py3-none-any.whl", hash = "sha256:089d1078aae1784395b4dbd923d0c8246641ddcc291c5ec6d92a30edb92ecbdd", size = 43308, upload_time = "2024-12-21T15:38:23.493Z" }, ] [[package]] @@ -368,9 +368,9 @@ dependencies = [ { name = "pyasn1-modules" }, { name = "rsa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c6/eb/d504ba1daf190af6b204a9d4714d457462b486043744901a6eeea711f913/google_auth-2.38.0.tar.gz", hash = "sha256:8285113607d3b80a3f1543b75962447ba8a09fe85783432a784fdeef6ac094c4", size = 270866 } +sdist = { url = "https://files.pythonhosted.org/packages/c6/eb/d504ba1daf190af6b204a9d4714d457462b486043744901a6eeea711f913/google_auth-2.38.0.tar.gz", hash = "sha256:8285113607d3b80a3f1543b75962447ba8a09fe85783432a784fdeef6ac094c4", size = 270866, upload_time = "2025-01-23T01:05:29.119Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9d/47/603554949a37bca5b7f894d51896a9c534b9eab808e2520a748e081669d0/google_auth-2.38.0-py2.py3-none-any.whl", hash = "sha256:e7dae6694313f434a2727bf2906f27ad259bae090d7aa896590d86feec3d9d4a", size = 210770 }, + { url = "https://files.pythonhosted.org/packages/9d/47/603554949a37bca5b7f894d51896a9c534b9eab808e2520a748e081669d0/google_auth-2.38.0-py2.py3-none-any.whl", hash = "sha256:e7dae6694313f434a2727bf2906f27ad259bae090d7aa896590d86feec3d9d4a", size = 210770, upload_time = "2025-01-23T01:05:26.572Z" }, ] [[package]] @@ -386,79 +386,79 @@ dependencies = [ { name = "typing-extensions" }, { name = "websockets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0e/7a/224e2f70c835202042969685ee3da00a6475508d1b64f0f1e90144f96beb/google_genai-1.10.0.tar.gz", hash = "sha256:f59423e0f155dc66b7792c8a0e6724c75c72dc699d1eb7907d4d0006d4f6186f", size = 156355 } +sdist = { url = "https://files.pythonhosted.org/packages/0e/7a/224e2f70c835202042969685ee3da00a6475508d1b64f0f1e90144f96beb/google_genai-1.10.0.tar.gz", hash = "sha256:f59423e0f155dc66b7792c8a0e6724c75c72dc699d1eb7907d4d0006d4f6186f", size = 156355, upload_time = "2025-04-09T03:48:21.961Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ba/a0/56839a2e202d79c773edd1c1db124da8eb2a7b657267a888080b678d0369/google_genai-1.10.0-py3-none-any.whl", hash = "sha256:41b105a2fcf8a027fc45cc16694cd559b8cd1272eab7345ad58cfa2c353bf34f", size = 154705 }, + { url = "https://files.pythonhosted.org/packages/ba/a0/56839a2e202d79c773edd1c1db124da8eb2a7b657267a888080b678d0369/google_genai-1.10.0-py3-none-any.whl", hash = "sha256:41b105a2fcf8a027fc45cc16694cd559b8cd1272eab7345ad58cfa2c353bf34f", size = 154705, upload_time = "2025-04-09T03:48:20.49Z" }, ] [[package]] name = "greenlet" version = "3.1.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2f/ff/df5fede753cc10f6a5be0931204ea30c35fa2f2ea7a35b25bdaf4fe40e46/greenlet-3.1.1.tar.gz", hash = "sha256:4ce3ac6cdb6adf7946475d7ef31777c26d94bccc377e070a7986bd2d5c515467", size = 186022 } +sdist = { url = "https://files.pythonhosted.org/packages/2f/ff/df5fede753cc10f6a5be0931204ea30c35fa2f2ea7a35b25bdaf4fe40e46/greenlet-3.1.1.tar.gz", hash = "sha256:4ce3ac6cdb6adf7946475d7ef31777c26d94bccc377e070a7986bd2d5c515467", size = 186022, upload_time = "2024-09-20T18:21:04.506Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/25/90/5234a78dc0ef6496a6eb97b67a42a8e96742a56f7dc808cb954a85390448/greenlet-3.1.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:0bbae94a29c9e5c7e4a2b7f0aae5c17e8e90acbfd3bf6270eeba60c39fce3563", size = 271235 }, - { url = "https://files.pythonhosted.org/packages/7c/16/cd631fa0ab7d06ef06387135b7549fdcc77d8d859ed770a0d28e47b20972/greenlet-3.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fde093fb93f35ca72a556cf72c92ea3ebfda3d79fc35bb19fbe685853869a83", size = 637168 }, - { url = "https://files.pythonhosted.org/packages/2f/b1/aed39043a6fec33c284a2c9abd63ce191f4f1a07319340ffc04d2ed3256f/greenlet-3.1.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:36b89d13c49216cadb828db8dfa6ce86bbbc476a82d3a6c397f0efae0525bdd0", size = 648826 }, - { url = "https://files.pythonhosted.org/packages/76/25/40e0112f7f3ebe54e8e8ed91b2b9f970805143efef16d043dfc15e70f44b/greenlet-3.1.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94b6150a85e1b33b40b1464a3f9988dcc5251d6ed06842abff82e42632fac120", size = 644443 }, - { url = "https://files.pythonhosted.org/packages/fb/2f/3850b867a9af519794784a7eeed1dd5bc68ffbcc5b28cef703711025fd0a/greenlet-3.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93147c513fac16385d1036b7e5b102c7fbbdb163d556b791f0f11eada7ba65dc", size = 643295 }, - { url = "https://files.pythonhosted.org/packages/cf/69/79e4d63b9387b48939096e25115b8af7cd8a90397a304f92436bcb21f5b2/greenlet-3.1.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:da7a9bff22ce038e19bf62c4dd1ec8391062878710ded0a845bcf47cc0200617", size = 599544 }, - { url = "https://files.pythonhosted.org/packages/46/1d/44dbcb0e6c323bd6f71b8c2f4233766a5faf4b8948873225d34a0b7efa71/greenlet-3.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b2795058c23988728eec1f36a4e5e4ebad22f8320c85f3587b539b9ac84128d7", size = 1125456 }, - { url = "https://files.pythonhosted.org/packages/e0/1d/a305dce121838d0278cee39d5bb268c657f10a5363ae4b726848f833f1bb/greenlet-3.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ed10eac5830befbdd0c32f83e8aa6288361597550ba669b04c48f0f9a2c843c6", size = 1149111 }, - { url = "https://files.pythonhosted.org/packages/96/28/d62835fb33fb5652f2e98d34c44ad1a0feacc8b1d3f1aecab035f51f267d/greenlet-3.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:77c386de38a60d1dfb8e55b8c1101d68c79dfdd25c7095d51fec2dd800892b80", size = 298392 }, - { url = "https://files.pythonhosted.org/packages/28/62/1c2665558618553c42922ed47a4e6d6527e2fa3516a8256c2f431c5d0441/greenlet-3.1.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:e4d333e558953648ca09d64f13e6d8f0523fa705f51cae3f03b5983489958c70", size = 272479 }, - { url = "https://files.pythonhosted.org/packages/76/9d/421e2d5f07285b6e4e3a676b016ca781f63cfe4a0cd8eaecf3fd6f7a71ae/greenlet-3.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09fc016b73c94e98e29af67ab7b9a879c307c6731a2c9da0db5a7d9b7edd1159", size = 640404 }, - { url = "https://files.pythonhosted.org/packages/e5/de/6e05f5c59262a584e502dd3d261bbdd2c97ab5416cc9c0b91ea38932a901/greenlet-3.1.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d5e975ca70269d66d17dd995dafc06f1b06e8cb1ec1e9ed54c1d1e4a7c4cf26e", size = 652813 }, - { url = "https://files.pythonhosted.org/packages/49/93/d5f93c84241acdea15a8fd329362c2c71c79e1a507c3f142a5d67ea435ae/greenlet-3.1.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b2813dc3de8c1ee3f924e4d4227999285fd335d1bcc0d2be6dc3f1f6a318ec1", size = 648517 }, - { url = "https://files.pythonhosted.org/packages/15/85/72f77fc02d00470c86a5c982b8daafdf65d38aefbbe441cebff3bf7037fc/greenlet-3.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e347b3bfcf985a05e8c0b7d462ba6f15b1ee1c909e2dcad795e49e91b152c383", size = 647831 }, - { url = "https://files.pythonhosted.org/packages/f7/4b/1c9695aa24f808e156c8f4813f685d975ca73c000c2a5056c514c64980f6/greenlet-3.1.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e8f8c9cb53cdac7ba9793c276acd90168f416b9ce36799b9b885790f8ad6c0a", size = 602413 }, - { url = "https://files.pythonhosted.org/packages/76/70/ad6e5b31ef330f03b12559d19fda2606a522d3849cde46b24f223d6d1619/greenlet-3.1.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:62ee94988d6b4722ce0028644418d93a52429e977d742ca2ccbe1c4f4a792511", size = 1129619 }, - { url = "https://files.pythonhosted.org/packages/f4/fb/201e1b932e584066e0f0658b538e73c459b34d44b4bd4034f682423bc801/greenlet-3.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1776fd7f989fc6b8d8c8cb8da1f6b82c5814957264d1f6cf818d475ec2bf6395", size = 1155198 }, - { url = "https://files.pythonhosted.org/packages/12/da/b9ed5e310bb8b89661b80cbcd4db5a067903bbcd7fc854923f5ebb4144f0/greenlet-3.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:48ca08c771c268a768087b408658e216133aecd835c0ded47ce955381105ba39", size = 298930 }, - { url = "https://files.pythonhosted.org/packages/7d/ec/bad1ac26764d26aa1353216fcbfa4670050f66d445448aafa227f8b16e80/greenlet-3.1.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:4afe7ea89de619adc868e087b4d2359282058479d7cfb94970adf4b55284574d", size = 274260 }, - { url = "https://files.pythonhosted.org/packages/66/d4/c8c04958870f482459ab5956c2942c4ec35cac7fe245527f1039837c17a9/greenlet-3.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f406b22b7c9a9b4f8aa9d2ab13d6ae0ac3e85c9a809bd590ad53fed2bf70dc79", size = 649064 }, - { url = "https://files.pythonhosted.org/packages/51/41/467b12a8c7c1303d20abcca145db2be4e6cd50a951fa30af48b6ec607581/greenlet-3.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c3a701fe5a9695b238503ce5bbe8218e03c3bcccf7e204e455e7462d770268aa", size = 663420 }, - { url = "https://files.pythonhosted.org/packages/27/8f/2a93cd9b1e7107d5c7b3b7816eeadcac2ebcaf6d6513df9abaf0334777f6/greenlet-3.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2846930c65b47d70b9d178e89c7e1a69c95c1f68ea5aa0a58646b7a96df12441", size = 658035 }, - { url = "https://files.pythonhosted.org/packages/57/5c/7c6f50cb12be092e1dccb2599be5a942c3416dbcfb76efcf54b3f8be4d8d/greenlet-3.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99cfaa2110534e2cf3ba31a7abcac9d328d1d9f1b95beede58294a60348fba36", size = 660105 }, - { url = "https://files.pythonhosted.org/packages/f1/66/033e58a50fd9ec9df00a8671c74f1f3a320564c6415a4ed82a1c651654ba/greenlet-3.1.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1443279c19fca463fc33e65ef2a935a5b09bb90f978beab37729e1c3c6c25fe9", size = 613077 }, - { url = "https://files.pythonhosted.org/packages/19/c5/36384a06f748044d06bdd8776e231fadf92fc896bd12cb1c9f5a1bda9578/greenlet-3.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b7cede291382a78f7bb5f04a529cb18e068dd29e0fb27376074b6d0317bf4dd0", size = 1135975 }, - { url = "https://files.pythonhosted.org/packages/38/f9/c0a0eb61bdf808d23266ecf1d63309f0e1471f284300ce6dac0ae1231881/greenlet-3.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:23f20bb60ae298d7d8656c6ec6db134bca379ecefadb0b19ce6f19d1f232a942", size = 1163955 }, - { url = "https://files.pythonhosted.org/packages/43/21/a5d9df1d21514883333fc86584c07c2b49ba7c602e670b174bd73cfc9c7f/greenlet-3.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:7124e16b4c55d417577c2077be379514321916d5790fa287c9ed6f23bd2ffd01", size = 299655 }, - { url = "https://files.pythonhosted.org/packages/f3/57/0db4940cd7bb461365ca8d6fd53e68254c9dbbcc2b452e69d0d41f10a85e/greenlet-3.1.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:05175c27cb459dcfc05d026c4232f9de8913ed006d42713cb8a5137bd49375f1", size = 272990 }, - { url = "https://files.pythonhosted.org/packages/1c/ec/423d113c9f74e5e402e175b157203e9102feeb7088cee844d735b28ef963/greenlet-3.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:935e943ec47c4afab8965954bf49bfa639c05d4ccf9ef6e924188f762145c0ff", size = 649175 }, - { url = "https://files.pythonhosted.org/packages/a9/46/ddbd2db9ff209186b7b7c621d1432e2f21714adc988703dbdd0e65155c77/greenlet-3.1.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:667a9706c970cb552ede35aee17339a18e8f2a87a51fba2ed39ceeeb1004798a", size = 663425 }, - { url = "https://files.pythonhosted.org/packages/bc/f9/9c82d6b2b04aa37e38e74f0c429aece5eeb02bab6e3b98e7db89b23d94c6/greenlet-3.1.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8a678974d1f3aa55f6cc34dc480169d58f2e6d8958895d68845fa4ab566509e", size = 657736 }, - { url = "https://files.pythonhosted.org/packages/d9/42/b87bc2a81e3a62c3de2b0d550bf91a86939442b7ff85abb94eec3fc0e6aa/greenlet-3.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efc0f674aa41b92da8c49e0346318c6075d734994c3c4e4430b1c3f853e498e4", size = 660347 }, - { url = "https://files.pythonhosted.org/packages/37/fa/71599c3fd06336cdc3eac52e6871cfebab4d9d70674a9a9e7a482c318e99/greenlet-3.1.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0153404a4bb921f0ff1abeb5ce8a5131da56b953eda6e14b88dc6bbc04d2049e", size = 615583 }, - { url = "https://files.pythonhosted.org/packages/4e/96/e9ef85de031703ee7a4483489b40cf307f93c1824a02e903106f2ea315fe/greenlet-3.1.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:275f72decf9932639c1c6dd1013a1bc266438eb32710016a1c742df5da6e60a1", size = 1133039 }, - { url = "https://files.pythonhosted.org/packages/87/76/b2b6362accd69f2d1889db61a18c94bc743e961e3cab344c2effaa4b4a25/greenlet-3.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c4aab7f6381f38a4b42f269057aee279ab0fc7bf2e929e3d4abfae97b682a12c", size = 1160716 }, - { url = "https://files.pythonhosted.org/packages/1f/1b/54336d876186920e185066d8c3024ad55f21d7cc3683c856127ddb7b13ce/greenlet-3.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:b42703b1cf69f2aa1df7d1030b9d77d3e584a70755674d60e710f0af570f3761", size = 299490 }, - { url = "https://files.pythonhosted.org/packages/5f/17/bea55bf36990e1638a2af5ba10c1640273ef20f627962cf97107f1e5d637/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1695e76146579f8c06c1509c7ce4dfe0706f49c6831a817ac04eebb2fd02011", size = 643731 }, - { url = "https://files.pythonhosted.org/packages/78/d2/aa3d2157f9ab742a08e0fd8f77d4699f37c22adfbfeb0c610a186b5f75e0/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7876452af029456b3f3549b696bb36a06db7c90747740c5302f74a9e9fa14b13", size = 649304 }, - { url = "https://files.pythonhosted.org/packages/f1/8e/d0aeffe69e53ccff5a28fa86f07ad1d2d2d6537a9506229431a2a02e2f15/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ead44c85f8ab905852d3de8d86f6f8baf77109f9da589cb4fa142bd3b57b475", size = 646537 }, - { url = "https://files.pythonhosted.org/packages/05/79/e15408220bbb989469c8871062c97c6c9136770657ba779711b90870d867/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8320f64b777d00dd7ccdade271eaf0cad6636343293a25074cc5566160e4de7b", size = 642506 }, - { url = "https://files.pythonhosted.org/packages/18/87/470e01a940307796f1d25f8167b551a968540fbe0551c0ebb853cb527dd6/greenlet-3.1.1-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6510bf84a6b643dabba74d3049ead221257603a253d0a9873f55f6a59a65f822", size = 602753 }, - { url = "https://files.pythonhosted.org/packages/e2/72/576815ba674eddc3c25028238f74d7b8068902b3968cbe456771b166455e/greenlet-3.1.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:04b013dc07c96f83134b1e99888e7a79979f1a247e2a9f59697fa14b5862ed01", size = 1122731 }, - { url = "https://files.pythonhosted.org/packages/ac/38/08cc303ddddc4b3d7c628c3039a61a3aae36c241ed01393d00c2fd663473/greenlet-3.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:411f015496fec93c1c8cd4e5238da364e1da7a124bcb293f085bf2860c32c6f6", size = 1142112 }, - { url = "https://files.pythonhosted.org/packages/8c/82/8051e82af6d6b5150aacb6789a657a8afd48f0a44d8e91cb72aaaf28553a/greenlet-3.1.1-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:396979749bd95f018296af156201d6211240e7a23090f50a8d5d18c370084dc3", size = 270027 }, - { url = "https://files.pythonhosted.org/packages/f9/74/f66de2785880293780eebd18a2958aeea7cbe7814af1ccef634f4701f846/greenlet-3.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca9d0ff5ad43e785350894d97e13633a66e2b50000e8a183a50a88d834752d42", size = 634822 }, - { url = "https://files.pythonhosted.org/packages/68/23/acd9ca6bc412b02b8aa755e47b16aafbe642dde0ad2f929f836e57a7949c/greenlet-3.1.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f6ff3b14f2df4c41660a7dec01045a045653998784bf8cfcb5a525bdffffbc8f", size = 646866 }, - { url = "https://files.pythonhosted.org/packages/a9/ab/562beaf8a53dc9f6b2459f200e7bc226bb07e51862a66351d8b7817e3efd/greenlet-3.1.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94ebba31df2aa506d7b14866fed00ac141a867e63143fe5bca82a8e503b36437", size = 641985 }, - { url = "https://files.pythonhosted.org/packages/03/d3/1006543621f16689f6dc75f6bcf06e3c23e044c26fe391c16c253623313e/greenlet-3.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73aaad12ac0ff500f62cebed98d8789198ea0e6f233421059fa68a5aa7220145", size = 641268 }, - { url = "https://files.pythonhosted.org/packages/2f/c1/ad71ce1b5f61f900593377b3f77b39408bce5dc96754790311b49869e146/greenlet-3.1.1-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63e4844797b975b9af3a3fb8f7866ff08775f5426925e1e0bbcfe7932059a12c", size = 597376 }, - { url = "https://files.pythonhosted.org/packages/f7/ff/183226685b478544d61d74804445589e069d00deb8ddef042699733950c7/greenlet-3.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7939aa3ca7d2a1593596e7ac6d59391ff30281ef280d8632fa03d81f7c5f955e", size = 1123359 }, - { url = "https://files.pythonhosted.org/packages/c0/8b/9b3b85a89c22f55f315908b94cd75ab5fed5973f7393bbef000ca8b2c5c1/greenlet-3.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d0028e725ee18175c6e422797c407874da24381ce0690d6b9396c204c7f7276e", size = 1147458 }, - { url = "https://files.pythonhosted.org/packages/b8/1c/248fadcecd1790b0ba793ff81fa2375c9ad6442f4c748bf2cc2e6563346a/greenlet-3.1.1-cp39-cp39-win32.whl", hash = "sha256:5e06afd14cbaf9e00899fae69b24a32f2196c19de08fcb9f4779dd4f004e5e7c", size = 281131 }, - { url = "https://files.pythonhosted.org/packages/ae/02/e7d0aef2354a38709b764df50b2b83608f0621493e47f47694eb80922822/greenlet-3.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:3319aa75e0e0639bc15ff54ca327e8dc7a6fe404003496e3c6925cd3142e0e22", size = 298306 }, + { url = "https://files.pythonhosted.org/packages/25/90/5234a78dc0ef6496a6eb97b67a42a8e96742a56f7dc808cb954a85390448/greenlet-3.1.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:0bbae94a29c9e5c7e4a2b7f0aae5c17e8e90acbfd3bf6270eeba60c39fce3563", size = 271235, upload_time = "2024-09-20T17:07:18.761Z" }, + { url = "https://files.pythonhosted.org/packages/7c/16/cd631fa0ab7d06ef06387135b7549fdcc77d8d859ed770a0d28e47b20972/greenlet-3.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fde093fb93f35ca72a556cf72c92ea3ebfda3d79fc35bb19fbe685853869a83", size = 637168, upload_time = "2024-09-20T17:36:43.774Z" }, + { url = "https://files.pythonhosted.org/packages/2f/b1/aed39043a6fec33c284a2c9abd63ce191f4f1a07319340ffc04d2ed3256f/greenlet-3.1.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:36b89d13c49216cadb828db8dfa6ce86bbbc476a82d3a6c397f0efae0525bdd0", size = 648826, upload_time = "2024-09-20T17:39:16.921Z" }, + { url = "https://files.pythonhosted.org/packages/76/25/40e0112f7f3ebe54e8e8ed91b2b9f970805143efef16d043dfc15e70f44b/greenlet-3.1.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94b6150a85e1b33b40b1464a3f9988dcc5251d6ed06842abff82e42632fac120", size = 644443, upload_time = "2024-09-20T17:44:21.896Z" }, + { url = "https://files.pythonhosted.org/packages/fb/2f/3850b867a9af519794784a7eeed1dd5bc68ffbcc5b28cef703711025fd0a/greenlet-3.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93147c513fac16385d1036b7e5b102c7fbbdb163d556b791f0f11eada7ba65dc", size = 643295, upload_time = "2024-09-20T17:08:37.951Z" }, + { url = "https://files.pythonhosted.org/packages/cf/69/79e4d63b9387b48939096e25115b8af7cd8a90397a304f92436bcb21f5b2/greenlet-3.1.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:da7a9bff22ce038e19bf62c4dd1ec8391062878710ded0a845bcf47cc0200617", size = 599544, upload_time = "2024-09-20T17:08:27.894Z" }, + { url = "https://files.pythonhosted.org/packages/46/1d/44dbcb0e6c323bd6f71b8c2f4233766a5faf4b8948873225d34a0b7efa71/greenlet-3.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b2795058c23988728eec1f36a4e5e4ebad22f8320c85f3587b539b9ac84128d7", size = 1125456, upload_time = "2024-09-20T17:44:11.755Z" }, + { url = "https://files.pythonhosted.org/packages/e0/1d/a305dce121838d0278cee39d5bb268c657f10a5363ae4b726848f833f1bb/greenlet-3.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ed10eac5830befbdd0c32f83e8aa6288361597550ba669b04c48f0f9a2c843c6", size = 1149111, upload_time = "2024-09-20T17:09:22.104Z" }, + { url = "https://files.pythonhosted.org/packages/96/28/d62835fb33fb5652f2e98d34c44ad1a0feacc8b1d3f1aecab035f51f267d/greenlet-3.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:77c386de38a60d1dfb8e55b8c1101d68c79dfdd25c7095d51fec2dd800892b80", size = 298392, upload_time = "2024-09-20T17:28:51.988Z" }, + { url = "https://files.pythonhosted.org/packages/28/62/1c2665558618553c42922ed47a4e6d6527e2fa3516a8256c2f431c5d0441/greenlet-3.1.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:e4d333e558953648ca09d64f13e6d8f0523fa705f51cae3f03b5983489958c70", size = 272479, upload_time = "2024-09-20T17:07:22.332Z" }, + { url = "https://files.pythonhosted.org/packages/76/9d/421e2d5f07285b6e4e3a676b016ca781f63cfe4a0cd8eaecf3fd6f7a71ae/greenlet-3.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09fc016b73c94e98e29af67ab7b9a879c307c6731a2c9da0db5a7d9b7edd1159", size = 640404, upload_time = "2024-09-20T17:36:45.588Z" }, + { url = "https://files.pythonhosted.org/packages/e5/de/6e05f5c59262a584e502dd3d261bbdd2c97ab5416cc9c0b91ea38932a901/greenlet-3.1.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d5e975ca70269d66d17dd995dafc06f1b06e8cb1ec1e9ed54c1d1e4a7c4cf26e", size = 652813, upload_time = "2024-09-20T17:39:19.052Z" }, + { url = "https://files.pythonhosted.org/packages/49/93/d5f93c84241acdea15a8fd329362c2c71c79e1a507c3f142a5d67ea435ae/greenlet-3.1.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b2813dc3de8c1ee3f924e4d4227999285fd335d1bcc0d2be6dc3f1f6a318ec1", size = 648517, upload_time = "2024-09-20T17:44:24.101Z" }, + { url = "https://files.pythonhosted.org/packages/15/85/72f77fc02d00470c86a5c982b8daafdf65d38aefbbe441cebff3bf7037fc/greenlet-3.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e347b3bfcf985a05e8c0b7d462ba6f15b1ee1c909e2dcad795e49e91b152c383", size = 647831, upload_time = "2024-09-20T17:08:40.577Z" }, + { url = "https://files.pythonhosted.org/packages/f7/4b/1c9695aa24f808e156c8f4813f685d975ca73c000c2a5056c514c64980f6/greenlet-3.1.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e8f8c9cb53cdac7ba9793c276acd90168f416b9ce36799b9b885790f8ad6c0a", size = 602413, upload_time = "2024-09-20T17:08:31.728Z" }, + { url = "https://files.pythonhosted.org/packages/76/70/ad6e5b31ef330f03b12559d19fda2606a522d3849cde46b24f223d6d1619/greenlet-3.1.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:62ee94988d6b4722ce0028644418d93a52429e977d742ca2ccbe1c4f4a792511", size = 1129619, upload_time = "2024-09-20T17:44:14.222Z" }, + { url = "https://files.pythonhosted.org/packages/f4/fb/201e1b932e584066e0f0658b538e73c459b34d44b4bd4034f682423bc801/greenlet-3.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1776fd7f989fc6b8d8c8cb8da1f6b82c5814957264d1f6cf818d475ec2bf6395", size = 1155198, upload_time = "2024-09-20T17:09:23.903Z" }, + { url = "https://files.pythonhosted.org/packages/12/da/b9ed5e310bb8b89661b80cbcd4db5a067903bbcd7fc854923f5ebb4144f0/greenlet-3.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:48ca08c771c268a768087b408658e216133aecd835c0ded47ce955381105ba39", size = 298930, upload_time = "2024-09-20T17:25:18.656Z" }, + { url = "https://files.pythonhosted.org/packages/7d/ec/bad1ac26764d26aa1353216fcbfa4670050f66d445448aafa227f8b16e80/greenlet-3.1.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:4afe7ea89de619adc868e087b4d2359282058479d7cfb94970adf4b55284574d", size = 274260, upload_time = "2024-09-20T17:08:07.301Z" }, + { url = "https://files.pythonhosted.org/packages/66/d4/c8c04958870f482459ab5956c2942c4ec35cac7fe245527f1039837c17a9/greenlet-3.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f406b22b7c9a9b4f8aa9d2ab13d6ae0ac3e85c9a809bd590ad53fed2bf70dc79", size = 649064, upload_time = "2024-09-20T17:36:47.628Z" }, + { url = "https://files.pythonhosted.org/packages/51/41/467b12a8c7c1303d20abcca145db2be4e6cd50a951fa30af48b6ec607581/greenlet-3.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c3a701fe5a9695b238503ce5bbe8218e03c3bcccf7e204e455e7462d770268aa", size = 663420, upload_time = "2024-09-20T17:39:21.258Z" }, + { url = "https://files.pythonhosted.org/packages/27/8f/2a93cd9b1e7107d5c7b3b7816eeadcac2ebcaf6d6513df9abaf0334777f6/greenlet-3.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2846930c65b47d70b9d178e89c7e1a69c95c1f68ea5aa0a58646b7a96df12441", size = 658035, upload_time = "2024-09-20T17:44:26.501Z" }, + { url = "https://files.pythonhosted.org/packages/57/5c/7c6f50cb12be092e1dccb2599be5a942c3416dbcfb76efcf54b3f8be4d8d/greenlet-3.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99cfaa2110534e2cf3ba31a7abcac9d328d1d9f1b95beede58294a60348fba36", size = 660105, upload_time = "2024-09-20T17:08:42.048Z" }, + { url = "https://files.pythonhosted.org/packages/f1/66/033e58a50fd9ec9df00a8671c74f1f3a320564c6415a4ed82a1c651654ba/greenlet-3.1.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1443279c19fca463fc33e65ef2a935a5b09bb90f978beab37729e1c3c6c25fe9", size = 613077, upload_time = "2024-09-20T17:08:33.707Z" }, + { url = "https://files.pythonhosted.org/packages/19/c5/36384a06f748044d06bdd8776e231fadf92fc896bd12cb1c9f5a1bda9578/greenlet-3.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b7cede291382a78f7bb5f04a529cb18e068dd29e0fb27376074b6d0317bf4dd0", size = 1135975, upload_time = "2024-09-20T17:44:15.989Z" }, + { url = "https://files.pythonhosted.org/packages/38/f9/c0a0eb61bdf808d23266ecf1d63309f0e1471f284300ce6dac0ae1231881/greenlet-3.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:23f20bb60ae298d7d8656c6ec6db134bca379ecefadb0b19ce6f19d1f232a942", size = 1163955, upload_time = "2024-09-20T17:09:25.539Z" }, + { url = "https://files.pythonhosted.org/packages/43/21/a5d9df1d21514883333fc86584c07c2b49ba7c602e670b174bd73cfc9c7f/greenlet-3.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:7124e16b4c55d417577c2077be379514321916d5790fa287c9ed6f23bd2ffd01", size = 299655, upload_time = "2024-09-20T17:21:22.427Z" }, + { url = "https://files.pythonhosted.org/packages/f3/57/0db4940cd7bb461365ca8d6fd53e68254c9dbbcc2b452e69d0d41f10a85e/greenlet-3.1.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:05175c27cb459dcfc05d026c4232f9de8913ed006d42713cb8a5137bd49375f1", size = 272990, upload_time = "2024-09-20T17:08:26.312Z" }, + { url = "https://files.pythonhosted.org/packages/1c/ec/423d113c9f74e5e402e175b157203e9102feeb7088cee844d735b28ef963/greenlet-3.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:935e943ec47c4afab8965954bf49bfa639c05d4ccf9ef6e924188f762145c0ff", size = 649175, upload_time = "2024-09-20T17:36:48.983Z" }, + { url = "https://files.pythonhosted.org/packages/a9/46/ddbd2db9ff209186b7b7c621d1432e2f21714adc988703dbdd0e65155c77/greenlet-3.1.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:667a9706c970cb552ede35aee17339a18e8f2a87a51fba2ed39ceeeb1004798a", size = 663425, upload_time = "2024-09-20T17:39:22.705Z" }, + { url = "https://files.pythonhosted.org/packages/bc/f9/9c82d6b2b04aa37e38e74f0c429aece5eeb02bab6e3b98e7db89b23d94c6/greenlet-3.1.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8a678974d1f3aa55f6cc34dc480169d58f2e6d8958895d68845fa4ab566509e", size = 657736, upload_time = "2024-09-20T17:44:28.544Z" }, + { url = "https://files.pythonhosted.org/packages/d9/42/b87bc2a81e3a62c3de2b0d550bf91a86939442b7ff85abb94eec3fc0e6aa/greenlet-3.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efc0f674aa41b92da8c49e0346318c6075d734994c3c4e4430b1c3f853e498e4", size = 660347, upload_time = "2024-09-20T17:08:45.56Z" }, + { url = "https://files.pythonhosted.org/packages/37/fa/71599c3fd06336cdc3eac52e6871cfebab4d9d70674a9a9e7a482c318e99/greenlet-3.1.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0153404a4bb921f0ff1abeb5ce8a5131da56b953eda6e14b88dc6bbc04d2049e", size = 615583, upload_time = "2024-09-20T17:08:36.85Z" }, + { url = "https://files.pythonhosted.org/packages/4e/96/e9ef85de031703ee7a4483489b40cf307f93c1824a02e903106f2ea315fe/greenlet-3.1.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:275f72decf9932639c1c6dd1013a1bc266438eb32710016a1c742df5da6e60a1", size = 1133039, upload_time = "2024-09-20T17:44:18.287Z" }, + { url = "https://files.pythonhosted.org/packages/87/76/b2b6362accd69f2d1889db61a18c94bc743e961e3cab344c2effaa4b4a25/greenlet-3.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c4aab7f6381f38a4b42f269057aee279ab0fc7bf2e929e3d4abfae97b682a12c", size = 1160716, upload_time = "2024-09-20T17:09:27.112Z" }, + { url = "https://files.pythonhosted.org/packages/1f/1b/54336d876186920e185066d8c3024ad55f21d7cc3683c856127ddb7b13ce/greenlet-3.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:b42703b1cf69f2aa1df7d1030b9d77d3e584a70755674d60e710f0af570f3761", size = 299490, upload_time = "2024-09-20T17:17:09.501Z" }, + { url = "https://files.pythonhosted.org/packages/5f/17/bea55bf36990e1638a2af5ba10c1640273ef20f627962cf97107f1e5d637/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1695e76146579f8c06c1509c7ce4dfe0706f49c6831a817ac04eebb2fd02011", size = 643731, upload_time = "2024-09-20T17:36:50.376Z" }, + { url = "https://files.pythonhosted.org/packages/78/d2/aa3d2157f9ab742a08e0fd8f77d4699f37c22adfbfeb0c610a186b5f75e0/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7876452af029456b3f3549b696bb36a06db7c90747740c5302f74a9e9fa14b13", size = 649304, upload_time = "2024-09-20T17:39:24.55Z" }, + { url = "https://files.pythonhosted.org/packages/f1/8e/d0aeffe69e53ccff5a28fa86f07ad1d2d2d6537a9506229431a2a02e2f15/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ead44c85f8ab905852d3de8d86f6f8baf77109f9da589cb4fa142bd3b57b475", size = 646537, upload_time = "2024-09-20T17:44:31.102Z" }, + { url = "https://files.pythonhosted.org/packages/05/79/e15408220bbb989469c8871062c97c6c9136770657ba779711b90870d867/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8320f64b777d00dd7ccdade271eaf0cad6636343293a25074cc5566160e4de7b", size = 642506, upload_time = "2024-09-20T17:08:47.852Z" }, + { url = "https://files.pythonhosted.org/packages/18/87/470e01a940307796f1d25f8167b551a968540fbe0551c0ebb853cb527dd6/greenlet-3.1.1-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6510bf84a6b643dabba74d3049ead221257603a253d0a9873f55f6a59a65f822", size = 602753, upload_time = "2024-09-20T17:08:38.079Z" }, + { url = "https://files.pythonhosted.org/packages/e2/72/576815ba674eddc3c25028238f74d7b8068902b3968cbe456771b166455e/greenlet-3.1.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:04b013dc07c96f83134b1e99888e7a79979f1a247e2a9f59697fa14b5862ed01", size = 1122731, upload_time = "2024-09-20T17:44:20.556Z" }, + { url = "https://files.pythonhosted.org/packages/ac/38/08cc303ddddc4b3d7c628c3039a61a3aae36c241ed01393d00c2fd663473/greenlet-3.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:411f015496fec93c1c8cd4e5238da364e1da7a124bcb293f085bf2860c32c6f6", size = 1142112, upload_time = "2024-09-20T17:09:28.753Z" }, + { url = "https://files.pythonhosted.org/packages/8c/82/8051e82af6d6b5150aacb6789a657a8afd48f0a44d8e91cb72aaaf28553a/greenlet-3.1.1-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:396979749bd95f018296af156201d6211240e7a23090f50a8d5d18c370084dc3", size = 270027, upload_time = "2024-09-20T17:08:27.964Z" }, + { url = "https://files.pythonhosted.org/packages/f9/74/f66de2785880293780eebd18a2958aeea7cbe7814af1ccef634f4701f846/greenlet-3.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca9d0ff5ad43e785350894d97e13633a66e2b50000e8a183a50a88d834752d42", size = 634822, upload_time = "2024-09-20T17:36:54.764Z" }, + { url = "https://files.pythonhosted.org/packages/68/23/acd9ca6bc412b02b8aa755e47b16aafbe642dde0ad2f929f836e57a7949c/greenlet-3.1.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f6ff3b14f2df4c41660a7dec01045a045653998784bf8cfcb5a525bdffffbc8f", size = 646866, upload_time = "2024-09-20T17:39:30.2Z" }, + { url = "https://files.pythonhosted.org/packages/a9/ab/562beaf8a53dc9f6b2459f200e7bc226bb07e51862a66351d8b7817e3efd/greenlet-3.1.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94ebba31df2aa506d7b14866fed00ac141a867e63143fe5bca82a8e503b36437", size = 641985, upload_time = "2024-09-20T17:44:36.168Z" }, + { url = "https://files.pythonhosted.org/packages/03/d3/1006543621f16689f6dc75f6bcf06e3c23e044c26fe391c16c253623313e/greenlet-3.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73aaad12ac0ff500f62cebed98d8789198ea0e6f233421059fa68a5aa7220145", size = 641268, upload_time = "2024-09-20T17:08:52.469Z" }, + { url = "https://files.pythonhosted.org/packages/2f/c1/ad71ce1b5f61f900593377b3f77b39408bce5dc96754790311b49869e146/greenlet-3.1.1-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63e4844797b975b9af3a3fb8f7866ff08775f5426925e1e0bbcfe7932059a12c", size = 597376, upload_time = "2024-09-20T17:08:46.096Z" }, + { url = "https://files.pythonhosted.org/packages/f7/ff/183226685b478544d61d74804445589e069d00deb8ddef042699733950c7/greenlet-3.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7939aa3ca7d2a1593596e7ac6d59391ff30281ef280d8632fa03d81f7c5f955e", size = 1123359, upload_time = "2024-09-20T17:44:27.559Z" }, + { url = "https://files.pythonhosted.org/packages/c0/8b/9b3b85a89c22f55f315908b94cd75ab5fed5973f7393bbef000ca8b2c5c1/greenlet-3.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d0028e725ee18175c6e422797c407874da24381ce0690d6b9396c204c7f7276e", size = 1147458, upload_time = "2024-09-20T17:09:33.708Z" }, + { url = "https://files.pythonhosted.org/packages/b8/1c/248fadcecd1790b0ba793ff81fa2375c9ad6442f4c748bf2cc2e6563346a/greenlet-3.1.1-cp39-cp39-win32.whl", hash = "sha256:5e06afd14cbaf9e00899fae69b24a32f2196c19de08fcb9f4779dd4f004e5e7c", size = 281131, upload_time = "2024-09-20T17:44:53.141Z" }, + { url = "https://files.pythonhosted.org/packages/ae/02/e7d0aef2354a38709b764df50b2b83608f0621493e47f47694eb80922822/greenlet-3.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:3319aa75e0e0639bc15ff54ca327e8dc7a6fe404003496e3c6925cd3142e0e22", size = 298306, upload_time = "2024-09-20T17:33:23.059Z" }, ] [[package]] name = "h11" version = "0.14.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", size = 100418 } +sdist = { url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", size = 100418, upload_time = "2022-09-25T15:40:01.519Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259 }, + { url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259, upload_time = "2022-09-25T15:39:59.68Z" }, ] [[package]] @@ -483,6 +483,7 @@ dependencies = [ { name = "rich" }, { name = "sentry-sdk", extra = ["anthropic", "fastapi", "sqlalchemy"] }, { name = "sqlalchemy" }, + { name = "tiktoken" }, ] [package.dev-dependencies] @@ -515,6 +516,7 @@ requires-dist = [ { name = "rich", specifier = ">=13.7.1" }, { name = "sentry-sdk", extras = ["anthropic", "fastapi", "sqlalchemy"], specifier = ">=2.3.1" }, { name = "sqlalchemy", specifier = ">=2.0.30" }, + { name = "tiktoken", specifier = ">=0.9.0" }, ] [package.metadata.requires-dev] @@ -536,52 +538,52 @@ dependencies = [ { name = "certifi" }, { name = "h11" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6a/41/d7d0a89eb493922c37d343b607bc1b5da7f5be7e383740b4753ad8943e90/httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c", size = 85196 } +sdist = { url = "https://files.pythonhosted.org/packages/6a/41/d7d0a89eb493922c37d343b607bc1b5da7f5be7e383740b4753ad8943e90/httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c", size = 85196, upload_time = "2024-11-15T12:30:47.531Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/87/f5/72347bc88306acb359581ac4d52f23c0ef445b57157adedb9aee0cd689d2/httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd", size = 78551 }, + { url = "https://files.pythonhosted.org/packages/87/f5/72347bc88306acb359581ac4d52f23c0ef445b57157adedb9aee0cd689d2/httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd", size = 78551, upload_time = "2024-11-15T12:30:45.782Z" }, ] [[package]] name = "httptools" version = "0.6.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a7/9a/ce5e1f7e131522e6d3426e8e7a490b3a01f39a6696602e1c4f33f9e94277/httptools-0.6.4.tar.gz", hash = "sha256:4e93eee4add6493b59a5c514da98c939b244fce4a0d8879cd3f466562f4b7d5c", size = 240639 } +sdist = { url = "https://files.pythonhosted.org/packages/a7/9a/ce5e1f7e131522e6d3426e8e7a490b3a01f39a6696602e1c4f33f9e94277/httptools-0.6.4.tar.gz", hash = "sha256:4e93eee4add6493b59a5c514da98c939b244fce4a0d8879cd3f466562f4b7d5c", size = 240639, upload_time = "2024-10-16T19:45:08.902Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/6f/972f8eb0ea7d98a1c6be436e2142d51ad2a64ee18e02b0e7ff1f62171ab1/httptools-0.6.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3c73ce323711a6ffb0d247dcd5a550b8babf0f757e86a52558fe5b86d6fefcc0", size = 198780 }, - { url = "https://files.pythonhosted.org/packages/6a/b0/17c672b4bc5c7ba7f201eada4e96c71d0a59fbc185e60e42580093a86f21/httptools-0.6.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:345c288418f0944a6fe67be8e6afa9262b18c7626c3ef3c28adc5eabc06a68da", size = 103297 }, - { url = "https://files.pythonhosted.org/packages/92/5e/b4a826fe91971a0b68e8c2bd4e7db3e7519882f5a8ccdb1194be2b3ab98f/httptools-0.6.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:deee0e3343f98ee8047e9f4c5bc7cedbf69f5734454a94c38ee829fb2d5fa3c1", size = 443130 }, - { url = "https://files.pythonhosted.org/packages/b0/51/ce61e531e40289a681a463e1258fa1e05e0be54540e40d91d065a264cd8f/httptools-0.6.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca80b7485c76f768a3bc83ea58373f8db7b015551117375e4918e2aa77ea9b50", size = 442148 }, - { url = "https://files.pythonhosted.org/packages/ea/9e/270b7d767849b0c96f275c695d27ca76c30671f8eb8cc1bab6ced5c5e1d0/httptools-0.6.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:90d96a385fa941283ebd231464045187a31ad932ebfa541be8edf5b3c2328959", size = 415949 }, - { url = "https://files.pythonhosted.org/packages/81/86/ced96e3179c48c6f656354e106934e65c8963d48b69be78f355797f0e1b3/httptools-0.6.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:59e724f8b332319e2875efd360e61ac07f33b492889284a3e05e6d13746876f4", size = 417591 }, - { url = "https://files.pythonhosted.org/packages/75/73/187a3f620ed3175364ddb56847d7a608a6fc42d551e133197098c0143eca/httptools-0.6.4-cp310-cp310-win_amd64.whl", hash = "sha256:c26f313951f6e26147833fc923f78f95604bbec812a43e5ee37f26dc9e5a686c", size = 88344 }, - { url = "https://files.pythonhosted.org/packages/7b/26/bb526d4d14c2774fe07113ca1db7255737ffbb119315839af2065abfdac3/httptools-0.6.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f47f8ed67cc0ff862b84a1189831d1d33c963fb3ce1ee0c65d3b0cbe7b711069", size = 199029 }, - { url = "https://files.pythonhosted.org/packages/a6/17/3e0d3e9b901c732987a45f4f94d4e2c62b89a041d93db89eafb262afd8d5/httptools-0.6.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0614154d5454c21b6410fdf5262b4a3ddb0f53f1e1721cfd59d55f32138c578a", size = 103492 }, - { url = "https://files.pythonhosted.org/packages/b7/24/0fe235d7b69c42423c7698d086d4db96475f9b50b6ad26a718ef27a0bce6/httptools-0.6.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8787367fbdfccae38e35abf7641dafc5310310a5987b689f4c32cc8cc3ee975", size = 462891 }, - { url = "https://files.pythonhosted.org/packages/b1/2f/205d1f2a190b72da6ffb5f41a3736c26d6fa7871101212b15e9b5cd8f61d/httptools-0.6.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40b0f7fe4fd38e6a507bdb751db0379df1e99120c65fbdc8ee6c1d044897a636", size = 459788 }, - { url = "https://files.pythonhosted.org/packages/6e/4c/d09ce0eff09057a206a74575ae8f1e1e2f0364d20e2442224f9e6612c8b9/httptools-0.6.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:40a5ec98d3f49904b9fe36827dcf1aadfef3b89e2bd05b0e35e94f97c2b14721", size = 433214 }, - { url = "https://files.pythonhosted.org/packages/3e/d2/84c9e23edbccc4a4c6f96a1b8d99dfd2350289e94f00e9ccc7aadde26fb5/httptools-0.6.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dacdd3d10ea1b4ca9df97a0a303cbacafc04b5cd375fa98732678151643d4988", size = 434120 }, - { url = "https://files.pythonhosted.org/packages/d0/46/4d8e7ba9581416de1c425b8264e2cadd201eb709ec1584c381f3e98f51c1/httptools-0.6.4-cp311-cp311-win_amd64.whl", hash = "sha256:288cd628406cc53f9a541cfaf06041b4c71d751856bab45e3702191f931ccd17", size = 88565 }, - { url = "https://files.pythonhosted.org/packages/bb/0e/d0b71465c66b9185f90a091ab36389a7352985fe857e352801c39d6127c8/httptools-0.6.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:df017d6c780287d5c80601dafa31f17bddb170232d85c066604d8558683711a2", size = 200683 }, - { url = "https://files.pythonhosted.org/packages/e2/b8/412a9bb28d0a8988de3296e01efa0bd62068b33856cdda47fe1b5e890954/httptools-0.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:85071a1e8c2d051b507161f6c3e26155b5c790e4e28d7f236422dbacc2a9cc44", size = 104337 }, - { url = "https://files.pythonhosted.org/packages/9b/01/6fb20be3196ffdc8eeec4e653bc2a275eca7f36634c86302242c4fbb2760/httptools-0.6.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69422b7f458c5af875922cdb5bd586cc1f1033295aa9ff63ee196a87519ac8e1", size = 508796 }, - { url = "https://files.pythonhosted.org/packages/f7/d8/b644c44acc1368938317d76ac991c9bba1166311880bcc0ac297cb9d6bd7/httptools-0.6.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16e603a3bff50db08cd578d54f07032ca1631450ceb972c2f834c2b860c28ea2", size = 510837 }, - { url = "https://files.pythonhosted.org/packages/52/d8/254d16a31d543073a0e57f1c329ca7378d8924e7e292eda72d0064987486/httptools-0.6.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ec4f178901fa1834d4a060320d2f3abc5c9e39766953d038f1458cb885f47e81", size = 485289 }, - { url = "https://files.pythonhosted.org/packages/5f/3c/4aee161b4b7a971660b8be71a92c24d6c64372c1ab3ae7f366b3680df20f/httptools-0.6.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f9eb89ecf8b290f2e293325c646a211ff1c2493222798bb80a530c5e7502494f", size = 489779 }, - { url = "https://files.pythonhosted.org/packages/12/b7/5cae71a8868e555f3f67a50ee7f673ce36eac970f029c0c5e9d584352961/httptools-0.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:db78cb9ca56b59b016e64b6031eda5653be0589dba2b1b43453f6e8b405a0970", size = 88634 }, - { url = "https://files.pythonhosted.org/packages/94/a3/9fe9ad23fd35f7de6b91eeb60848986058bd8b5a5c1e256f5860a160cc3e/httptools-0.6.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ade273d7e767d5fae13fa637f4d53b6e961fb7fd93c7797562663f0171c26660", size = 197214 }, - { url = "https://files.pythonhosted.org/packages/ea/d9/82d5e68bab783b632023f2fa31db20bebb4e89dfc4d2293945fd68484ee4/httptools-0.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:856f4bc0478ae143bad54a4242fccb1f3f86a6e1be5548fecfd4102061b3a083", size = 102431 }, - { url = "https://files.pythonhosted.org/packages/96/c1/cb499655cbdbfb57b577734fde02f6fa0bbc3fe9fb4d87b742b512908dff/httptools-0.6.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:322d20ea9cdd1fa98bd6a74b77e2ec5b818abdc3d36695ab402a0de8ef2865a3", size = 473121 }, - { url = "https://files.pythonhosted.org/packages/af/71/ee32fd358f8a3bb199b03261f10921716990808a675d8160b5383487a317/httptools-0.6.4-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d87b29bd4486c0093fc64dea80231f7c7f7eb4dc70ae394d70a495ab8436071", size = 473805 }, - { url = "https://files.pythonhosted.org/packages/8a/0a/0d4df132bfca1507114198b766f1737d57580c9ad1cf93c1ff673e3387be/httptools-0.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:342dd6946aa6bda4b8f18c734576106b8a31f2fe31492881a9a160ec84ff4bd5", size = 448858 }, - { url = "https://files.pythonhosted.org/packages/1e/6a/787004fdef2cabea27bad1073bf6a33f2437b4dbd3b6fb4a9d71172b1c7c/httptools-0.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b36913ba52008249223042dca46e69967985fb4051951f94357ea681e1f5dc0", size = 452042 }, - { url = "https://files.pythonhosted.org/packages/4d/dc/7decab5c404d1d2cdc1bb330b1bf70e83d6af0396fd4fc76fc60c0d522bf/httptools-0.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:28908df1b9bb8187393d5b5db91435ccc9c8e891657f9cbb42a2541b44c82fc8", size = 87682 }, - { url = "https://files.pythonhosted.org/packages/51/b1/4fc6f52afdf93b7c4304e21f6add9e981e4f857c2fa622a55dfe21b6059e/httptools-0.6.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:85797e37e8eeaa5439d33e556662cc370e474445d5fab24dcadc65a8ffb04003", size = 201123 }, - { url = "https://files.pythonhosted.org/packages/c2/01/e6ecb40ac8fdfb76607c7d3b74a41b464458d5c8710534d8f163b0c15f29/httptools-0.6.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:db353d22843cf1028f43c3651581e4bb49374d85692a85f95f7b9a130e1b2cab", size = 104507 }, - { url = "https://files.pythonhosted.org/packages/dc/24/c70c34119d209bf08199d938dc9c69164f585ed3029237b4bdb90f673cb9/httptools-0.6.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1ffd262a73d7c28424252381a5b854c19d9de5f56f075445d33919a637e3547", size = 449615 }, - { url = "https://files.pythonhosted.org/packages/2b/62/e7f317fed3703bd81053840cacba4e40bcf424b870e4197f94bd1cf9fe7a/httptools-0.6.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:703c346571fa50d2e9856a37d7cd9435a25e7fd15e236c397bf224afaa355fe9", size = 448819 }, - { url = "https://files.pythonhosted.org/packages/2a/13/68337d3be6b023260139434c49d7aa466aaa98f9aee7ed29270ac7dde6a2/httptools-0.6.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:aafe0f1918ed07b67c1e838f950b1c1fabc683030477e60b335649b8020e1076", size = 422093 }, - { url = "https://files.pythonhosted.org/packages/fc/b3/3a1bc45be03dda7a60c7858e55b6cd0489a81613c1908fb81cf21d34ae50/httptools-0.6.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:0e563e54979e97b6d13f1bbc05a96109923e76b901f786a5eae36e99c01237bd", size = 423898 }, - { url = "https://files.pythonhosted.org/packages/05/72/2ddc2ae5f7ace986f7e68a326215b2e7c32e32fd40e6428fa8f1d8065c7e/httptools-0.6.4-cp39-cp39-win_amd64.whl", hash = "sha256:b799de31416ecc589ad79dd85a0b2657a8fe39327944998dea368c1d4c9e55e6", size = 89552 }, + { url = "https://files.pythonhosted.org/packages/3b/6f/972f8eb0ea7d98a1c6be436e2142d51ad2a64ee18e02b0e7ff1f62171ab1/httptools-0.6.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3c73ce323711a6ffb0d247dcd5a550b8babf0f757e86a52558fe5b86d6fefcc0", size = 198780, upload_time = "2024-10-16T19:44:06.882Z" }, + { url = "https://files.pythonhosted.org/packages/6a/b0/17c672b4bc5c7ba7f201eada4e96c71d0a59fbc185e60e42580093a86f21/httptools-0.6.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:345c288418f0944a6fe67be8e6afa9262b18c7626c3ef3c28adc5eabc06a68da", size = 103297, upload_time = "2024-10-16T19:44:08.129Z" }, + { url = "https://files.pythonhosted.org/packages/92/5e/b4a826fe91971a0b68e8c2bd4e7db3e7519882f5a8ccdb1194be2b3ab98f/httptools-0.6.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:deee0e3343f98ee8047e9f4c5bc7cedbf69f5734454a94c38ee829fb2d5fa3c1", size = 443130, upload_time = "2024-10-16T19:44:09.45Z" }, + { url = "https://files.pythonhosted.org/packages/b0/51/ce61e531e40289a681a463e1258fa1e05e0be54540e40d91d065a264cd8f/httptools-0.6.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca80b7485c76f768a3bc83ea58373f8db7b015551117375e4918e2aa77ea9b50", size = 442148, upload_time = "2024-10-16T19:44:11.539Z" }, + { url = "https://files.pythonhosted.org/packages/ea/9e/270b7d767849b0c96f275c695d27ca76c30671f8eb8cc1bab6ced5c5e1d0/httptools-0.6.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:90d96a385fa941283ebd231464045187a31ad932ebfa541be8edf5b3c2328959", size = 415949, upload_time = "2024-10-16T19:44:13.388Z" }, + { url = "https://files.pythonhosted.org/packages/81/86/ced96e3179c48c6f656354e106934e65c8963d48b69be78f355797f0e1b3/httptools-0.6.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:59e724f8b332319e2875efd360e61ac07f33b492889284a3e05e6d13746876f4", size = 417591, upload_time = "2024-10-16T19:44:15.258Z" }, + { url = "https://files.pythonhosted.org/packages/75/73/187a3f620ed3175364ddb56847d7a608a6fc42d551e133197098c0143eca/httptools-0.6.4-cp310-cp310-win_amd64.whl", hash = "sha256:c26f313951f6e26147833fc923f78f95604bbec812a43e5ee37f26dc9e5a686c", size = 88344, upload_time = "2024-10-16T19:44:16.54Z" }, + { url = "https://files.pythonhosted.org/packages/7b/26/bb526d4d14c2774fe07113ca1db7255737ffbb119315839af2065abfdac3/httptools-0.6.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f47f8ed67cc0ff862b84a1189831d1d33c963fb3ce1ee0c65d3b0cbe7b711069", size = 199029, upload_time = "2024-10-16T19:44:18.427Z" }, + { url = "https://files.pythonhosted.org/packages/a6/17/3e0d3e9b901c732987a45f4f94d4e2c62b89a041d93db89eafb262afd8d5/httptools-0.6.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0614154d5454c21b6410fdf5262b4a3ddb0f53f1e1721cfd59d55f32138c578a", size = 103492, upload_time = "2024-10-16T19:44:19.515Z" }, + { url = "https://files.pythonhosted.org/packages/b7/24/0fe235d7b69c42423c7698d086d4db96475f9b50b6ad26a718ef27a0bce6/httptools-0.6.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8787367fbdfccae38e35abf7641dafc5310310a5987b689f4c32cc8cc3ee975", size = 462891, upload_time = "2024-10-16T19:44:21.067Z" }, + { url = "https://files.pythonhosted.org/packages/b1/2f/205d1f2a190b72da6ffb5f41a3736c26d6fa7871101212b15e9b5cd8f61d/httptools-0.6.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40b0f7fe4fd38e6a507bdb751db0379df1e99120c65fbdc8ee6c1d044897a636", size = 459788, upload_time = "2024-10-16T19:44:22.958Z" }, + { url = "https://files.pythonhosted.org/packages/6e/4c/d09ce0eff09057a206a74575ae8f1e1e2f0364d20e2442224f9e6612c8b9/httptools-0.6.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:40a5ec98d3f49904b9fe36827dcf1aadfef3b89e2bd05b0e35e94f97c2b14721", size = 433214, upload_time = "2024-10-16T19:44:24.513Z" }, + { url = "https://files.pythonhosted.org/packages/3e/d2/84c9e23edbccc4a4c6f96a1b8d99dfd2350289e94f00e9ccc7aadde26fb5/httptools-0.6.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dacdd3d10ea1b4ca9df97a0a303cbacafc04b5cd375fa98732678151643d4988", size = 434120, upload_time = "2024-10-16T19:44:26.295Z" }, + { url = "https://files.pythonhosted.org/packages/d0/46/4d8e7ba9581416de1c425b8264e2cadd201eb709ec1584c381f3e98f51c1/httptools-0.6.4-cp311-cp311-win_amd64.whl", hash = "sha256:288cd628406cc53f9a541cfaf06041b4c71d751856bab45e3702191f931ccd17", size = 88565, upload_time = "2024-10-16T19:44:29.188Z" }, + { url = "https://files.pythonhosted.org/packages/bb/0e/d0b71465c66b9185f90a091ab36389a7352985fe857e352801c39d6127c8/httptools-0.6.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:df017d6c780287d5c80601dafa31f17bddb170232d85c066604d8558683711a2", size = 200683, upload_time = "2024-10-16T19:44:30.175Z" }, + { url = "https://files.pythonhosted.org/packages/e2/b8/412a9bb28d0a8988de3296e01efa0bd62068b33856cdda47fe1b5e890954/httptools-0.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:85071a1e8c2d051b507161f6c3e26155b5c790e4e28d7f236422dbacc2a9cc44", size = 104337, upload_time = "2024-10-16T19:44:31.786Z" }, + { url = "https://files.pythonhosted.org/packages/9b/01/6fb20be3196ffdc8eeec4e653bc2a275eca7f36634c86302242c4fbb2760/httptools-0.6.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69422b7f458c5af875922cdb5bd586cc1f1033295aa9ff63ee196a87519ac8e1", size = 508796, upload_time = "2024-10-16T19:44:32.825Z" }, + { url = "https://files.pythonhosted.org/packages/f7/d8/b644c44acc1368938317d76ac991c9bba1166311880bcc0ac297cb9d6bd7/httptools-0.6.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16e603a3bff50db08cd578d54f07032ca1631450ceb972c2f834c2b860c28ea2", size = 510837, upload_time = "2024-10-16T19:44:33.974Z" }, + { url = "https://files.pythonhosted.org/packages/52/d8/254d16a31d543073a0e57f1c329ca7378d8924e7e292eda72d0064987486/httptools-0.6.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ec4f178901fa1834d4a060320d2f3abc5c9e39766953d038f1458cb885f47e81", size = 485289, upload_time = "2024-10-16T19:44:35.111Z" }, + { url = "https://files.pythonhosted.org/packages/5f/3c/4aee161b4b7a971660b8be71a92c24d6c64372c1ab3ae7f366b3680df20f/httptools-0.6.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f9eb89ecf8b290f2e293325c646a211ff1c2493222798bb80a530c5e7502494f", size = 489779, upload_time = "2024-10-16T19:44:36.253Z" }, + { url = "https://files.pythonhosted.org/packages/12/b7/5cae71a8868e555f3f67a50ee7f673ce36eac970f029c0c5e9d584352961/httptools-0.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:db78cb9ca56b59b016e64b6031eda5653be0589dba2b1b43453f6e8b405a0970", size = 88634, upload_time = "2024-10-16T19:44:37.357Z" }, + { url = "https://files.pythonhosted.org/packages/94/a3/9fe9ad23fd35f7de6b91eeb60848986058bd8b5a5c1e256f5860a160cc3e/httptools-0.6.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ade273d7e767d5fae13fa637f4d53b6e961fb7fd93c7797562663f0171c26660", size = 197214, upload_time = "2024-10-16T19:44:38.738Z" }, + { url = "https://files.pythonhosted.org/packages/ea/d9/82d5e68bab783b632023f2fa31db20bebb4e89dfc4d2293945fd68484ee4/httptools-0.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:856f4bc0478ae143bad54a4242fccb1f3f86a6e1be5548fecfd4102061b3a083", size = 102431, upload_time = "2024-10-16T19:44:39.818Z" }, + { url = "https://files.pythonhosted.org/packages/96/c1/cb499655cbdbfb57b577734fde02f6fa0bbc3fe9fb4d87b742b512908dff/httptools-0.6.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:322d20ea9cdd1fa98bd6a74b77e2ec5b818abdc3d36695ab402a0de8ef2865a3", size = 473121, upload_time = "2024-10-16T19:44:41.189Z" }, + { url = "https://files.pythonhosted.org/packages/af/71/ee32fd358f8a3bb199b03261f10921716990808a675d8160b5383487a317/httptools-0.6.4-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d87b29bd4486c0093fc64dea80231f7c7f7eb4dc70ae394d70a495ab8436071", size = 473805, upload_time = "2024-10-16T19:44:42.384Z" }, + { url = "https://files.pythonhosted.org/packages/8a/0a/0d4df132bfca1507114198b766f1737d57580c9ad1cf93c1ff673e3387be/httptools-0.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:342dd6946aa6bda4b8f18c734576106b8a31f2fe31492881a9a160ec84ff4bd5", size = 448858, upload_time = "2024-10-16T19:44:43.959Z" }, + { url = "https://files.pythonhosted.org/packages/1e/6a/787004fdef2cabea27bad1073bf6a33f2437b4dbd3b6fb4a9d71172b1c7c/httptools-0.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b36913ba52008249223042dca46e69967985fb4051951f94357ea681e1f5dc0", size = 452042, upload_time = "2024-10-16T19:44:45.071Z" }, + { url = "https://files.pythonhosted.org/packages/4d/dc/7decab5c404d1d2cdc1bb330b1bf70e83d6af0396fd4fc76fc60c0d522bf/httptools-0.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:28908df1b9bb8187393d5b5db91435ccc9c8e891657f9cbb42a2541b44c82fc8", size = 87682, upload_time = "2024-10-16T19:44:46.46Z" }, + { url = "https://files.pythonhosted.org/packages/51/b1/4fc6f52afdf93b7c4304e21f6add9e981e4f857c2fa622a55dfe21b6059e/httptools-0.6.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:85797e37e8eeaa5439d33e556662cc370e474445d5fab24dcadc65a8ffb04003", size = 201123, upload_time = "2024-10-16T19:44:59.13Z" }, + { url = "https://files.pythonhosted.org/packages/c2/01/e6ecb40ac8fdfb76607c7d3b74a41b464458d5c8710534d8f163b0c15f29/httptools-0.6.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:db353d22843cf1028f43c3651581e4bb49374d85692a85f95f7b9a130e1b2cab", size = 104507, upload_time = "2024-10-16T19:45:00.254Z" }, + { url = "https://files.pythonhosted.org/packages/dc/24/c70c34119d209bf08199d938dc9c69164f585ed3029237b4bdb90f673cb9/httptools-0.6.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1ffd262a73d7c28424252381a5b854c19d9de5f56f075445d33919a637e3547", size = 449615, upload_time = "2024-10-16T19:45:01.351Z" }, + { url = "https://files.pythonhosted.org/packages/2b/62/e7f317fed3703bd81053840cacba4e40bcf424b870e4197f94bd1cf9fe7a/httptools-0.6.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:703c346571fa50d2e9856a37d7cd9435a25e7fd15e236c397bf224afaa355fe9", size = 448819, upload_time = "2024-10-16T19:45:02.652Z" }, + { url = "https://files.pythonhosted.org/packages/2a/13/68337d3be6b023260139434c49d7aa466aaa98f9aee7ed29270ac7dde6a2/httptools-0.6.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:aafe0f1918ed07b67c1e838f950b1c1fabc683030477e60b335649b8020e1076", size = 422093, upload_time = "2024-10-16T19:45:03.765Z" }, + { url = "https://files.pythonhosted.org/packages/fc/b3/3a1bc45be03dda7a60c7858e55b6cd0489a81613c1908fb81cf21d34ae50/httptools-0.6.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:0e563e54979e97b6d13f1bbc05a96109923e76b901f786a5eae36e99c01237bd", size = 423898, upload_time = "2024-10-16T19:45:05.683Z" }, + { url = "https://files.pythonhosted.org/packages/05/72/2ddc2ae5f7ace986f7e68a326215b2e7c32e32fd40e6428fa8f1d8065c7e/httptools-0.6.4-cp39-cp39-win_amd64.whl", hash = "sha256:b799de31416ecc589ad79dd85a0b2657a8fe39327944998dea368c1d4c9e55e6", size = 89552, upload_time = "2024-10-16T19:45:07.566Z" }, ] [[package]] @@ -594,27 +596,27 @@ dependencies = [ { name = "httpcore" }, { name = "idna" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406 } +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload_time = "2024-12-06T15:37:23.222Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517 }, + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload_time = "2024-12-06T15:37:21.509Z" }, ] [[package]] name = "idna" version = "3.10" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload_time = "2024-09-15T18:07:39.745Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload_time = "2024-09-15T18:07:37.964Z" }, ] [[package]] name = "iniconfig" version = "2.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793 } +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload_time = "2025-03-19T20:09:59.721Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050 }, + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload_time = "2025-03-19T20:10:01.071Z" }, ] [[package]] @@ -629,9 +631,9 @@ dependencies = [ { name = "tabulate" }, { name = "tomli", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8b/22/74f7fcc96280eea46cf2bcbfa1354ac31de0e60a4be6f7966f12cef20893/interrogate-1.7.0.tar.gz", hash = "sha256:a320d6ec644dfd887cc58247a345054fc4d9f981100c45184470068f4b3719b0", size = 159636 } +sdist = { url = "https://files.pythonhosted.org/packages/8b/22/74f7fcc96280eea46cf2bcbfa1354ac31de0e60a4be6f7966f12cef20893/interrogate-1.7.0.tar.gz", hash = "sha256:a320d6ec644dfd887cc58247a345054fc4d9f981100c45184470068f4b3719b0", size = 159636, upload_time = "2024-04-07T22:30:46.217Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/12/c9/6869a1dcf4aaf309b9543ec070be3ec3adebee7c9bec9af8c230494134b9/interrogate-1.7.0-py3-none-any.whl", hash = "sha256:b13ff4dd8403369670e2efe684066de9fcb868ad9d7f2b4095d8112142dc9d12", size = 46982 }, + { url = "https://files.pythonhosted.org/packages/12/c9/6869a1dcf4aaf309b9543ec070be3ec3adebee7c9bec9af8c230494134b9/interrogate-1.7.0-py3-none-any.whl", hash = "sha256:b13ff4dd8403369670e2efe684066de9fcb868ad9d7f2b4095d8112142dc9d12", size = 46982, upload_time = "2024-04-07T22:30:44.277Z" }, ] [[package]] @@ -641,80 +643,80 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markupsafe" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115 } +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload_time = "2025-03-05T20:05:02.478Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899 }, + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload_time = "2025-03-05T20:05:00.369Z" }, ] [[package]] name = "jiter" version = "0.9.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1e/c2/e4562507f52f0af7036da125bb699602ead37a2332af0788f8e0a3417f36/jiter-0.9.0.tar.gz", hash = "sha256:aadba0964deb424daa24492abc3d229c60c4a31bfee205aedbf1acc7639d7893", size = 162604 } +sdist = { url = "https://files.pythonhosted.org/packages/1e/c2/e4562507f52f0af7036da125bb699602ead37a2332af0788f8e0a3417f36/jiter-0.9.0.tar.gz", hash = "sha256:aadba0964deb424daa24492abc3d229c60c4a31bfee205aedbf1acc7639d7893", size = 162604, upload_time = "2025-03-10T21:37:03.278Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b0/82/39f7c9e67b3b0121f02a0b90d433626caa95a565c3d2449fea6bcfa3f5f5/jiter-0.9.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:816ec9b60fdfd1fec87da1d7ed46c66c44ffec37ab2ef7de5b147b2fce3fd5ad", size = 314540 }, - { url = "https://files.pythonhosted.org/packages/01/07/7bf6022c5a152fca767cf5c086bb41f7c28f70cf33ad259d023b53c0b858/jiter-0.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9b1d3086f8a3ee0194ecf2008cf81286a5c3e540d977fa038ff23576c023c0ea", size = 321065 }, - { url = "https://files.pythonhosted.org/packages/6c/b2/de3f3446ecba7c48f317568e111cc112613da36c7b29a6de45a1df365556/jiter-0.9.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1339f839b91ae30b37c409bf16ccd3dc453e8b8c3ed4bd1d6a567193651a4a51", size = 341664 }, - { url = "https://files.pythonhosted.org/packages/13/cf/6485a4012af5d407689c91296105fcdb080a3538e0658d2abf679619c72f/jiter-0.9.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ffba79584b3b670fefae66ceb3a28822365d25b7bf811e030609a3d5b876f538", size = 364635 }, - { url = "https://files.pythonhosted.org/packages/0d/f7/4a491c568f005553240b486f8e05c82547340572d5018ef79414b4449327/jiter-0.9.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cfc7d0a8e899089d11f065e289cb5b2daf3d82fbe028f49b20d7b809193958d", size = 406288 }, - { url = "https://files.pythonhosted.org/packages/d3/ca/f4263ecbce7f5e6bded8f52a9f1a66540b270c300b5c9f5353d163f9ac61/jiter-0.9.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e00a1a2bbfaaf237e13c3d1592356eab3e9015d7efd59359ac8b51eb56390a12", size = 397499 }, - { url = "https://files.pythonhosted.org/packages/ac/a2/522039e522a10bac2f2194f50e183a49a360d5f63ebf46f6d890ef8aa3f9/jiter-0.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1d9870561eb26b11448854dce0ff27a9a27cb616b632468cafc938de25e9e51", size = 352926 }, - { url = "https://files.pythonhosted.org/packages/b1/67/306a5c5abc82f2e32bd47333a1c9799499c1c3a415f8dde19dbf876f00cb/jiter-0.9.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9872aeff3f21e437651df378cb75aeb7043e5297261222b6441a620218b58708", size = 384506 }, - { url = "https://files.pythonhosted.org/packages/0f/89/c12fe7b65a4fb74f6c0d7b5119576f1f16c79fc2953641f31b288fad8a04/jiter-0.9.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:1fd19112d1049bdd47f17bfbb44a2c0001061312dcf0e72765bfa8abd4aa30e5", size = 520621 }, - { url = "https://files.pythonhosted.org/packages/c4/2b/d57900c5c06e6273fbaa76a19efa74dbc6e70c7427ab421bf0095dfe5d4a/jiter-0.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6ef5da104664e526836070e4a23b5f68dec1cc673b60bf1edb1bfbe8a55d0678", size = 512613 }, - { url = "https://files.pythonhosted.org/packages/89/05/d8b90bfb21e58097d5a4e0224f2940568366f68488a079ae77d4b2653500/jiter-0.9.0-cp310-cp310-win32.whl", hash = "sha256:cb12e6d65ebbefe5518de819f3eda53b73187b7089040b2d17f5b39001ff31c4", size = 206613 }, - { url = "https://files.pythonhosted.org/packages/2c/1d/5767f23f88e4f885090d74bbd2755518050a63040c0f59aa059947035711/jiter-0.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:c43ca669493626d8672be3b645dbb406ef25af3f4b6384cfd306da7eb2e70322", size = 208371 }, - { url = "https://files.pythonhosted.org/packages/23/44/e241a043f114299254e44d7e777ead311da400517f179665e59611ab0ee4/jiter-0.9.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:6c4d99c71508912a7e556d631768dcdef43648a93660670986916b297f1c54af", size = 314654 }, - { url = "https://files.pythonhosted.org/packages/fb/1b/a7e5e42db9fa262baaa9489d8d14ca93f8663e7f164ed5e9acc9f467fc00/jiter-0.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8f60fb8ce7df529812bf6c625635a19d27f30806885139e367af93f6e734ef58", size = 320909 }, - { url = "https://files.pythonhosted.org/packages/60/bf/8ebdfce77bc04b81abf2ea316e9c03b4a866a7d739cf355eae4d6fd9f6fe/jiter-0.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51c4e1a4f8ea84d98b7b98912aa4290ac3d1eabfde8e3c34541fae30e9d1f08b", size = 341733 }, - { url = "https://files.pythonhosted.org/packages/a8/4e/754ebce77cff9ab34d1d0fa0fe98f5d42590fd33622509a3ba6ec37ff466/jiter-0.9.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f4c677c424dc76684fea3e7285a7a2a7493424bea89ac441045e6a1fb1d7b3b", size = 365097 }, - { url = "https://files.pythonhosted.org/packages/32/2c/6019587e6f5844c612ae18ca892f4cd7b3d8bbf49461ed29e384a0f13d98/jiter-0.9.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2221176dfec87f3470b21e6abca056e6b04ce9bff72315cb0b243ca9e835a4b5", size = 406603 }, - { url = "https://files.pythonhosted.org/packages/da/e9/c9e6546c817ab75a1a7dab6dcc698e62e375e1017113e8e983fccbd56115/jiter-0.9.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3c7adb66f899ffa25e3c92bfcb593391ee1947dbdd6a9a970e0d7e713237d572", size = 396625 }, - { url = "https://files.pythonhosted.org/packages/be/bd/976b458add04271ebb5a255e992bd008546ea04bb4dcadc042a16279b4b4/jiter-0.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c98d27330fdfb77913c1097a7aab07f38ff2259048949f499c9901700789ac15", size = 351832 }, - { url = "https://files.pythonhosted.org/packages/07/51/fe59e307aaebec9265dbad44d9d4381d030947e47b0f23531579b9a7c2df/jiter-0.9.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:eda3f8cc74df66892b1d06b5d41a71670c22d95a1ca2cbab73654745ce9d0419", size = 384590 }, - { url = "https://files.pythonhosted.org/packages/db/55/5dcd2693794d8e6f4889389ff66ef3be557a77f8aeeca8973a97a7c00557/jiter-0.9.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:dd5ab5ddc11418dce28343123644a100f487eaccf1de27a459ab36d6cca31043", size = 520690 }, - { url = "https://files.pythonhosted.org/packages/54/d5/9f51dc90985e9eb251fbbb747ab2b13b26601f16c595a7b8baba964043bd/jiter-0.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:42f8a68a69f047b310319ef8e2f52fdb2e7976fb3313ef27df495cf77bcad965", size = 512649 }, - { url = "https://files.pythonhosted.org/packages/a6/e5/4e385945179bcf128fa10ad8dca9053d717cbe09e258110e39045c881fe5/jiter-0.9.0-cp311-cp311-win32.whl", hash = "sha256:a25519efb78a42254d59326ee417d6f5161b06f5da827d94cf521fed961b1ff2", size = 206920 }, - { url = "https://files.pythonhosted.org/packages/4c/47/5e0b94c603d8e54dd1faab439b40b832c277d3b90743e7835879ab663757/jiter-0.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:923b54afdd697dfd00d368b7ccad008cccfeb1efb4e621f32860c75e9f25edbd", size = 210119 }, - { url = "https://files.pythonhosted.org/packages/af/d7/c55086103d6f29b694ec79156242304adf521577530d9031317ce5338c59/jiter-0.9.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:7b46249cfd6c48da28f89eb0be3f52d6fdb40ab88e2c66804f546674e539ec11", size = 309203 }, - { url = "https://files.pythonhosted.org/packages/b0/01/f775dfee50beb420adfd6baf58d1c4d437de41c9b666ddf127c065e5a488/jiter-0.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:609cf3c78852f1189894383cf0b0b977665f54cb38788e3e6b941fa6d982c00e", size = 319678 }, - { url = "https://files.pythonhosted.org/packages/ab/b8/09b73a793714726893e5d46d5c534a63709261af3d24444ad07885ce87cb/jiter-0.9.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d726a3890a54561e55a9c5faea1f7655eda7f105bd165067575ace6e65f80bb2", size = 341816 }, - { url = "https://files.pythonhosted.org/packages/35/6f/b8f89ec5398b2b0d344257138182cc090302854ed63ed9c9051e9c673441/jiter-0.9.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2e89dc075c1fef8fa9be219e249f14040270dbc507df4215c324a1839522ea75", size = 364152 }, - { url = "https://files.pythonhosted.org/packages/9b/ca/978cc3183113b8e4484cc7e210a9ad3c6614396e7abd5407ea8aa1458eef/jiter-0.9.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:04e8ffa3c353b1bc4134f96f167a2082494351e42888dfcf06e944f2729cbe1d", size = 406991 }, - { url = "https://files.pythonhosted.org/packages/13/3a/72861883e11a36d6aa314b4922125f6ae90bdccc225cd96d24cc78a66385/jiter-0.9.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:203f28a72a05ae0e129b3ed1f75f56bc419d5f91dfacd057519a8bd137b00c42", size = 395824 }, - { url = "https://files.pythonhosted.org/packages/87/67/22728a86ef53589c3720225778f7c5fdb617080e3deaed58b04789418212/jiter-0.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fca1a02ad60ec30bb230f65bc01f611c8608b02d269f998bc29cca8619a919dc", size = 351318 }, - { url = "https://files.pythonhosted.org/packages/69/b9/f39728e2e2007276806d7a6609cda7fac44ffa28ca0d02c49a4f397cc0d9/jiter-0.9.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:237e5cee4d5d2659aaf91bbf8ec45052cc217d9446070699441a91b386ae27dc", size = 384591 }, - { url = "https://files.pythonhosted.org/packages/eb/8f/8a708bc7fd87b8a5d861f1c118a995eccbe6d672fe10c9753e67362d0dd0/jiter-0.9.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:528b6b71745e7326eed73c53d4aa57e2a522242320b6f7d65b9c5af83cf49b6e", size = 520746 }, - { url = "https://files.pythonhosted.org/packages/95/1e/65680c7488bd2365dbd2980adaf63c562d3d41d3faac192ebc7ef5b4ae25/jiter-0.9.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9f48e86b57bc711eb5acdfd12b6cb580a59cc9a993f6e7dcb6d8b50522dcd50d", size = 512754 }, - { url = "https://files.pythonhosted.org/packages/78/f3/fdc43547a9ee6e93c837685da704fb6da7dba311fc022e2766d5277dfde5/jiter-0.9.0-cp312-cp312-win32.whl", hash = "sha256:699edfde481e191d81f9cf6d2211debbfe4bd92f06410e7637dffb8dd5dfde06", size = 207075 }, - { url = "https://files.pythonhosted.org/packages/cd/9d/742b289016d155f49028fe1bfbeb935c9bf0ffeefdf77daf4a63a42bb72b/jiter-0.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:099500d07b43f61d8bd780466d429c45a7b25411b334c60ca875fa775f68ccb0", size = 207999 }, - { url = "https://files.pythonhosted.org/packages/e7/1b/4cd165c362e8f2f520fdb43245e2b414f42a255921248b4f8b9c8d871ff1/jiter-0.9.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:2764891d3f3e8b18dce2cff24949153ee30c9239da7c00f032511091ba688ff7", size = 308197 }, - { url = "https://files.pythonhosted.org/packages/13/aa/7a890dfe29c84c9a82064a9fe36079c7c0309c91b70c380dc138f9bea44a/jiter-0.9.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:387b22fbfd7a62418d5212b4638026d01723761c75c1c8232a8b8c37c2f1003b", size = 318160 }, - { url = "https://files.pythonhosted.org/packages/6a/38/5888b43fc01102f733f085673c4f0be5a298f69808ec63de55051754e390/jiter-0.9.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d8da8629ccae3606c61d9184970423655fb4e33d03330bcdfe52d234d32f69", size = 341259 }, - { url = "https://files.pythonhosted.org/packages/3d/5e/bbdbb63305bcc01006de683b6228cd061458b9b7bb9b8d9bc348a58e5dc2/jiter-0.9.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1be73d8982bdc278b7b9377426a4b44ceb5c7952073dd7488e4ae96b88e1103", size = 363730 }, - { url = "https://files.pythonhosted.org/packages/75/85/53a3edc616992fe4af6814c25f91ee3b1e22f7678e979b6ea82d3bc0667e/jiter-0.9.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2228eaaaa111ec54b9e89f7481bffb3972e9059301a878d085b2b449fbbde635", size = 405126 }, - { url = "https://files.pythonhosted.org/packages/ae/b3/1ee26b12b2693bd3f0b71d3188e4e5d817b12e3c630a09e099e0a89e28fa/jiter-0.9.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:11509bfecbc319459647d4ac3fd391d26fdf530dad00c13c4dadabf5b81f01a4", size = 393668 }, - { url = "https://files.pythonhosted.org/packages/11/87/e084ce261950c1861773ab534d49127d1517b629478304d328493f980791/jiter-0.9.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f22238da568be8bbd8e0650e12feeb2cfea15eda4f9fc271d3b362a4fa0604d", size = 352350 }, - { url = "https://files.pythonhosted.org/packages/f0/06/7dca84b04987e9df563610aa0bc154ea176e50358af532ab40ffb87434df/jiter-0.9.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:17f5d55eb856597607562257c8e36c42bc87f16bef52ef7129b7da11afc779f3", size = 384204 }, - { url = "https://files.pythonhosted.org/packages/16/2f/82e1c6020db72f397dd070eec0c85ebc4df7c88967bc86d3ce9864148f28/jiter-0.9.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:6a99bed9fbb02f5bed416d137944419a69aa4c423e44189bc49718859ea83bc5", size = 520322 }, - { url = "https://files.pythonhosted.org/packages/36/fd/4f0cd3abe83ce208991ca61e7e5df915aa35b67f1c0633eb7cf2f2e88ec7/jiter-0.9.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e057adb0cd1bd39606100be0eafe742de2de88c79df632955b9ab53a086b3c8d", size = 512184 }, - { url = "https://files.pythonhosted.org/packages/a0/3c/8a56f6d547731a0b4410a2d9d16bf39c861046f91f57c98f7cab3d2aa9ce/jiter-0.9.0-cp313-cp313-win32.whl", hash = "sha256:f7e6850991f3940f62d387ccfa54d1a92bd4bb9f89690b53aea36b4364bcab53", size = 206504 }, - { url = "https://files.pythonhosted.org/packages/f4/1c/0c996fd90639acda75ed7fa698ee5fd7d80243057185dc2f63d4c1c9f6b9/jiter-0.9.0-cp313-cp313-win_amd64.whl", hash = "sha256:c8ae3bf27cd1ac5e6e8b7a27487bf3ab5f82318211ec2e1346a5b058756361f7", size = 204943 }, - { url = "https://files.pythonhosted.org/packages/78/0f/77a63ca7aa5fed9a1b9135af57e190d905bcd3702b36aca46a01090d39ad/jiter-0.9.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f0b2827fb88dda2cbecbbc3e596ef08d69bda06c6f57930aec8e79505dc17001", size = 317281 }, - { url = "https://files.pythonhosted.org/packages/f9/39/a3a1571712c2bf6ec4c657f0d66da114a63a2e32b7e4eb8e0b83295ee034/jiter-0.9.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:062b756ceb1d40b0b28f326cba26cfd575a4918415b036464a52f08632731e5a", size = 350273 }, - { url = "https://files.pythonhosted.org/packages/ee/47/3729f00f35a696e68da15d64eb9283c330e776f3b5789bac7f2c0c4df209/jiter-0.9.0-cp313-cp313t-win_amd64.whl", hash = "sha256:6f7838bc467ab7e8ef9f387bd6de195c43bad82a569c1699cb822f6609dd4cdf", size = 206867 }, - { url = "https://files.pythonhosted.org/packages/aa/2c/9bee940db68d8cefb84178f8b15220c836276db8c6e09cbd422071c01c33/jiter-0.9.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:9ef340fae98065071ccd5805fe81c99c8f80484e820e40043689cf97fb66b3e2", size = 315246 }, - { url = "https://files.pythonhosted.org/packages/d0/9b/42d5d59585d9af4fe207e96c6edac2a62bca26d76e2471e78c2f5da28bb8/jiter-0.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:efb767d92c63b2cd9ec9f24feeb48f49574a713870ec87e9ba0c2c6e9329c3e2", size = 312621 }, - { url = "https://files.pythonhosted.org/packages/2e/a5/a64de757516e5531f8d147a32251905f0e23641738d3520a0a0724fe9651/jiter-0.9.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:113f30f87fb1f412510c6d7ed13e91422cfd329436364a690c34c8b8bd880c42", size = 343006 }, - { url = "https://files.pythonhosted.org/packages/89/be/08d2bae711200d558ab8c5771f05f47cd09b82b2258a8d6fad0ee2c6a1f3/jiter-0.9.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8793b6df019b988526f5a633fdc7456ea75e4a79bd8396a3373c371fc59f5c9b", size = 365099 }, - { url = "https://files.pythonhosted.org/packages/03/9e/d137a0088be90ba5081f7d5d2383374bd77a1447158e44c3ec4e142f902c/jiter-0.9.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7a9aaa5102dba4e079bb728076fadd5a2dca94c05c04ce68004cfd96f128ea34", size = 407834 }, - { url = "https://files.pythonhosted.org/packages/04/4c/b6bee52a5b327830abea13eba4222f33f88895a1055eff8870ab3ebbde41/jiter-0.9.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d838650f6ebaf4ccadfb04522463e74a4c378d7e667e0eb1865cfe3990bfac49", size = 399255 }, - { url = "https://files.pythonhosted.org/packages/12/b7/364b615a35f99d01cc27d3caea8c3a3ac5451bd5cadf8e5dc4355b102aba/jiter-0.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0194f813efdf4b8865ad5f5c5f50f8566df7d770a82c51ef593d09e0b347020", size = 354142 }, - { url = "https://files.pythonhosted.org/packages/65/cc/5156f75c496aac65080e2995910104d0e46644df1452c20d963cb904b4b1/jiter-0.9.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a7954a401d0a8a0b8bc669199db78af435aae1e3569187c2939c477c53cb6a0a", size = 385142 }, - { url = "https://files.pythonhosted.org/packages/46/cf/370be59c38e56a6fed0308ca266b12d8178b8d6630284cc88ae5af110764/jiter-0.9.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4feafe787eb8a8d98168ab15637ca2577f6ddf77ac6c8c66242c2d028aa5420e", size = 522035 }, - { url = "https://files.pythonhosted.org/packages/ff/f5/c462d994dcbff43de8a3c953548d609c73a5db8138182408944fce2b68c1/jiter-0.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:27cd1f2e8bb377f31d3190b34e4328d280325ad7ef55c6ac9abde72f79e84d2e", size = 513844 }, - { url = "https://files.pythonhosted.org/packages/15/39/60d8f17de27586fa1e7c8215ead8222556d40a6b96b20f1ad70528961f99/jiter-0.9.0-cp39-cp39-win32.whl", hash = "sha256:161d461dcbe658cf0bd0aa375b30a968b087cdddc624fc585f3867c63c6eca95", size = 207147 }, - { url = "https://files.pythonhosted.org/packages/4b/13/c10f17dcddd1b4c1313418e64ace5e77cc4f7313246140fb09044516a62c/jiter-0.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:e8b36d8a16a61993be33e75126ad3d8aa29cf450b09576f3c427d27647fcb4aa", size = 208879 }, + { url = "https://files.pythonhosted.org/packages/b0/82/39f7c9e67b3b0121f02a0b90d433626caa95a565c3d2449fea6bcfa3f5f5/jiter-0.9.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:816ec9b60fdfd1fec87da1d7ed46c66c44ffec37ab2ef7de5b147b2fce3fd5ad", size = 314540, upload_time = "2025-03-10T21:35:02.218Z" }, + { url = "https://files.pythonhosted.org/packages/01/07/7bf6022c5a152fca767cf5c086bb41f7c28f70cf33ad259d023b53c0b858/jiter-0.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9b1d3086f8a3ee0194ecf2008cf81286a5c3e540d977fa038ff23576c023c0ea", size = 321065, upload_time = "2025-03-10T21:35:04.274Z" }, + { url = "https://files.pythonhosted.org/packages/6c/b2/de3f3446ecba7c48f317568e111cc112613da36c7b29a6de45a1df365556/jiter-0.9.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1339f839b91ae30b37c409bf16ccd3dc453e8b8c3ed4bd1d6a567193651a4a51", size = 341664, upload_time = "2025-03-10T21:35:06.032Z" }, + { url = "https://files.pythonhosted.org/packages/13/cf/6485a4012af5d407689c91296105fcdb080a3538e0658d2abf679619c72f/jiter-0.9.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ffba79584b3b670fefae66ceb3a28822365d25b7bf811e030609a3d5b876f538", size = 364635, upload_time = "2025-03-10T21:35:07.749Z" }, + { url = "https://files.pythonhosted.org/packages/0d/f7/4a491c568f005553240b486f8e05c82547340572d5018ef79414b4449327/jiter-0.9.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cfc7d0a8e899089d11f065e289cb5b2daf3d82fbe028f49b20d7b809193958d", size = 406288, upload_time = "2025-03-10T21:35:09.238Z" }, + { url = "https://files.pythonhosted.org/packages/d3/ca/f4263ecbce7f5e6bded8f52a9f1a66540b270c300b5c9f5353d163f9ac61/jiter-0.9.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e00a1a2bbfaaf237e13c3d1592356eab3e9015d7efd59359ac8b51eb56390a12", size = 397499, upload_time = "2025-03-10T21:35:12.463Z" }, + { url = "https://files.pythonhosted.org/packages/ac/a2/522039e522a10bac2f2194f50e183a49a360d5f63ebf46f6d890ef8aa3f9/jiter-0.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1d9870561eb26b11448854dce0ff27a9a27cb616b632468cafc938de25e9e51", size = 352926, upload_time = "2025-03-10T21:35:13.85Z" }, + { url = "https://files.pythonhosted.org/packages/b1/67/306a5c5abc82f2e32bd47333a1c9799499c1c3a415f8dde19dbf876f00cb/jiter-0.9.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9872aeff3f21e437651df378cb75aeb7043e5297261222b6441a620218b58708", size = 384506, upload_time = "2025-03-10T21:35:15.735Z" }, + { url = "https://files.pythonhosted.org/packages/0f/89/c12fe7b65a4fb74f6c0d7b5119576f1f16c79fc2953641f31b288fad8a04/jiter-0.9.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:1fd19112d1049bdd47f17bfbb44a2c0001061312dcf0e72765bfa8abd4aa30e5", size = 520621, upload_time = "2025-03-10T21:35:17.55Z" }, + { url = "https://files.pythonhosted.org/packages/c4/2b/d57900c5c06e6273fbaa76a19efa74dbc6e70c7427ab421bf0095dfe5d4a/jiter-0.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6ef5da104664e526836070e4a23b5f68dec1cc673b60bf1edb1bfbe8a55d0678", size = 512613, upload_time = "2025-03-10T21:35:19.178Z" }, + { url = "https://files.pythonhosted.org/packages/89/05/d8b90bfb21e58097d5a4e0224f2940568366f68488a079ae77d4b2653500/jiter-0.9.0-cp310-cp310-win32.whl", hash = "sha256:cb12e6d65ebbefe5518de819f3eda53b73187b7089040b2d17f5b39001ff31c4", size = 206613, upload_time = "2025-03-10T21:35:21.039Z" }, + { url = "https://files.pythonhosted.org/packages/2c/1d/5767f23f88e4f885090d74bbd2755518050a63040c0f59aa059947035711/jiter-0.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:c43ca669493626d8672be3b645dbb406ef25af3f4b6384cfd306da7eb2e70322", size = 208371, upload_time = "2025-03-10T21:35:22.536Z" }, + { url = "https://files.pythonhosted.org/packages/23/44/e241a043f114299254e44d7e777ead311da400517f179665e59611ab0ee4/jiter-0.9.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:6c4d99c71508912a7e556d631768dcdef43648a93660670986916b297f1c54af", size = 314654, upload_time = "2025-03-10T21:35:23.939Z" }, + { url = "https://files.pythonhosted.org/packages/fb/1b/a7e5e42db9fa262baaa9489d8d14ca93f8663e7f164ed5e9acc9f467fc00/jiter-0.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8f60fb8ce7df529812bf6c625635a19d27f30806885139e367af93f6e734ef58", size = 320909, upload_time = "2025-03-10T21:35:26.127Z" }, + { url = "https://files.pythonhosted.org/packages/60/bf/8ebdfce77bc04b81abf2ea316e9c03b4a866a7d739cf355eae4d6fd9f6fe/jiter-0.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51c4e1a4f8ea84d98b7b98912aa4290ac3d1eabfde8e3c34541fae30e9d1f08b", size = 341733, upload_time = "2025-03-10T21:35:27.94Z" }, + { url = "https://files.pythonhosted.org/packages/a8/4e/754ebce77cff9ab34d1d0fa0fe98f5d42590fd33622509a3ba6ec37ff466/jiter-0.9.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f4c677c424dc76684fea3e7285a7a2a7493424bea89ac441045e6a1fb1d7b3b", size = 365097, upload_time = "2025-03-10T21:35:29.605Z" }, + { url = "https://files.pythonhosted.org/packages/32/2c/6019587e6f5844c612ae18ca892f4cd7b3d8bbf49461ed29e384a0f13d98/jiter-0.9.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2221176dfec87f3470b21e6abca056e6b04ce9bff72315cb0b243ca9e835a4b5", size = 406603, upload_time = "2025-03-10T21:35:31.696Z" }, + { url = "https://files.pythonhosted.org/packages/da/e9/c9e6546c817ab75a1a7dab6dcc698e62e375e1017113e8e983fccbd56115/jiter-0.9.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3c7adb66f899ffa25e3c92bfcb593391ee1947dbdd6a9a970e0d7e713237d572", size = 396625, upload_time = "2025-03-10T21:35:33.182Z" }, + { url = "https://files.pythonhosted.org/packages/be/bd/976b458add04271ebb5a255e992bd008546ea04bb4dcadc042a16279b4b4/jiter-0.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c98d27330fdfb77913c1097a7aab07f38ff2259048949f499c9901700789ac15", size = 351832, upload_time = "2025-03-10T21:35:35.394Z" }, + { url = "https://files.pythonhosted.org/packages/07/51/fe59e307aaebec9265dbad44d9d4381d030947e47b0f23531579b9a7c2df/jiter-0.9.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:eda3f8cc74df66892b1d06b5d41a71670c22d95a1ca2cbab73654745ce9d0419", size = 384590, upload_time = "2025-03-10T21:35:37.171Z" }, + { url = "https://files.pythonhosted.org/packages/db/55/5dcd2693794d8e6f4889389ff66ef3be557a77f8aeeca8973a97a7c00557/jiter-0.9.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:dd5ab5ddc11418dce28343123644a100f487eaccf1de27a459ab36d6cca31043", size = 520690, upload_time = "2025-03-10T21:35:38.717Z" }, + { url = "https://files.pythonhosted.org/packages/54/d5/9f51dc90985e9eb251fbbb747ab2b13b26601f16c595a7b8baba964043bd/jiter-0.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:42f8a68a69f047b310319ef8e2f52fdb2e7976fb3313ef27df495cf77bcad965", size = 512649, upload_time = "2025-03-10T21:35:40.157Z" }, + { url = "https://files.pythonhosted.org/packages/a6/e5/4e385945179bcf128fa10ad8dca9053d717cbe09e258110e39045c881fe5/jiter-0.9.0-cp311-cp311-win32.whl", hash = "sha256:a25519efb78a42254d59326ee417d6f5161b06f5da827d94cf521fed961b1ff2", size = 206920, upload_time = "2025-03-10T21:35:41.72Z" }, + { url = "https://files.pythonhosted.org/packages/4c/47/5e0b94c603d8e54dd1faab439b40b832c277d3b90743e7835879ab663757/jiter-0.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:923b54afdd697dfd00d368b7ccad008cccfeb1efb4e621f32860c75e9f25edbd", size = 210119, upload_time = "2025-03-10T21:35:43.46Z" }, + { url = "https://files.pythonhosted.org/packages/af/d7/c55086103d6f29b694ec79156242304adf521577530d9031317ce5338c59/jiter-0.9.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:7b46249cfd6c48da28f89eb0be3f52d6fdb40ab88e2c66804f546674e539ec11", size = 309203, upload_time = "2025-03-10T21:35:44.852Z" }, + { url = "https://files.pythonhosted.org/packages/b0/01/f775dfee50beb420adfd6baf58d1c4d437de41c9b666ddf127c065e5a488/jiter-0.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:609cf3c78852f1189894383cf0b0b977665f54cb38788e3e6b941fa6d982c00e", size = 319678, upload_time = "2025-03-10T21:35:46.365Z" }, + { url = "https://files.pythonhosted.org/packages/ab/b8/09b73a793714726893e5d46d5c534a63709261af3d24444ad07885ce87cb/jiter-0.9.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d726a3890a54561e55a9c5faea1f7655eda7f105bd165067575ace6e65f80bb2", size = 341816, upload_time = "2025-03-10T21:35:47.856Z" }, + { url = "https://files.pythonhosted.org/packages/35/6f/b8f89ec5398b2b0d344257138182cc090302854ed63ed9c9051e9c673441/jiter-0.9.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2e89dc075c1fef8fa9be219e249f14040270dbc507df4215c324a1839522ea75", size = 364152, upload_time = "2025-03-10T21:35:49.397Z" }, + { url = "https://files.pythonhosted.org/packages/9b/ca/978cc3183113b8e4484cc7e210a9ad3c6614396e7abd5407ea8aa1458eef/jiter-0.9.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:04e8ffa3c353b1bc4134f96f167a2082494351e42888dfcf06e944f2729cbe1d", size = 406991, upload_time = "2025-03-10T21:35:50.745Z" }, + { url = "https://files.pythonhosted.org/packages/13/3a/72861883e11a36d6aa314b4922125f6ae90bdccc225cd96d24cc78a66385/jiter-0.9.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:203f28a72a05ae0e129b3ed1f75f56bc419d5f91dfacd057519a8bd137b00c42", size = 395824, upload_time = "2025-03-10T21:35:52.162Z" }, + { url = "https://files.pythonhosted.org/packages/87/67/22728a86ef53589c3720225778f7c5fdb617080e3deaed58b04789418212/jiter-0.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fca1a02ad60ec30bb230f65bc01f611c8608b02d269f998bc29cca8619a919dc", size = 351318, upload_time = "2025-03-10T21:35:53.566Z" }, + { url = "https://files.pythonhosted.org/packages/69/b9/f39728e2e2007276806d7a6609cda7fac44ffa28ca0d02c49a4f397cc0d9/jiter-0.9.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:237e5cee4d5d2659aaf91bbf8ec45052cc217d9446070699441a91b386ae27dc", size = 384591, upload_time = "2025-03-10T21:35:54.95Z" }, + { url = "https://files.pythonhosted.org/packages/eb/8f/8a708bc7fd87b8a5d861f1c118a995eccbe6d672fe10c9753e67362d0dd0/jiter-0.9.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:528b6b71745e7326eed73c53d4aa57e2a522242320b6f7d65b9c5af83cf49b6e", size = 520746, upload_time = "2025-03-10T21:35:56.444Z" }, + { url = "https://files.pythonhosted.org/packages/95/1e/65680c7488bd2365dbd2980adaf63c562d3d41d3faac192ebc7ef5b4ae25/jiter-0.9.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9f48e86b57bc711eb5acdfd12b6cb580a59cc9a993f6e7dcb6d8b50522dcd50d", size = 512754, upload_time = "2025-03-10T21:35:58.789Z" }, + { url = "https://files.pythonhosted.org/packages/78/f3/fdc43547a9ee6e93c837685da704fb6da7dba311fc022e2766d5277dfde5/jiter-0.9.0-cp312-cp312-win32.whl", hash = "sha256:699edfde481e191d81f9cf6d2211debbfe4bd92f06410e7637dffb8dd5dfde06", size = 207075, upload_time = "2025-03-10T21:36:00.616Z" }, + { url = "https://files.pythonhosted.org/packages/cd/9d/742b289016d155f49028fe1bfbeb935c9bf0ffeefdf77daf4a63a42bb72b/jiter-0.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:099500d07b43f61d8bd780466d429c45a7b25411b334c60ca875fa775f68ccb0", size = 207999, upload_time = "2025-03-10T21:36:02.366Z" }, + { url = "https://files.pythonhosted.org/packages/e7/1b/4cd165c362e8f2f520fdb43245e2b414f42a255921248b4f8b9c8d871ff1/jiter-0.9.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:2764891d3f3e8b18dce2cff24949153ee30c9239da7c00f032511091ba688ff7", size = 308197, upload_time = "2025-03-10T21:36:03.828Z" }, + { url = "https://files.pythonhosted.org/packages/13/aa/7a890dfe29c84c9a82064a9fe36079c7c0309c91b70c380dc138f9bea44a/jiter-0.9.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:387b22fbfd7a62418d5212b4638026d01723761c75c1c8232a8b8c37c2f1003b", size = 318160, upload_time = "2025-03-10T21:36:05.281Z" }, + { url = "https://files.pythonhosted.org/packages/6a/38/5888b43fc01102f733f085673c4f0be5a298f69808ec63de55051754e390/jiter-0.9.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d8da8629ccae3606c61d9184970423655fb4e33d03330bcdfe52d234d32f69", size = 341259, upload_time = "2025-03-10T21:36:06.716Z" }, + { url = "https://files.pythonhosted.org/packages/3d/5e/bbdbb63305bcc01006de683b6228cd061458b9b7bb9b8d9bc348a58e5dc2/jiter-0.9.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1be73d8982bdc278b7b9377426a4b44ceb5c7952073dd7488e4ae96b88e1103", size = 363730, upload_time = "2025-03-10T21:36:08.138Z" }, + { url = "https://files.pythonhosted.org/packages/75/85/53a3edc616992fe4af6814c25f91ee3b1e22f7678e979b6ea82d3bc0667e/jiter-0.9.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2228eaaaa111ec54b9e89f7481bffb3972e9059301a878d085b2b449fbbde635", size = 405126, upload_time = "2025-03-10T21:36:10.934Z" }, + { url = "https://files.pythonhosted.org/packages/ae/b3/1ee26b12b2693bd3f0b71d3188e4e5d817b12e3c630a09e099e0a89e28fa/jiter-0.9.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:11509bfecbc319459647d4ac3fd391d26fdf530dad00c13c4dadabf5b81f01a4", size = 393668, upload_time = "2025-03-10T21:36:12.468Z" }, + { url = "https://files.pythonhosted.org/packages/11/87/e084ce261950c1861773ab534d49127d1517b629478304d328493f980791/jiter-0.9.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f22238da568be8bbd8e0650e12feeb2cfea15eda4f9fc271d3b362a4fa0604d", size = 352350, upload_time = "2025-03-10T21:36:14.148Z" }, + { url = "https://files.pythonhosted.org/packages/f0/06/7dca84b04987e9df563610aa0bc154ea176e50358af532ab40ffb87434df/jiter-0.9.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:17f5d55eb856597607562257c8e36c42bc87f16bef52ef7129b7da11afc779f3", size = 384204, upload_time = "2025-03-10T21:36:15.545Z" }, + { url = "https://files.pythonhosted.org/packages/16/2f/82e1c6020db72f397dd070eec0c85ebc4df7c88967bc86d3ce9864148f28/jiter-0.9.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:6a99bed9fbb02f5bed416d137944419a69aa4c423e44189bc49718859ea83bc5", size = 520322, upload_time = "2025-03-10T21:36:17.016Z" }, + { url = "https://files.pythonhosted.org/packages/36/fd/4f0cd3abe83ce208991ca61e7e5df915aa35b67f1c0633eb7cf2f2e88ec7/jiter-0.9.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e057adb0cd1bd39606100be0eafe742de2de88c79df632955b9ab53a086b3c8d", size = 512184, upload_time = "2025-03-10T21:36:18.47Z" }, + { url = "https://files.pythonhosted.org/packages/a0/3c/8a56f6d547731a0b4410a2d9d16bf39c861046f91f57c98f7cab3d2aa9ce/jiter-0.9.0-cp313-cp313-win32.whl", hash = "sha256:f7e6850991f3940f62d387ccfa54d1a92bd4bb9f89690b53aea36b4364bcab53", size = 206504, upload_time = "2025-03-10T21:36:19.809Z" }, + { url = "https://files.pythonhosted.org/packages/f4/1c/0c996fd90639acda75ed7fa698ee5fd7d80243057185dc2f63d4c1c9f6b9/jiter-0.9.0-cp313-cp313-win_amd64.whl", hash = "sha256:c8ae3bf27cd1ac5e6e8b7a27487bf3ab5f82318211ec2e1346a5b058756361f7", size = 204943, upload_time = "2025-03-10T21:36:21.536Z" }, + { url = "https://files.pythonhosted.org/packages/78/0f/77a63ca7aa5fed9a1b9135af57e190d905bcd3702b36aca46a01090d39ad/jiter-0.9.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f0b2827fb88dda2cbecbbc3e596ef08d69bda06c6f57930aec8e79505dc17001", size = 317281, upload_time = "2025-03-10T21:36:22.959Z" }, + { url = "https://files.pythonhosted.org/packages/f9/39/a3a1571712c2bf6ec4c657f0d66da114a63a2e32b7e4eb8e0b83295ee034/jiter-0.9.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:062b756ceb1d40b0b28f326cba26cfd575a4918415b036464a52f08632731e5a", size = 350273, upload_time = "2025-03-10T21:36:24.414Z" }, + { url = "https://files.pythonhosted.org/packages/ee/47/3729f00f35a696e68da15d64eb9283c330e776f3b5789bac7f2c0c4df209/jiter-0.9.0-cp313-cp313t-win_amd64.whl", hash = "sha256:6f7838bc467ab7e8ef9f387bd6de195c43bad82a569c1699cb822f6609dd4cdf", size = 206867, upload_time = "2025-03-10T21:36:25.843Z" }, + { url = "https://files.pythonhosted.org/packages/aa/2c/9bee940db68d8cefb84178f8b15220c836276db8c6e09cbd422071c01c33/jiter-0.9.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:9ef340fae98065071ccd5805fe81c99c8f80484e820e40043689cf97fb66b3e2", size = 315246, upload_time = "2025-03-10T21:36:44.958Z" }, + { url = "https://files.pythonhosted.org/packages/d0/9b/42d5d59585d9af4fe207e96c6edac2a62bca26d76e2471e78c2f5da28bb8/jiter-0.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:efb767d92c63b2cd9ec9f24feeb48f49574a713870ec87e9ba0c2c6e9329c3e2", size = 312621, upload_time = "2025-03-10T21:36:46.826Z" }, + { url = "https://files.pythonhosted.org/packages/2e/a5/a64de757516e5531f8d147a32251905f0e23641738d3520a0a0724fe9651/jiter-0.9.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:113f30f87fb1f412510c6d7ed13e91422cfd329436364a690c34c8b8bd880c42", size = 343006, upload_time = "2025-03-10T21:36:48.299Z" }, + { url = "https://files.pythonhosted.org/packages/89/be/08d2bae711200d558ab8c5771f05f47cd09b82b2258a8d6fad0ee2c6a1f3/jiter-0.9.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8793b6df019b988526f5a633fdc7456ea75e4a79bd8396a3373c371fc59f5c9b", size = 365099, upload_time = "2025-03-10T21:36:49.701Z" }, + { url = "https://files.pythonhosted.org/packages/03/9e/d137a0088be90ba5081f7d5d2383374bd77a1447158e44c3ec4e142f902c/jiter-0.9.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7a9aaa5102dba4e079bb728076fadd5a2dca94c05c04ce68004cfd96f128ea34", size = 407834, upload_time = "2025-03-10T21:36:51.144Z" }, + { url = "https://files.pythonhosted.org/packages/04/4c/b6bee52a5b327830abea13eba4222f33f88895a1055eff8870ab3ebbde41/jiter-0.9.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d838650f6ebaf4ccadfb04522463e74a4c378d7e667e0eb1865cfe3990bfac49", size = 399255, upload_time = "2025-03-10T21:36:52.581Z" }, + { url = "https://files.pythonhosted.org/packages/12/b7/364b615a35f99d01cc27d3caea8c3a3ac5451bd5cadf8e5dc4355b102aba/jiter-0.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0194f813efdf4b8865ad5f5c5f50f8566df7d770a82c51ef593d09e0b347020", size = 354142, upload_time = "2025-03-10T21:36:54.138Z" }, + { url = "https://files.pythonhosted.org/packages/65/cc/5156f75c496aac65080e2995910104d0e46644df1452c20d963cb904b4b1/jiter-0.9.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a7954a401d0a8a0b8bc669199db78af435aae1e3569187c2939c477c53cb6a0a", size = 385142, upload_time = "2025-03-10T21:36:55.631Z" }, + { url = "https://files.pythonhosted.org/packages/46/cf/370be59c38e56a6fed0308ca266b12d8178b8d6630284cc88ae5af110764/jiter-0.9.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4feafe787eb8a8d98168ab15637ca2577f6ddf77ac6c8c66242c2d028aa5420e", size = 522035, upload_time = "2025-03-10T21:36:57.082Z" }, + { url = "https://files.pythonhosted.org/packages/ff/f5/c462d994dcbff43de8a3c953548d609c73a5db8138182408944fce2b68c1/jiter-0.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:27cd1f2e8bb377f31d3190b34e4328d280325ad7ef55c6ac9abde72f79e84d2e", size = 513844, upload_time = "2025-03-10T21:36:58.827Z" }, + { url = "https://files.pythonhosted.org/packages/15/39/60d8f17de27586fa1e7c8215ead8222556d40a6b96b20f1ad70528961f99/jiter-0.9.0-cp39-cp39-win32.whl", hash = "sha256:161d461dcbe658cf0bd0aa375b30a968b087cdddc624fc585f3867c63c6eca95", size = 207147, upload_time = "2025-03-10T21:37:00.227Z" }, + { url = "https://files.pythonhosted.org/packages/4b/13/c10f17dcddd1b4c1313418e64ace5e77cc4f7313246140fb09044516a62c/jiter-0.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:e8b36d8a16a61993be33e75126ad3d8aa29cf450b09576f3c427d27647fcb4aa", size = 208879, upload_time = "2025-03-10T21:37:01.582Z" }, ] [[package]] @@ -731,9 +733,9 @@ dependencies = [ { name = "requests" }, { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/06/54/8f7888631843e1b8af9c96a021f7913068af8972d9dfa9ffdcda7774c3b5/langfuse-2.60.2.tar.gz", hash = "sha256:b63cba99c24358b3ec4bde63fb76c2825d162c4f5670862e6be43b9dafb530d9", size = 152439 } +sdist = { url = "https://files.pythonhosted.org/packages/06/54/8f7888631843e1b8af9c96a021f7913068af8972d9dfa9ffdcda7774c3b5/langfuse-2.60.2.tar.gz", hash = "sha256:b63cba99c24358b3ec4bde63fb76c2825d162c4f5670862e6be43b9dafb530d9", size = 152439, upload_time = "2025-03-24T12:44:11.696Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/06/64/58eb43a7a21e0aeabb004703834b242f1538e0daac1058a3b6f8b5f1d110/langfuse-2.60.2-py3-none-any.whl", hash = "sha256:749c875b808a386fe4608f6ffa44474bb6f984b3a510596e1fb1a49f8c973064", size = 274986 }, + { url = "https://files.pythonhosted.org/packages/06/64/58eb43a7a21e0aeabb004703834b242f1538e0daac1058a3b6f8b5f1d110/langfuse-2.60.2-py3-none-any.whl", hash = "sha256:749c875b808a386fe4608f6ffa44474bb6f984b3a510596e1fb1a49f8c973064", size = 274986, upload_time = "2025-03-24T12:44:09.442Z" }, ] [[package]] @@ -743,9 +745,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markupsafe" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/62/4f/ddb1965901bc388958db9f0c991255b2c469349a741ae8c9cd8a562d70a6/mako-1.3.9.tar.gz", hash = "sha256:b5d65ff3462870feec922dbccf38f6efb44e5714d7b593a656be86663d8600ac", size = 392195 } +sdist = { url = "https://files.pythonhosted.org/packages/62/4f/ddb1965901bc388958db9f0c991255b2c469349a741ae8c9cd8a562d70a6/mako-1.3.9.tar.gz", hash = "sha256:b5d65ff3462870feec922dbccf38f6efb44e5714d7b593a656be86663d8600ac", size = 392195, upload_time = "2025-02-04T15:05:49.37Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cd/83/de0a49e7de540513f53ab5d2e105321dedeb08a8f5850f0208decf4390ec/Mako-1.3.9-py3-none-any.whl", hash = "sha256:95920acccb578427a9aa38e37a186b1e43156c87260d7ba18ca63aa4c7cbd3a1", size = 78456 }, + { url = "https://files.pythonhosted.org/packages/cd/83/de0a49e7de540513f53ab5d2e105321dedeb08a8f5850f0208decf4390ec/Mako-1.3.9-py3-none-any.whl", hash = "sha256:95920acccb578427a9aa38e37a186b1e43156c87260d7ba18ca63aa4c7cbd3a1", size = 78456, upload_time = "2025-02-04T15:05:51.115Z" }, ] [[package]] @@ -755,95 +757,95 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mdurl" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596 } +sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596, upload_time = "2023-06-03T06:41:14.443Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528 }, + { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528, upload_time = "2023-06-03T06:41:11.019Z" }, ] [[package]] name = "markupsafe" version = "3.0.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537 } +sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload_time = "2024-10-18T15:21:54.129Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/04/90/d08277ce111dd22f77149fd1a5d4653eeb3b3eaacbdfcbae5afb2600eebd/MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8", size = 14357 }, - { url = "https://files.pythonhosted.org/packages/04/e1/6e2194baeae0bca1fae6629dc0cbbb968d4d941469cbab11a3872edff374/MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158", size = 12393 }, - { url = "https://files.pythonhosted.org/packages/1d/69/35fa85a8ece0a437493dc61ce0bb6d459dcba482c34197e3efc829aa357f/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579", size = 21732 }, - { url = "https://files.pythonhosted.org/packages/22/35/137da042dfb4720b638d2937c38a9c2df83fe32d20e8c8f3185dbfef05f7/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d", size = 20866 }, - { url = "https://files.pythonhosted.org/packages/29/28/6d029a903727a1b62edb51863232152fd335d602def598dade38996887f0/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb", size = 20964 }, - { url = "https://files.pythonhosted.org/packages/cc/cd/07438f95f83e8bc028279909d9c9bd39e24149b0d60053a97b2bc4f8aa51/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b", size = 21977 }, - { url = "https://files.pythonhosted.org/packages/29/01/84b57395b4cc062f9c4c55ce0df7d3108ca32397299d9df00fedd9117d3d/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c", size = 21366 }, - { url = "https://files.pythonhosted.org/packages/bd/6e/61ebf08d8940553afff20d1fb1ba7294b6f8d279df9fd0c0db911b4bbcfd/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171", size = 21091 }, - { url = "https://files.pythonhosted.org/packages/11/23/ffbf53694e8c94ebd1e7e491de185124277964344733c45481f32ede2499/MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50", size = 15065 }, - { url = "https://files.pythonhosted.org/packages/44/06/e7175d06dd6e9172d4a69a72592cb3f7a996a9c396eee29082826449bbc3/MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a", size = 15514 }, - { url = "https://files.pythonhosted.org/packages/6b/28/bbf83e3f76936960b850435576dd5e67034e200469571be53f69174a2dfd/MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", size = 14353 }, - { url = "https://files.pythonhosted.org/packages/6c/30/316d194b093cde57d448a4c3209f22e3046c5bb2fb0820b118292b334be7/MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", size = 12392 }, - { url = "https://files.pythonhosted.org/packages/f2/96/9cdafba8445d3a53cae530aaf83c38ec64c4d5427d975c974084af5bc5d2/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", size = 23984 }, - { url = "https://files.pythonhosted.org/packages/f1/a4/aefb044a2cd8d7334c8a47d3fb2c9f328ac48cb349468cc31c20b539305f/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", size = 23120 }, - { url = "https://files.pythonhosted.org/packages/8d/21/5e4851379f88f3fad1de30361db501300d4f07bcad047d3cb0449fc51f8c/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", size = 23032 }, - { url = "https://files.pythonhosted.org/packages/00/7b/e92c64e079b2d0d7ddf69899c98842f3f9a60a1ae72657c89ce2655c999d/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", size = 24057 }, - { url = "https://files.pythonhosted.org/packages/f9/ac/46f960ca323037caa0a10662ef97d0a4728e890334fc156b9f9e52bcc4ca/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", size = 23359 }, - { url = "https://files.pythonhosted.org/packages/69/84/83439e16197337b8b14b6a5b9c2105fff81d42c2a7c5b58ac7b62ee2c3b1/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", size = 23306 }, - { url = "https://files.pythonhosted.org/packages/9a/34/a15aa69f01e2181ed8d2b685c0d2f6655d5cca2c4db0ddea775e631918cd/MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", size = 15094 }, - { url = "https://files.pythonhosted.org/packages/da/b8/3a3bd761922d416f3dc5d00bfbed11f66b1ab89a0c2b6e887240a30b0f6b/MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", size = 15521 }, - { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274 }, - { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348 }, - { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149 }, - { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118 }, - { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993 }, - { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178 }, - { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319 }, - { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352 }, - { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097 }, - { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601 }, - { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274 }, - { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352 }, - { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122 }, - { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085 }, - { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978 }, - { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208 }, - { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357 }, - { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344 }, - { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101 }, - { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603 }, - { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510 }, - { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486 }, - { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480 }, - { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914 }, - { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796 }, - { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473 }, - { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114 }, - { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098 }, - { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208 }, - { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739 }, - { url = "https://files.pythonhosted.org/packages/a7/ea/9b1530c3fdeeca613faeb0fb5cbcf2389d816072fab72a71b45749ef6062/MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a", size = 14344 }, - { url = "https://files.pythonhosted.org/packages/4b/c2/fbdbfe48848e7112ab05e627e718e854d20192b674952d9042ebd8c9e5de/MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff", size = 12389 }, - { url = "https://files.pythonhosted.org/packages/f0/25/7a7c6e4dbd4f867d95d94ca15449e91e52856f6ed1905d58ef1de5e211d0/MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13", size = 21607 }, - { url = "https://files.pythonhosted.org/packages/53/8f/f339c98a178f3c1e545622206b40986a4c3307fe39f70ccd3d9df9a9e425/MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144", size = 20728 }, - { url = "https://files.pythonhosted.org/packages/1a/03/8496a1a78308456dbd50b23a385c69b41f2e9661c67ea1329849a598a8f9/MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29", size = 20826 }, - { url = "https://files.pythonhosted.org/packages/e6/cf/0a490a4bd363048c3022f2f475c8c05582179bb179defcee4766fb3dcc18/MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0", size = 21843 }, - { url = "https://files.pythonhosted.org/packages/19/a3/34187a78613920dfd3cdf68ef6ce5e99c4f3417f035694074beb8848cd77/MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0", size = 21219 }, - { url = "https://files.pythonhosted.org/packages/17/d8/5811082f85bb88410ad7e452263af048d685669bbbfb7b595e8689152498/MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178", size = 20946 }, - { url = "https://files.pythonhosted.org/packages/7c/31/bd635fb5989440d9365c5e3c47556cfea121c7803f5034ac843e8f37c2f2/MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f", size = 15063 }, - { url = "https://files.pythonhosted.org/packages/b3/73/085399401383ce949f727afec55ec3abd76648d04b9f22e1c0e99cb4bec3/MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a", size = 15506 }, + { url = "https://files.pythonhosted.org/packages/04/90/d08277ce111dd22f77149fd1a5d4653eeb3b3eaacbdfcbae5afb2600eebd/MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8", size = 14357, upload_time = "2024-10-18T15:20:51.44Z" }, + { url = "https://files.pythonhosted.org/packages/04/e1/6e2194baeae0bca1fae6629dc0cbbb968d4d941469cbab11a3872edff374/MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158", size = 12393, upload_time = "2024-10-18T15:20:52.426Z" }, + { url = "https://files.pythonhosted.org/packages/1d/69/35fa85a8ece0a437493dc61ce0bb6d459dcba482c34197e3efc829aa357f/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579", size = 21732, upload_time = "2024-10-18T15:20:53.578Z" }, + { url = "https://files.pythonhosted.org/packages/22/35/137da042dfb4720b638d2937c38a9c2df83fe32d20e8c8f3185dbfef05f7/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d", size = 20866, upload_time = "2024-10-18T15:20:55.06Z" }, + { url = "https://files.pythonhosted.org/packages/29/28/6d029a903727a1b62edb51863232152fd335d602def598dade38996887f0/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb", size = 20964, upload_time = "2024-10-18T15:20:55.906Z" }, + { url = "https://files.pythonhosted.org/packages/cc/cd/07438f95f83e8bc028279909d9c9bd39e24149b0d60053a97b2bc4f8aa51/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b", size = 21977, upload_time = "2024-10-18T15:20:57.189Z" }, + { url = "https://files.pythonhosted.org/packages/29/01/84b57395b4cc062f9c4c55ce0df7d3108ca32397299d9df00fedd9117d3d/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c", size = 21366, upload_time = "2024-10-18T15:20:58.235Z" }, + { url = "https://files.pythonhosted.org/packages/bd/6e/61ebf08d8940553afff20d1fb1ba7294b6f8d279df9fd0c0db911b4bbcfd/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171", size = 21091, upload_time = "2024-10-18T15:20:59.235Z" }, + { url = "https://files.pythonhosted.org/packages/11/23/ffbf53694e8c94ebd1e7e491de185124277964344733c45481f32ede2499/MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50", size = 15065, upload_time = "2024-10-18T15:21:00.307Z" }, + { url = "https://files.pythonhosted.org/packages/44/06/e7175d06dd6e9172d4a69a72592cb3f7a996a9c396eee29082826449bbc3/MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a", size = 15514, upload_time = "2024-10-18T15:21:01.122Z" }, + { url = "https://files.pythonhosted.org/packages/6b/28/bbf83e3f76936960b850435576dd5e67034e200469571be53f69174a2dfd/MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", size = 14353, upload_time = "2024-10-18T15:21:02.187Z" }, + { url = "https://files.pythonhosted.org/packages/6c/30/316d194b093cde57d448a4c3209f22e3046c5bb2fb0820b118292b334be7/MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", size = 12392, upload_time = "2024-10-18T15:21:02.941Z" }, + { url = "https://files.pythonhosted.org/packages/f2/96/9cdafba8445d3a53cae530aaf83c38ec64c4d5427d975c974084af5bc5d2/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", size = 23984, upload_time = "2024-10-18T15:21:03.953Z" }, + { url = "https://files.pythonhosted.org/packages/f1/a4/aefb044a2cd8d7334c8a47d3fb2c9f328ac48cb349468cc31c20b539305f/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", size = 23120, upload_time = "2024-10-18T15:21:06.495Z" }, + { url = "https://files.pythonhosted.org/packages/8d/21/5e4851379f88f3fad1de30361db501300d4f07bcad047d3cb0449fc51f8c/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", size = 23032, upload_time = "2024-10-18T15:21:07.295Z" }, + { url = "https://files.pythonhosted.org/packages/00/7b/e92c64e079b2d0d7ddf69899c98842f3f9a60a1ae72657c89ce2655c999d/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", size = 24057, upload_time = "2024-10-18T15:21:08.073Z" }, + { url = "https://files.pythonhosted.org/packages/f9/ac/46f960ca323037caa0a10662ef97d0a4728e890334fc156b9f9e52bcc4ca/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", size = 23359, upload_time = "2024-10-18T15:21:09.318Z" }, + { url = "https://files.pythonhosted.org/packages/69/84/83439e16197337b8b14b6a5b9c2105fff81d42c2a7c5b58ac7b62ee2c3b1/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", size = 23306, upload_time = "2024-10-18T15:21:10.185Z" }, + { url = "https://files.pythonhosted.org/packages/9a/34/a15aa69f01e2181ed8d2b685c0d2f6655d5cca2c4db0ddea775e631918cd/MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", size = 15094, upload_time = "2024-10-18T15:21:11.005Z" }, + { url = "https://files.pythonhosted.org/packages/da/b8/3a3bd761922d416f3dc5d00bfbed11f66b1ab89a0c2b6e887240a30b0f6b/MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", size = 15521, upload_time = "2024-10-18T15:21:12.911Z" }, + { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274, upload_time = "2024-10-18T15:21:13.777Z" }, + { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348, upload_time = "2024-10-18T15:21:14.822Z" }, + { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149, upload_time = "2024-10-18T15:21:15.642Z" }, + { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118, upload_time = "2024-10-18T15:21:17.133Z" }, + { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993, upload_time = "2024-10-18T15:21:18.064Z" }, + { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178, upload_time = "2024-10-18T15:21:18.859Z" }, + { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319, upload_time = "2024-10-18T15:21:19.671Z" }, + { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352, upload_time = "2024-10-18T15:21:20.971Z" }, + { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097, upload_time = "2024-10-18T15:21:22.646Z" }, + { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601, upload_time = "2024-10-18T15:21:23.499Z" }, + { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274, upload_time = "2024-10-18T15:21:24.577Z" }, + { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352, upload_time = "2024-10-18T15:21:25.382Z" }, + { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122, upload_time = "2024-10-18T15:21:26.199Z" }, + { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085, upload_time = "2024-10-18T15:21:27.029Z" }, + { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978, upload_time = "2024-10-18T15:21:27.846Z" }, + { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208, upload_time = "2024-10-18T15:21:28.744Z" }, + { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357, upload_time = "2024-10-18T15:21:29.545Z" }, + { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344, upload_time = "2024-10-18T15:21:30.366Z" }, + { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101, upload_time = "2024-10-18T15:21:31.207Z" }, + { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603, upload_time = "2024-10-18T15:21:32.032Z" }, + { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510, upload_time = "2024-10-18T15:21:33.625Z" }, + { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486, upload_time = "2024-10-18T15:21:34.611Z" }, + { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480, upload_time = "2024-10-18T15:21:35.398Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914, upload_time = "2024-10-18T15:21:36.231Z" }, + { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796, upload_time = "2024-10-18T15:21:37.073Z" }, + { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473, upload_time = "2024-10-18T15:21:37.932Z" }, + { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114, upload_time = "2024-10-18T15:21:39.799Z" }, + { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098, upload_time = "2024-10-18T15:21:40.813Z" }, + { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208, upload_time = "2024-10-18T15:21:41.814Z" }, + { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739, upload_time = "2024-10-18T15:21:42.784Z" }, + { url = "https://files.pythonhosted.org/packages/a7/ea/9b1530c3fdeeca613faeb0fb5cbcf2389d816072fab72a71b45749ef6062/MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a", size = 14344, upload_time = "2024-10-18T15:21:43.721Z" }, + { url = "https://files.pythonhosted.org/packages/4b/c2/fbdbfe48848e7112ab05e627e718e854d20192b674952d9042ebd8c9e5de/MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff", size = 12389, upload_time = "2024-10-18T15:21:44.666Z" }, + { url = "https://files.pythonhosted.org/packages/f0/25/7a7c6e4dbd4f867d95d94ca15449e91e52856f6ed1905d58ef1de5e211d0/MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13", size = 21607, upload_time = "2024-10-18T15:21:45.452Z" }, + { url = "https://files.pythonhosted.org/packages/53/8f/f339c98a178f3c1e545622206b40986a4c3307fe39f70ccd3d9df9a9e425/MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144", size = 20728, upload_time = "2024-10-18T15:21:46.295Z" }, + { url = "https://files.pythonhosted.org/packages/1a/03/8496a1a78308456dbd50b23a385c69b41f2e9661c67ea1329849a598a8f9/MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29", size = 20826, upload_time = "2024-10-18T15:21:47.134Z" }, + { url = "https://files.pythonhosted.org/packages/e6/cf/0a490a4bd363048c3022f2f475c8c05582179bb179defcee4766fb3dcc18/MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0", size = 21843, upload_time = "2024-10-18T15:21:48.334Z" }, + { url = "https://files.pythonhosted.org/packages/19/a3/34187a78613920dfd3cdf68ef6ce5e99c4f3417f035694074beb8848cd77/MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0", size = 21219, upload_time = "2024-10-18T15:21:49.587Z" }, + { url = "https://files.pythonhosted.org/packages/17/d8/5811082f85bb88410ad7e452263af048d685669bbbfb7b595e8689152498/MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178", size = 20946, upload_time = "2024-10-18T15:21:50.441Z" }, + { url = "https://files.pythonhosted.org/packages/7c/31/bd635fb5989440d9365c5e3c47556cfea121c7803f5034ac843e8f37c2f2/MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f", size = 15063, upload_time = "2024-10-18T15:21:51.385Z" }, + { url = "https://files.pythonhosted.org/packages/b3/73/085399401383ce949f727afec55ec3abd76648d04b9f22e1c0e99cb4bec3/MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a", size = 15506, upload_time = "2024-10-18T15:21:52.974Z" }, ] [[package]] name = "mdurl" version = "0.1.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729 } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload_time = "2022-08-14T12:40:10.846Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 }, + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload_time = "2022-08-14T12:40:09.779Z" }, ] [[package]] name = "nanoid" version = "2.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b7/9d/0250bf5935d88e214df469d35eccc0f6ff7e9db046fc8a9aeb4b2a192775/nanoid-2.0.0.tar.gz", hash = "sha256:5a80cad5e9c6e9ae3a41fa2fb34ae189f7cb420b2a5d8f82bd9d23466e4efa68", size = 3290 } +sdist = { url = "https://files.pythonhosted.org/packages/b7/9d/0250bf5935d88e214df469d35eccc0f6ff7e9db046fc8a9aeb4b2a192775/nanoid-2.0.0.tar.gz", hash = "sha256:5a80cad5e9c6e9ae3a41fa2fb34ae189f7cb420b2a5d8f82bd9d23466e4efa68", size = 3290, upload_time = "2018-11-20T14:45:51.578Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/0d/8630f13998638dc01e187fadd2e5c6d42d127d08aeb4943d231664d6e539/nanoid-2.0.0-py3-none-any.whl", hash = "sha256:90aefa650e328cffb0893bbd4c236cfd44c48bc1f2d0b525ecc53c3187b653bb", size = 5844 }, + { url = "https://files.pythonhosted.org/packages/2e/0d/8630f13998638dc01e187fadd2e5c6d42d127d08aeb4943d231664d6e539/nanoid-2.0.0-py3-none-any.whl", hash = "sha256:90aefa650e328cffb0893bbd4c236cfd44c48bc1f2d0b525ecc53c3187b653bb", size = 5844, upload_time = "2018-11-20T14:45:50.165Z" }, ] [[package]] @@ -853,52 +855,52 @@ source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version < '3.10'", ] -sdist = { url = "https://files.pythonhosted.org/packages/a9/75/10dd1f8116a8b796cb2c737b674e02d02e80454bda953fa7e65d8c12b016/numpy-2.0.2.tar.gz", hash = "sha256:883c987dee1880e2a864ab0dc9892292582510604156762362d9326444636e78", size = 18902015 } +sdist = { url = "https://files.pythonhosted.org/packages/a9/75/10dd1f8116a8b796cb2c737b674e02d02e80454bda953fa7e65d8c12b016/numpy-2.0.2.tar.gz", hash = "sha256:883c987dee1880e2a864ab0dc9892292582510604156762362d9326444636e78", size = 18902015, upload_time = "2024-08-26T20:19:40.945Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/21/91/3495b3237510f79f5d81f2508f9f13fea78ebfdf07538fc7444badda173d/numpy-2.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:51129a29dbe56f9ca83438b706e2e69a39892b5eda6cedcb6b0c9fdc9b0d3ece", size = 21165245 }, - { url = "https://files.pythonhosted.org/packages/05/33/26178c7d437a87082d11019292dce6d3fe6f0e9026b7b2309cbf3e489b1d/numpy-2.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f15975dfec0cf2239224d80e32c3170b1d168335eaedee69da84fbe9f1f9cd04", size = 13738540 }, - { url = "https://files.pythonhosted.org/packages/ec/31/cc46e13bf07644efc7a4bf68df2df5fb2a1a88d0cd0da9ddc84dc0033e51/numpy-2.0.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:8c5713284ce4e282544c68d1c3b2c7161d38c256d2eefc93c1d683cf47683e66", size = 5300623 }, - { url = "https://files.pythonhosted.org/packages/6e/16/7bfcebf27bb4f9d7ec67332ffebee4d1bf085c84246552d52dbb548600e7/numpy-2.0.2-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:becfae3ddd30736fe1889a37f1f580e245ba79a5855bff5f2a29cb3ccc22dd7b", size = 6901774 }, - { url = "https://files.pythonhosted.org/packages/f9/a3/561c531c0e8bf082c5bef509d00d56f82e0ea7e1e3e3a7fc8fa78742a6e5/numpy-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2da5960c3cf0df7eafefd806d4e612c5e19358de82cb3c343631188991566ccd", size = 13907081 }, - { url = "https://files.pythonhosted.org/packages/fa/66/f7177ab331876200ac7563a580140643d1179c8b4b6a6b0fc9838de2a9b8/numpy-2.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:496f71341824ed9f3d2fd36cf3ac57ae2e0165c143b55c3a035ee219413f3318", size = 19523451 }, - { url = "https://files.pythonhosted.org/packages/25/7f/0b209498009ad6453e4efc2c65bcdf0ae08a182b2b7877d7ab38a92dc542/numpy-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a61ec659f68ae254e4d237816e33171497e978140353c0c2038d46e63282d0c8", size = 19927572 }, - { url = "https://files.pythonhosted.org/packages/3e/df/2619393b1e1b565cd2d4c4403bdd979621e2c4dea1f8532754b2598ed63b/numpy-2.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d731a1c6116ba289c1e9ee714b08a8ff882944d4ad631fd411106a30f083c326", size = 14400722 }, - { url = "https://files.pythonhosted.org/packages/22/ad/77e921b9f256d5da36424ffb711ae79ca3f451ff8489eeca544d0701d74a/numpy-2.0.2-cp310-cp310-win32.whl", hash = "sha256:984d96121c9f9616cd33fbd0618b7f08e0cfc9600a7ee1d6fd9b239186d19d97", size = 6472170 }, - { url = "https://files.pythonhosted.org/packages/10/05/3442317535028bc29cf0c0dd4c191a4481e8376e9f0db6bcf29703cadae6/numpy-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:c7b0be4ef08607dd04da4092faee0b86607f111d5ae68036f16cc787e250a131", size = 15905558 }, - { url = "https://files.pythonhosted.org/packages/8b/cf/034500fb83041aa0286e0fb16e7c76e5c8b67c0711bb6e9e9737a717d5fe/numpy-2.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:49ca4decb342d66018b01932139c0961a8f9ddc7589611158cb3c27cbcf76448", size = 21169137 }, - { url = "https://files.pythonhosted.org/packages/4a/d9/32de45561811a4b87fbdee23b5797394e3d1504b4a7cf40c10199848893e/numpy-2.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:11a76c372d1d37437857280aa142086476136a8c0f373b2e648ab2c8f18fb195", size = 13703552 }, - { url = "https://files.pythonhosted.org/packages/c1/ca/2f384720020c7b244d22508cb7ab23d95f179fcfff33c31a6eeba8d6c512/numpy-2.0.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:807ec44583fd708a21d4a11d94aedf2f4f3c3719035c76a2bbe1fe8e217bdc57", size = 5298957 }, - { url = "https://files.pythonhosted.org/packages/0e/78/a3e4f9fb6aa4e6fdca0c5428e8ba039408514388cf62d89651aade838269/numpy-2.0.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8cafab480740e22f8d833acefed5cc87ce276f4ece12fdaa2e8903db2f82897a", size = 6905573 }, - { url = "https://files.pythonhosted.org/packages/a0/72/cfc3a1beb2caf4efc9d0b38a15fe34025230da27e1c08cc2eb9bfb1c7231/numpy-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a15f476a45e6e5a3a79d8a14e62161d27ad897381fecfa4a09ed5322f2085669", size = 13914330 }, - { url = "https://files.pythonhosted.org/packages/ba/a8/c17acf65a931ce551fee11b72e8de63bf7e8a6f0e21add4c937c83563538/numpy-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13e689d772146140a252c3a28501da66dfecd77490b498b168b501835041f951", size = 19534895 }, - { url = "https://files.pythonhosted.org/packages/ba/86/8767f3d54f6ae0165749f84648da9dcc8cd78ab65d415494962c86fac80f/numpy-2.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9ea91dfb7c3d1c56a0e55657c0afb38cf1eeae4544c208dc465c3c9f3a7c09f9", size = 19937253 }, - { url = "https://files.pythonhosted.org/packages/df/87/f76450e6e1c14e5bb1eae6836478b1028e096fd02e85c1c37674606ab752/numpy-2.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c1c9307701fec8f3f7a1e6711f9089c06e6284b3afbbcd259f7791282d660a15", size = 14414074 }, - { url = "https://files.pythonhosted.org/packages/5c/ca/0f0f328e1e59f73754f06e1adfb909de43726d4f24c6a3f8805f34f2b0fa/numpy-2.0.2-cp311-cp311-win32.whl", hash = "sha256:a392a68bd329eafac5817e5aefeb39038c48b671afd242710b451e76090e81f4", size = 6470640 }, - { url = "https://files.pythonhosted.org/packages/eb/57/3a3f14d3a759dcf9bf6e9eda905794726b758819df4663f217d658a58695/numpy-2.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:286cd40ce2b7d652a6f22efdfc6d1edf879440e53e76a75955bc0c826c7e64dc", size = 15910230 }, - { url = "https://files.pythonhosted.org/packages/45/40/2e117be60ec50d98fa08c2f8c48e09b3edea93cfcabd5a9ff6925d54b1c2/numpy-2.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:df55d490dea7934f330006d0f81e8551ba6010a5bf035a249ef61a94f21c500b", size = 20895803 }, - { url = "https://files.pythonhosted.org/packages/46/92/1b8b8dee833f53cef3e0a3f69b2374467789e0bb7399689582314df02651/numpy-2.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8df823f570d9adf0978347d1f926b2a867d5608f434a7cff7f7908c6570dcf5e", size = 13471835 }, - { url = "https://files.pythonhosted.org/packages/7f/19/e2793bde475f1edaea6945be141aef6c8b4c669b90c90a300a8954d08f0a/numpy-2.0.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9a92ae5c14811e390f3767053ff54eaee3bf84576d99a2456391401323f4ec2c", size = 5038499 }, - { url = "https://files.pythonhosted.org/packages/e3/ff/ddf6dac2ff0dd50a7327bcdba45cb0264d0e96bb44d33324853f781a8f3c/numpy-2.0.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:a842d573724391493a97a62ebbb8e731f8a5dcc5d285dfc99141ca15a3302d0c", size = 6633497 }, - { url = "https://files.pythonhosted.org/packages/72/21/67f36eac8e2d2cd652a2e69595a54128297cdcb1ff3931cfc87838874bd4/numpy-2.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c05e238064fc0610c840d1cf6a13bf63d7e391717d247f1bf0318172e759e692", size = 13621158 }, - { url = "https://files.pythonhosted.org/packages/39/68/e9f1126d757653496dbc096cb429014347a36b228f5a991dae2c6b6cfd40/numpy-2.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0123ffdaa88fa4ab64835dcbde75dcdf89c453c922f18dced6e27c90d1d0ec5a", size = 19236173 }, - { url = "https://files.pythonhosted.org/packages/d1/e9/1f5333281e4ebf483ba1c888b1d61ba7e78d7e910fdd8e6499667041cc35/numpy-2.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:96a55f64139912d61de9137f11bf39a55ec8faec288c75a54f93dfd39f7eb40c", size = 19634174 }, - { url = "https://files.pythonhosted.org/packages/71/af/a469674070c8d8408384e3012e064299f7a2de540738a8e414dcfd639996/numpy-2.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ec9852fb39354b5a45a80bdab5ac02dd02b15f44b3804e9f00c556bf24b4bded", size = 14099701 }, - { url = "https://files.pythonhosted.org/packages/d0/3d/08ea9f239d0e0e939b6ca52ad403c84a2bce1bde301a8eb4888c1c1543f1/numpy-2.0.2-cp312-cp312-win32.whl", hash = "sha256:671bec6496f83202ed2d3c8fdc486a8fc86942f2e69ff0e986140339a63bcbe5", size = 6174313 }, - { url = "https://files.pythonhosted.org/packages/b2/b5/4ac39baebf1fdb2e72585c8352c56d063b6126be9fc95bd2bb5ef5770c20/numpy-2.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:cfd41e13fdc257aa5778496b8caa5e856dc4896d4ccf01841daee1d96465467a", size = 15606179 }, - { url = "https://files.pythonhosted.org/packages/43/c1/41c8f6df3162b0c6ffd4437d729115704bd43363de0090c7f913cfbc2d89/numpy-2.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9059e10581ce4093f735ed23f3b9d283b9d517ff46009ddd485f1747eb22653c", size = 21169942 }, - { url = "https://files.pythonhosted.org/packages/39/bc/fd298f308dcd232b56a4031fd6ddf11c43f9917fbc937e53762f7b5a3bb1/numpy-2.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:423e89b23490805d2a5a96fe40ec507407b8ee786d66f7328be214f9679df6dd", size = 13711512 }, - { url = "https://files.pythonhosted.org/packages/96/ff/06d1aa3eeb1c614eda245c1ba4fb88c483bee6520d361641331872ac4b82/numpy-2.0.2-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:2b2955fa6f11907cf7a70dab0d0755159bca87755e831e47932367fc8f2f2d0b", size = 5306976 }, - { url = "https://files.pythonhosted.org/packages/2d/98/121996dcfb10a6087a05e54453e28e58694a7db62c5a5a29cee14c6e047b/numpy-2.0.2-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:97032a27bd9d8988b9a97a8c4d2c9f2c15a81f61e2f21404d7e8ef00cb5be729", size = 6906494 }, - { url = "https://files.pythonhosted.org/packages/15/31/9dffc70da6b9bbf7968f6551967fc21156207366272c2a40b4ed6008dc9b/numpy-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e795a8be3ddbac43274f18588329c72939870a16cae810c2b73461c40718ab1", size = 13912596 }, - { url = "https://files.pythonhosted.org/packages/b9/14/78635daab4b07c0930c919d451b8bf8c164774e6a3413aed04a6d95758ce/numpy-2.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26b258c385842546006213344c50655ff1555a9338e2e5e02a0756dc3e803dd", size = 19526099 }, - { url = "https://files.pythonhosted.org/packages/26/4c/0eeca4614003077f68bfe7aac8b7496f04221865b3a5e7cb230c9d055afd/numpy-2.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fec9451a7789926bcf7c2b8d187292c9f93ea30284802a0ab3f5be8ab36865d", size = 19932823 }, - { url = "https://files.pythonhosted.org/packages/f1/46/ea25b98b13dccaebddf1a803f8c748680d972e00507cd9bc6dcdb5aa2ac1/numpy-2.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9189427407d88ff25ecf8f12469d4d39d35bee1db5d39fc5c168c6f088a6956d", size = 14404424 }, - { url = "https://files.pythonhosted.org/packages/c8/a6/177dd88d95ecf07e722d21008b1b40e681a929eb9e329684d449c36586b2/numpy-2.0.2-cp39-cp39-win32.whl", hash = "sha256:905d16e0c60200656500c95b6b8dca5d109e23cb24abc701d41c02d74c6b3afa", size = 6476809 }, - { url = "https://files.pythonhosted.org/packages/ea/2b/7fc9f4e7ae5b507c1a3a21f0f15ed03e794c1242ea8a242ac158beb56034/numpy-2.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:a3f4ab0caa7f053f6797fcd4e1e25caee367db3112ef2b6ef82d749530768c73", size = 15911314 }, - { url = "https://files.pythonhosted.org/packages/8f/3b/df5a870ac6a3be3a86856ce195ef42eec7ae50d2a202be1f5a4b3b340e14/numpy-2.0.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7f0a0c6f12e07fa94133c8a67404322845220c06a9e80e85999afe727f7438b8", size = 21025288 }, - { url = "https://files.pythonhosted.org/packages/2c/97/51af92f18d6f6f2d9ad8b482a99fb74e142d71372da5d834b3a2747a446e/numpy-2.0.2-pp39-pypy39_pp73-macosx_14_0_x86_64.whl", hash = "sha256:312950fdd060354350ed123c0e25a71327d3711584beaef30cdaa93320c392d4", size = 6762793 }, - { url = "https://files.pythonhosted.org/packages/12/46/de1fbd0c1b5ccaa7f9a005b66761533e2f6a3e560096682683a223631fe9/numpy-2.0.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26df23238872200f63518dd2aa984cfca675d82469535dc7162dc2ee52d9dd5c", size = 19334885 }, - { url = "https://files.pythonhosted.org/packages/cc/dc/d330a6faefd92b446ec0f0dfea4c3207bb1fef3c4771d19cf4543efd2c78/numpy-2.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a46288ec55ebbd58947d31d72be2c63cbf839f0a63b49cb755022310792a3385", size = 15828784 }, + { url = "https://files.pythonhosted.org/packages/21/91/3495b3237510f79f5d81f2508f9f13fea78ebfdf07538fc7444badda173d/numpy-2.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:51129a29dbe56f9ca83438b706e2e69a39892b5eda6cedcb6b0c9fdc9b0d3ece", size = 21165245, upload_time = "2024-08-26T20:04:14.625Z" }, + { url = "https://files.pythonhosted.org/packages/05/33/26178c7d437a87082d11019292dce6d3fe6f0e9026b7b2309cbf3e489b1d/numpy-2.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f15975dfec0cf2239224d80e32c3170b1d168335eaedee69da84fbe9f1f9cd04", size = 13738540, upload_time = "2024-08-26T20:04:36.784Z" }, + { url = "https://files.pythonhosted.org/packages/ec/31/cc46e13bf07644efc7a4bf68df2df5fb2a1a88d0cd0da9ddc84dc0033e51/numpy-2.0.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:8c5713284ce4e282544c68d1c3b2c7161d38c256d2eefc93c1d683cf47683e66", size = 5300623, upload_time = "2024-08-26T20:04:46.491Z" }, + { url = "https://files.pythonhosted.org/packages/6e/16/7bfcebf27bb4f9d7ec67332ffebee4d1bf085c84246552d52dbb548600e7/numpy-2.0.2-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:becfae3ddd30736fe1889a37f1f580e245ba79a5855bff5f2a29cb3ccc22dd7b", size = 6901774, upload_time = "2024-08-26T20:04:58.173Z" }, + { url = "https://files.pythonhosted.org/packages/f9/a3/561c531c0e8bf082c5bef509d00d56f82e0ea7e1e3e3a7fc8fa78742a6e5/numpy-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2da5960c3cf0df7eafefd806d4e612c5e19358de82cb3c343631188991566ccd", size = 13907081, upload_time = "2024-08-26T20:05:19.098Z" }, + { url = "https://files.pythonhosted.org/packages/fa/66/f7177ab331876200ac7563a580140643d1179c8b4b6a6b0fc9838de2a9b8/numpy-2.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:496f71341824ed9f3d2fd36cf3ac57ae2e0165c143b55c3a035ee219413f3318", size = 19523451, upload_time = "2024-08-26T20:05:47.479Z" }, + { url = "https://files.pythonhosted.org/packages/25/7f/0b209498009ad6453e4efc2c65bcdf0ae08a182b2b7877d7ab38a92dc542/numpy-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a61ec659f68ae254e4d237816e33171497e978140353c0c2038d46e63282d0c8", size = 19927572, upload_time = "2024-08-26T20:06:17.137Z" }, + { url = "https://files.pythonhosted.org/packages/3e/df/2619393b1e1b565cd2d4c4403bdd979621e2c4dea1f8532754b2598ed63b/numpy-2.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d731a1c6116ba289c1e9ee714b08a8ff882944d4ad631fd411106a30f083c326", size = 14400722, upload_time = "2024-08-26T20:06:39.16Z" }, + { url = "https://files.pythonhosted.org/packages/22/ad/77e921b9f256d5da36424ffb711ae79ca3f451ff8489eeca544d0701d74a/numpy-2.0.2-cp310-cp310-win32.whl", hash = "sha256:984d96121c9f9616cd33fbd0618b7f08e0cfc9600a7ee1d6fd9b239186d19d97", size = 6472170, upload_time = "2024-08-26T20:06:50.361Z" }, + { url = "https://files.pythonhosted.org/packages/10/05/3442317535028bc29cf0c0dd4c191a4481e8376e9f0db6bcf29703cadae6/numpy-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:c7b0be4ef08607dd04da4092faee0b86607f111d5ae68036f16cc787e250a131", size = 15905558, upload_time = "2024-08-26T20:07:13.881Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cf/034500fb83041aa0286e0fb16e7c76e5c8b67c0711bb6e9e9737a717d5fe/numpy-2.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:49ca4decb342d66018b01932139c0961a8f9ddc7589611158cb3c27cbcf76448", size = 21169137, upload_time = "2024-08-26T20:07:45.345Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d9/32de45561811a4b87fbdee23b5797394e3d1504b4a7cf40c10199848893e/numpy-2.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:11a76c372d1d37437857280aa142086476136a8c0f373b2e648ab2c8f18fb195", size = 13703552, upload_time = "2024-08-26T20:08:06.666Z" }, + { url = "https://files.pythonhosted.org/packages/c1/ca/2f384720020c7b244d22508cb7ab23d95f179fcfff33c31a6eeba8d6c512/numpy-2.0.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:807ec44583fd708a21d4a11d94aedf2f4f3c3719035c76a2bbe1fe8e217bdc57", size = 5298957, upload_time = "2024-08-26T20:08:15.83Z" }, + { url = "https://files.pythonhosted.org/packages/0e/78/a3e4f9fb6aa4e6fdca0c5428e8ba039408514388cf62d89651aade838269/numpy-2.0.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8cafab480740e22f8d833acefed5cc87ce276f4ece12fdaa2e8903db2f82897a", size = 6905573, upload_time = "2024-08-26T20:08:27.185Z" }, + { url = "https://files.pythonhosted.org/packages/a0/72/cfc3a1beb2caf4efc9d0b38a15fe34025230da27e1c08cc2eb9bfb1c7231/numpy-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a15f476a45e6e5a3a79d8a14e62161d27ad897381fecfa4a09ed5322f2085669", size = 13914330, upload_time = "2024-08-26T20:08:48.058Z" }, + { url = "https://files.pythonhosted.org/packages/ba/a8/c17acf65a931ce551fee11b72e8de63bf7e8a6f0e21add4c937c83563538/numpy-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13e689d772146140a252c3a28501da66dfecd77490b498b168b501835041f951", size = 19534895, upload_time = "2024-08-26T20:09:16.536Z" }, + { url = "https://files.pythonhosted.org/packages/ba/86/8767f3d54f6ae0165749f84648da9dcc8cd78ab65d415494962c86fac80f/numpy-2.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9ea91dfb7c3d1c56a0e55657c0afb38cf1eeae4544c208dc465c3c9f3a7c09f9", size = 19937253, upload_time = "2024-08-26T20:09:46.263Z" }, + { url = "https://files.pythonhosted.org/packages/df/87/f76450e6e1c14e5bb1eae6836478b1028e096fd02e85c1c37674606ab752/numpy-2.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c1c9307701fec8f3f7a1e6711f9089c06e6284b3afbbcd259f7791282d660a15", size = 14414074, upload_time = "2024-08-26T20:10:08.483Z" }, + { url = "https://files.pythonhosted.org/packages/5c/ca/0f0f328e1e59f73754f06e1adfb909de43726d4f24c6a3f8805f34f2b0fa/numpy-2.0.2-cp311-cp311-win32.whl", hash = "sha256:a392a68bd329eafac5817e5aefeb39038c48b671afd242710b451e76090e81f4", size = 6470640, upload_time = "2024-08-26T20:10:19.732Z" }, + { url = "https://files.pythonhosted.org/packages/eb/57/3a3f14d3a759dcf9bf6e9eda905794726b758819df4663f217d658a58695/numpy-2.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:286cd40ce2b7d652a6f22efdfc6d1edf879440e53e76a75955bc0c826c7e64dc", size = 15910230, upload_time = "2024-08-26T20:10:43.413Z" }, + { url = "https://files.pythonhosted.org/packages/45/40/2e117be60ec50d98fa08c2f8c48e09b3edea93cfcabd5a9ff6925d54b1c2/numpy-2.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:df55d490dea7934f330006d0f81e8551ba6010a5bf035a249ef61a94f21c500b", size = 20895803, upload_time = "2024-08-26T20:11:13.916Z" }, + { url = "https://files.pythonhosted.org/packages/46/92/1b8b8dee833f53cef3e0a3f69b2374467789e0bb7399689582314df02651/numpy-2.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8df823f570d9adf0978347d1f926b2a867d5608f434a7cff7f7908c6570dcf5e", size = 13471835, upload_time = "2024-08-26T20:11:34.779Z" }, + { url = "https://files.pythonhosted.org/packages/7f/19/e2793bde475f1edaea6945be141aef6c8b4c669b90c90a300a8954d08f0a/numpy-2.0.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9a92ae5c14811e390f3767053ff54eaee3bf84576d99a2456391401323f4ec2c", size = 5038499, upload_time = "2024-08-26T20:11:43.902Z" }, + { url = "https://files.pythonhosted.org/packages/e3/ff/ddf6dac2ff0dd50a7327bcdba45cb0264d0e96bb44d33324853f781a8f3c/numpy-2.0.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:a842d573724391493a97a62ebbb8e731f8a5dcc5d285dfc99141ca15a3302d0c", size = 6633497, upload_time = "2024-08-26T20:11:55.09Z" }, + { url = "https://files.pythonhosted.org/packages/72/21/67f36eac8e2d2cd652a2e69595a54128297cdcb1ff3931cfc87838874bd4/numpy-2.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c05e238064fc0610c840d1cf6a13bf63d7e391717d247f1bf0318172e759e692", size = 13621158, upload_time = "2024-08-26T20:12:14.95Z" }, + { url = "https://files.pythonhosted.org/packages/39/68/e9f1126d757653496dbc096cb429014347a36b228f5a991dae2c6b6cfd40/numpy-2.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0123ffdaa88fa4ab64835dcbde75dcdf89c453c922f18dced6e27c90d1d0ec5a", size = 19236173, upload_time = "2024-08-26T20:12:44.049Z" }, + { url = "https://files.pythonhosted.org/packages/d1/e9/1f5333281e4ebf483ba1c888b1d61ba7e78d7e910fdd8e6499667041cc35/numpy-2.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:96a55f64139912d61de9137f11bf39a55ec8faec288c75a54f93dfd39f7eb40c", size = 19634174, upload_time = "2024-08-26T20:13:13.634Z" }, + { url = "https://files.pythonhosted.org/packages/71/af/a469674070c8d8408384e3012e064299f7a2de540738a8e414dcfd639996/numpy-2.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ec9852fb39354b5a45a80bdab5ac02dd02b15f44b3804e9f00c556bf24b4bded", size = 14099701, upload_time = "2024-08-26T20:13:34.851Z" }, + { url = "https://files.pythonhosted.org/packages/d0/3d/08ea9f239d0e0e939b6ca52ad403c84a2bce1bde301a8eb4888c1c1543f1/numpy-2.0.2-cp312-cp312-win32.whl", hash = "sha256:671bec6496f83202ed2d3c8fdc486a8fc86942f2e69ff0e986140339a63bcbe5", size = 6174313, upload_time = "2024-08-26T20:13:45.653Z" }, + { url = "https://files.pythonhosted.org/packages/b2/b5/4ac39baebf1fdb2e72585c8352c56d063b6126be9fc95bd2bb5ef5770c20/numpy-2.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:cfd41e13fdc257aa5778496b8caa5e856dc4896d4ccf01841daee1d96465467a", size = 15606179, upload_time = "2024-08-26T20:14:08.786Z" }, + { url = "https://files.pythonhosted.org/packages/43/c1/41c8f6df3162b0c6ffd4437d729115704bd43363de0090c7f913cfbc2d89/numpy-2.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9059e10581ce4093f735ed23f3b9d283b9d517ff46009ddd485f1747eb22653c", size = 21169942, upload_time = "2024-08-26T20:14:40.108Z" }, + { url = "https://files.pythonhosted.org/packages/39/bc/fd298f308dcd232b56a4031fd6ddf11c43f9917fbc937e53762f7b5a3bb1/numpy-2.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:423e89b23490805d2a5a96fe40ec507407b8ee786d66f7328be214f9679df6dd", size = 13711512, upload_time = "2024-08-26T20:15:00.985Z" }, + { url = "https://files.pythonhosted.org/packages/96/ff/06d1aa3eeb1c614eda245c1ba4fb88c483bee6520d361641331872ac4b82/numpy-2.0.2-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:2b2955fa6f11907cf7a70dab0d0755159bca87755e831e47932367fc8f2f2d0b", size = 5306976, upload_time = "2024-08-26T20:15:10.876Z" }, + { url = "https://files.pythonhosted.org/packages/2d/98/121996dcfb10a6087a05e54453e28e58694a7db62c5a5a29cee14c6e047b/numpy-2.0.2-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:97032a27bd9d8988b9a97a8c4d2c9f2c15a81f61e2f21404d7e8ef00cb5be729", size = 6906494, upload_time = "2024-08-26T20:15:22.055Z" }, + { url = "https://files.pythonhosted.org/packages/15/31/9dffc70da6b9bbf7968f6551967fc21156207366272c2a40b4ed6008dc9b/numpy-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e795a8be3ddbac43274f18588329c72939870a16cae810c2b73461c40718ab1", size = 13912596, upload_time = "2024-08-26T20:15:42.452Z" }, + { url = "https://files.pythonhosted.org/packages/b9/14/78635daab4b07c0930c919d451b8bf8c164774e6a3413aed04a6d95758ce/numpy-2.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26b258c385842546006213344c50655ff1555a9338e2e5e02a0756dc3e803dd", size = 19526099, upload_time = "2024-08-26T20:16:11.048Z" }, + { url = "https://files.pythonhosted.org/packages/26/4c/0eeca4614003077f68bfe7aac8b7496f04221865b3a5e7cb230c9d055afd/numpy-2.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fec9451a7789926bcf7c2b8d187292c9f93ea30284802a0ab3f5be8ab36865d", size = 19932823, upload_time = "2024-08-26T20:16:40.171Z" }, + { url = "https://files.pythonhosted.org/packages/f1/46/ea25b98b13dccaebddf1a803f8c748680d972e00507cd9bc6dcdb5aa2ac1/numpy-2.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9189427407d88ff25ecf8f12469d4d39d35bee1db5d39fc5c168c6f088a6956d", size = 14404424, upload_time = "2024-08-26T20:17:02.604Z" }, + { url = "https://files.pythonhosted.org/packages/c8/a6/177dd88d95ecf07e722d21008b1b40e681a929eb9e329684d449c36586b2/numpy-2.0.2-cp39-cp39-win32.whl", hash = "sha256:905d16e0c60200656500c95b6b8dca5d109e23cb24abc701d41c02d74c6b3afa", size = 6476809, upload_time = "2024-08-26T20:17:13.553Z" }, + { url = "https://files.pythonhosted.org/packages/ea/2b/7fc9f4e7ae5b507c1a3a21f0f15ed03e794c1242ea8a242ac158beb56034/numpy-2.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:a3f4ab0caa7f053f6797fcd4e1e25caee367db3112ef2b6ef82d749530768c73", size = 15911314, upload_time = "2024-08-26T20:17:36.72Z" }, + { url = "https://files.pythonhosted.org/packages/8f/3b/df5a870ac6a3be3a86856ce195ef42eec7ae50d2a202be1f5a4b3b340e14/numpy-2.0.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7f0a0c6f12e07fa94133c8a67404322845220c06a9e80e85999afe727f7438b8", size = 21025288, upload_time = "2024-08-26T20:18:07.732Z" }, + { url = "https://files.pythonhosted.org/packages/2c/97/51af92f18d6f6f2d9ad8b482a99fb74e142d71372da5d834b3a2747a446e/numpy-2.0.2-pp39-pypy39_pp73-macosx_14_0_x86_64.whl", hash = "sha256:312950fdd060354350ed123c0e25a71327d3711584beaef30cdaa93320c392d4", size = 6762793, upload_time = "2024-08-26T20:18:19.125Z" }, + { url = "https://files.pythonhosted.org/packages/12/46/de1fbd0c1b5ccaa7f9a005b66761533e2f6a3e560096682683a223631fe9/numpy-2.0.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26df23238872200f63518dd2aa984cfca675d82469535dc7162dc2ee52d9dd5c", size = 19334885, upload_time = "2024-08-26T20:18:47.237Z" }, + { url = "https://files.pythonhosted.org/packages/cc/dc/d330a6faefd92b446ec0f0dfea4c3207bb1fef3c4771d19cf4543efd2c78/numpy-2.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a46288ec55ebbd58947d31d72be2c63cbf839f0a63b49cb755022310792a3385", size = 15828784, upload_time = "2024-08-26T20:19:11.19Z" }, ] [[package]] @@ -908,62 +910,62 @@ source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.10'", ] -sdist = { url = "https://files.pythonhosted.org/packages/e1/78/31103410a57bc2c2b93a3597340a8119588571f6a4539067546cb9a0bfac/numpy-2.2.4.tar.gz", hash = "sha256:9ba03692a45d3eef66559efe1d1096c4b9b75c0986b5dff5530c378fb8331d4f", size = 20270701 } +sdist = { url = "https://files.pythonhosted.org/packages/e1/78/31103410a57bc2c2b93a3597340a8119588571f6a4539067546cb9a0bfac/numpy-2.2.4.tar.gz", hash = "sha256:9ba03692a45d3eef66559efe1d1096c4b9b75c0986b5dff5530c378fb8331d4f", size = 20270701, upload_time = "2025-03-16T18:27:00.648Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/04/89/a79e86e5c1433926ed7d60cb267fb64aa578b6101ab645800fd43b4801de/numpy-2.2.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8146f3550d627252269ac42ae660281d673eb6f8b32f113538e0cc2a9aed42b9", size = 21250661 }, - { url = "https://files.pythonhosted.org/packages/79/c2/f50921beb8afd60ed9589ad880332cfefdb805422210d327fb48f12b7a81/numpy-2.2.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e642d86b8f956098b564a45e6f6ce68a22c2c97a04f5acd3f221f57b8cb850ae", size = 14389926 }, - { url = "https://files.pythonhosted.org/packages/c7/b9/2c4e96130b0b0f97b0ef4a06d6dae3b39d058b21a5e2fa2decd7fd6b1c8f/numpy-2.2.4-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:a84eda42bd12edc36eb5b53bbcc9b406820d3353f1994b6cfe453a33ff101775", size = 5428329 }, - { url = "https://files.pythonhosted.org/packages/7f/a5/3d7094aa898f4fc5c84cdfb26beeae780352d43f5d8bdec966c4393d644c/numpy-2.2.4-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:4ba5054787e89c59c593a4169830ab362ac2bee8a969249dc56e5d7d20ff8df9", size = 6963559 }, - { url = "https://files.pythonhosted.org/packages/4c/22/fb1be710a14434c09080dd4a0acc08939f612ec02efcb04b9e210474782d/numpy-2.2.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7716e4a9b7af82c06a2543c53ca476fa0b57e4d760481273e09da04b74ee6ee2", size = 14368066 }, - { url = "https://files.pythonhosted.org/packages/c2/07/2e5cc71193e3ef3a219ffcf6ca4858e46ea2be09c026ddd480d596b32867/numpy-2.2.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:adf8c1d66f432ce577d0197dceaac2ac00c0759f573f28516246351c58a85020", size = 16417040 }, - { url = "https://files.pythonhosted.org/packages/1a/97/3b1537776ad9a6d1a41813818343745e8dd928a2916d4c9edcd9a8af1dac/numpy-2.2.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:218f061d2faa73621fa23d6359442b0fc658d5b9a70801373625d958259eaca3", size = 15879862 }, - { url = "https://files.pythonhosted.org/packages/b0/b7/4472f603dd45ef36ff3d8e84e84fe02d9467c78f92cc121633dce6da307b/numpy-2.2.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:df2f57871a96bbc1b69733cd4c51dc33bea66146b8c63cacbfed73eec0883017", size = 18206032 }, - { url = "https://files.pythonhosted.org/packages/0d/bd/6a092963fb82e6c5aa0d0440635827bbb2910da229545473bbb58c537ed3/numpy-2.2.4-cp310-cp310-win32.whl", hash = "sha256:a0258ad1f44f138b791327961caedffbf9612bfa504ab9597157806faa95194a", size = 6608517 }, - { url = "https://files.pythonhosted.org/packages/01/e3/cb04627bc2a1638948bc13e818df26495aa18e20d5be1ed95ab2b10b6847/numpy-2.2.4-cp310-cp310-win_amd64.whl", hash = "sha256:0d54974f9cf14acf49c60f0f7f4084b6579d24d439453d5fc5805d46a165b542", size = 12943498 }, - { url = "https://files.pythonhosted.org/packages/16/fb/09e778ee3a8ea0d4dc8329cca0a9c9e65fed847d08e37eba74cb7ed4b252/numpy-2.2.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e9e0a277bb2eb5d8a7407e14688b85fd8ad628ee4e0c7930415687b6564207a4", size = 21254989 }, - { url = "https://files.pythonhosted.org/packages/a2/0a/1212befdbecab5d80eca3cde47d304cad986ad4eec7d85a42e0b6d2cc2ef/numpy-2.2.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9eeea959168ea555e556b8188da5fa7831e21d91ce031e95ce23747b7609f8a4", size = 14425910 }, - { url = "https://files.pythonhosted.org/packages/2b/3e/e7247c1d4f15086bb106c8d43c925b0b2ea20270224f5186fa48d4fb5cbd/numpy-2.2.4-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:bd3ad3b0a40e713fc68f99ecfd07124195333f1e689387c180813f0e94309d6f", size = 5426490 }, - { url = "https://files.pythonhosted.org/packages/5d/fa/aa7cd6be51419b894c5787a8a93c3302a1ed4f82d35beb0613ec15bdd0e2/numpy-2.2.4-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:cf28633d64294969c019c6df4ff37f5698e8326db68cc2b66576a51fad634880", size = 6967754 }, - { url = "https://files.pythonhosted.org/packages/d5/ee/96457c943265de9fadeb3d2ffdbab003f7fba13d971084a9876affcda095/numpy-2.2.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2fa8fa7697ad1646b5c93de1719965844e004fcad23c91228aca1cf0800044a1", size = 14373079 }, - { url = "https://files.pythonhosted.org/packages/c5/5c/ceefca458559f0ccc7a982319f37ed07b0d7b526964ae6cc61f8ad1b6119/numpy-2.2.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f4162988a360a29af158aeb4a2f4f09ffed6a969c9776f8f3bdee9b06a8ab7e5", size = 16428819 }, - { url = "https://files.pythonhosted.org/packages/22/31/9b2ac8eee99e001eb6add9fa27514ef5e9faf176169057a12860af52704c/numpy-2.2.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:892c10d6a73e0f14935c31229e03325a7b3093fafd6ce0af704be7f894d95687", size = 15881470 }, - { url = "https://files.pythonhosted.org/packages/f0/dc/8569b5f25ff30484b555ad8a3f537e0225d091abec386c9420cf5f7a2976/numpy-2.2.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db1f1c22173ac1c58db249ae48aa7ead29f534b9a948bc56828337aa84a32ed6", size = 18218144 }, - { url = "https://files.pythonhosted.org/packages/5e/05/463c023a39bdeb9bb43a99e7dee2c664cb68d5bb87d14f92482b9f6011cc/numpy-2.2.4-cp311-cp311-win32.whl", hash = "sha256:ea2bb7e2ae9e37d96835b3576a4fa4b3a97592fbea8ef7c3587078b0068b8f09", size = 6606368 }, - { url = "https://files.pythonhosted.org/packages/8b/72/10c1d2d82101c468a28adc35de6c77b308f288cfd0b88e1070f15b98e00c/numpy-2.2.4-cp311-cp311-win_amd64.whl", hash = "sha256:f7de08cbe5551911886d1ab60de58448c6df0f67d9feb7d1fb21e9875ef95e91", size = 12947526 }, - { url = "https://files.pythonhosted.org/packages/a2/30/182db21d4f2a95904cec1a6f779479ea1ac07c0647f064dea454ec650c42/numpy-2.2.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a7b9084668aa0f64e64bd00d27ba5146ef1c3a8835f3bd912e7a9e01326804c4", size = 20947156 }, - { url = "https://files.pythonhosted.org/packages/24/6d/9483566acfbda6c62c6bc74b6e981c777229d2af93c8eb2469b26ac1b7bc/numpy-2.2.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:dbe512c511956b893d2dacd007d955a3f03d555ae05cfa3ff1c1ff6df8851854", size = 14133092 }, - { url = "https://files.pythonhosted.org/packages/27/f6/dba8a258acbf9d2bed2525cdcbb9493ef9bae5199d7a9cb92ee7e9b2aea6/numpy-2.2.4-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:bb649f8b207ab07caebba230d851b579a3c8711a851d29efe15008e31bb4de24", size = 5163515 }, - { url = "https://files.pythonhosted.org/packages/62/30/82116199d1c249446723c68f2c9da40d7f062551036f50b8c4caa42ae252/numpy-2.2.4-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:f34dc300df798742b3d06515aa2a0aee20941c13579d7a2f2e10af01ae4901ee", size = 6696558 }, - { url = "https://files.pythonhosted.org/packages/0e/b2/54122b3c6df5df3e87582b2e9430f1bdb63af4023c739ba300164c9ae503/numpy-2.2.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3f7ac96b16955634e223b579a3e5798df59007ca43e8d451a0e6a50f6bfdfba", size = 14084742 }, - { url = "https://files.pythonhosted.org/packages/02/e2/e2cbb8d634151aab9528ef7b8bab52ee4ab10e076509285602c2a3a686e0/numpy-2.2.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f92084defa704deadd4e0a5ab1dc52d8ac9e8a8ef617f3fbb853e79b0ea3592", size = 16134051 }, - { url = "https://files.pythonhosted.org/packages/8e/21/efd47800e4affc993e8be50c1b768de038363dd88865920439ef7b422c60/numpy-2.2.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7a4e84a6283b36632e2a5b56e121961f6542ab886bc9e12f8f9818b3c266bfbb", size = 15578972 }, - { url = "https://files.pythonhosted.org/packages/04/1e/f8bb88f6157045dd5d9b27ccf433d016981032690969aa5c19e332b138c0/numpy-2.2.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:11c43995255eb4127115956495f43e9343736edb7fcdb0d973defd9de14cd84f", size = 17898106 }, - { url = "https://files.pythonhosted.org/packages/2b/93/df59a5a3897c1f036ae8ff845e45f4081bb06943039ae28a3c1c7c780f22/numpy-2.2.4-cp312-cp312-win32.whl", hash = "sha256:65ef3468b53269eb5fdb3a5c09508c032b793da03251d5f8722b1194f1790c00", size = 6311190 }, - { url = "https://files.pythonhosted.org/packages/46/69/8c4f928741c2a8efa255fdc7e9097527c6dc4e4df147e3cadc5d9357ce85/numpy-2.2.4-cp312-cp312-win_amd64.whl", hash = "sha256:2aad3c17ed2ff455b8eaafe06bcdae0062a1db77cb99f4b9cbb5f4ecb13c5146", size = 12644305 }, - { url = "https://files.pythonhosted.org/packages/2a/d0/bd5ad792e78017f5decfb2ecc947422a3669a34f775679a76317af671ffc/numpy-2.2.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cf4e5c6a278d620dee9ddeb487dc6a860f9b199eadeecc567f777daace1e9e7", size = 20933623 }, - { url = "https://files.pythonhosted.org/packages/c3/bc/2b3545766337b95409868f8e62053135bdc7fa2ce630aba983a2aa60b559/numpy-2.2.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1974afec0b479e50438fc3648974268f972e2d908ddb6d7fb634598cdb8260a0", size = 14148681 }, - { url = "https://files.pythonhosted.org/packages/6a/70/67b24d68a56551d43a6ec9fe8c5f91b526d4c1a46a6387b956bf2d64744e/numpy-2.2.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:79bd5f0a02aa16808fcbc79a9a376a147cc1045f7dfe44c6e7d53fa8b8a79392", size = 5148759 }, - { url = "https://files.pythonhosted.org/packages/1c/8b/e2fc8a75fcb7be12d90b31477c9356c0cbb44abce7ffb36be39a0017afad/numpy-2.2.4-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:3387dd7232804b341165cedcb90694565a6015433ee076c6754775e85d86f1fc", size = 6683092 }, - { url = "https://files.pythonhosted.org/packages/13/73/41b7b27f169ecf368b52533edb72e56a133f9e86256e809e169362553b49/numpy-2.2.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f527d8fdb0286fd2fd97a2a96c6be17ba4232da346931d967a0630050dfd298", size = 14081422 }, - { url = "https://files.pythonhosted.org/packages/4b/04/e208ff3ae3ddfbafc05910f89546382f15a3f10186b1f56bd99f159689c2/numpy-2.2.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bce43e386c16898b91e162e5baaad90c4b06f9dcbe36282490032cec98dc8ae7", size = 16132202 }, - { url = "https://files.pythonhosted.org/packages/fe/bc/2218160574d862d5e55f803d88ddcad88beff94791f9c5f86d67bd8fbf1c/numpy-2.2.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:31504f970f563d99f71a3512d0c01a645b692b12a63630d6aafa0939e52361e6", size = 15573131 }, - { url = "https://files.pythonhosted.org/packages/a5/78/97c775bc4f05abc8a8426436b7cb1be806a02a2994b195945600855e3a25/numpy-2.2.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:81413336ef121a6ba746892fad881a83351ee3e1e4011f52e97fba79233611fd", size = 17894270 }, - { url = "https://files.pythonhosted.org/packages/b9/eb/38c06217a5f6de27dcb41524ca95a44e395e6a1decdc0c99fec0832ce6ae/numpy-2.2.4-cp313-cp313-win32.whl", hash = "sha256:f486038e44caa08dbd97275a9a35a283a8f1d2f0ee60ac260a1790e76660833c", size = 6308141 }, - { url = "https://files.pythonhosted.org/packages/52/17/d0dd10ab6d125c6d11ffb6dfa3423c3571befab8358d4f85cd4471964fcd/numpy-2.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:207a2b8441cc8b6a2a78c9ddc64d00d20c303d79fba08c577752f080c4007ee3", size = 12636885 }, - { url = "https://files.pythonhosted.org/packages/fa/e2/793288ede17a0fdc921172916efb40f3cbc2aa97e76c5c84aba6dc7e8747/numpy-2.2.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8120575cb4882318c791f839a4fd66161a6fa46f3f0a5e613071aae35b5dd8f8", size = 20961829 }, - { url = "https://files.pythonhosted.org/packages/3a/75/bb4573f6c462afd1ea5cbedcc362fe3e9bdbcc57aefd37c681be1155fbaa/numpy-2.2.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a761ba0fa886a7bb33c6c8f6f20213735cb19642c580a931c625ee377ee8bd39", size = 14161419 }, - { url = "https://files.pythonhosted.org/packages/03/68/07b4cd01090ca46c7a336958b413cdbe75002286295f2addea767b7f16c9/numpy-2.2.4-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:ac0280f1ba4a4bfff363a99a6aceed4f8e123f8a9b234c89140f5e894e452ecd", size = 5196414 }, - { url = "https://files.pythonhosted.org/packages/a5/fd/d4a29478d622fedff5c4b4b4cedfc37a00691079623c0575978d2446db9e/numpy-2.2.4-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:879cf3a9a2b53a4672a168c21375166171bc3932b7e21f622201811c43cdd3b0", size = 6709379 }, - { url = "https://files.pythonhosted.org/packages/41/78/96dddb75bb9be730b87c72f30ffdd62611aba234e4e460576a068c98eff6/numpy-2.2.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f05d4198c1bacc9124018109c5fba2f3201dbe7ab6e92ff100494f236209c960", size = 14051725 }, - { url = "https://files.pythonhosted.org/packages/00/06/5306b8199bffac2a29d9119c11f457f6c7d41115a335b78d3f86fad4dbe8/numpy-2.2.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2f085ce2e813a50dfd0e01fbfc0c12bbe5d2063d99f8b29da30e544fb6483b8", size = 16101638 }, - { url = "https://files.pythonhosted.org/packages/fa/03/74c5b631ee1ded596945c12027649e6344614144369fd3ec1aaced782882/numpy-2.2.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:92bda934a791c01d6d9d8e038363c50918ef7c40601552a58ac84c9613a665bc", size = 15571717 }, - { url = "https://files.pythonhosted.org/packages/cb/dc/4fc7c0283abe0981e3b89f9b332a134e237dd476b0c018e1e21083310c31/numpy-2.2.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ee4d528022f4c5ff67332469e10efe06a267e32f4067dc76bb7e2cddf3cd25ff", size = 17879998 }, - { url = "https://files.pythonhosted.org/packages/e5/2b/878576190c5cfa29ed896b518cc516aecc7c98a919e20706c12480465f43/numpy-2.2.4-cp313-cp313t-win32.whl", hash = "sha256:05c076d531e9998e7e694c36e8b349969c56eadd2cdcd07242958489d79a7286", size = 6366896 }, - { url = "https://files.pythonhosted.org/packages/3e/05/eb7eec66b95cf697f08c754ef26c3549d03ebd682819f794cb039574a0a6/numpy-2.2.4-cp313-cp313t-win_amd64.whl", hash = "sha256:188dcbca89834cc2e14eb2f106c96d6d46f200fe0200310fc29089657379c58d", size = 12739119 }, - { url = "https://files.pythonhosted.org/packages/b2/5c/f09c33a511aff41a098e6ef3498465d95f6360621034a3d95f47edbc9119/numpy-2.2.4-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7051ee569db5fbac144335e0f3b9c2337e0c8d5c9fee015f259a5bd70772b7e8", size = 21081956 }, - { url = "https://files.pythonhosted.org/packages/ba/30/74c48b3b6494c4b820b7fa1781d441e94d87a08daa5b35d222f06ba41a6f/numpy-2.2.4-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:ab2939cd5bec30a7430cbdb2287b63151b77cf9624de0532d629c9a1c59b1d5c", size = 6827143 }, - { url = "https://files.pythonhosted.org/packages/54/f5/ab0d2f48b490535c7a80e05da4a98902b632369efc04f0e47bb31ca97d8f/numpy-2.2.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0f35b19894a9e08639fd60a1ec1978cb7f5f7f1eace62f38dd36be8aecdef4d", size = 16233350 }, - { url = "https://files.pythonhosted.org/packages/3b/3a/2f6d8c1f8e45d496bca6baaec93208035faeb40d5735c25afac092ec9a12/numpy-2.2.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b4adfbbc64014976d2f91084915ca4e626fbf2057fb81af209c1a6d776d23e3d", size = 12857565 }, + { url = "https://files.pythonhosted.org/packages/04/89/a79e86e5c1433926ed7d60cb267fb64aa578b6101ab645800fd43b4801de/numpy-2.2.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8146f3550d627252269ac42ae660281d673eb6f8b32f113538e0cc2a9aed42b9", size = 21250661, upload_time = "2025-03-16T18:02:13.017Z" }, + { url = "https://files.pythonhosted.org/packages/79/c2/f50921beb8afd60ed9589ad880332cfefdb805422210d327fb48f12b7a81/numpy-2.2.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e642d86b8f956098b564a45e6f6ce68a22c2c97a04f5acd3f221f57b8cb850ae", size = 14389926, upload_time = "2025-03-16T18:02:39.022Z" }, + { url = "https://files.pythonhosted.org/packages/c7/b9/2c4e96130b0b0f97b0ef4a06d6dae3b39d058b21a5e2fa2decd7fd6b1c8f/numpy-2.2.4-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:a84eda42bd12edc36eb5b53bbcc9b406820d3353f1994b6cfe453a33ff101775", size = 5428329, upload_time = "2025-03-16T18:02:50.032Z" }, + { url = "https://files.pythonhosted.org/packages/7f/a5/3d7094aa898f4fc5c84cdfb26beeae780352d43f5d8bdec966c4393d644c/numpy-2.2.4-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:4ba5054787e89c59c593a4169830ab362ac2bee8a969249dc56e5d7d20ff8df9", size = 6963559, upload_time = "2025-03-16T18:03:02.523Z" }, + { url = "https://files.pythonhosted.org/packages/4c/22/fb1be710a14434c09080dd4a0acc08939f612ec02efcb04b9e210474782d/numpy-2.2.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7716e4a9b7af82c06a2543c53ca476fa0b57e4d760481273e09da04b74ee6ee2", size = 14368066, upload_time = "2025-03-16T18:03:27.146Z" }, + { url = "https://files.pythonhosted.org/packages/c2/07/2e5cc71193e3ef3a219ffcf6ca4858e46ea2be09c026ddd480d596b32867/numpy-2.2.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:adf8c1d66f432ce577d0197dceaac2ac00c0759f573f28516246351c58a85020", size = 16417040, upload_time = "2025-03-16T18:03:55.999Z" }, + { url = "https://files.pythonhosted.org/packages/1a/97/3b1537776ad9a6d1a41813818343745e8dd928a2916d4c9edcd9a8af1dac/numpy-2.2.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:218f061d2faa73621fa23d6359442b0fc658d5b9a70801373625d958259eaca3", size = 15879862, upload_time = "2025-03-16T18:04:23.56Z" }, + { url = "https://files.pythonhosted.org/packages/b0/b7/4472f603dd45ef36ff3d8e84e84fe02d9467c78f92cc121633dce6da307b/numpy-2.2.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:df2f57871a96bbc1b69733cd4c51dc33bea66146b8c63cacbfed73eec0883017", size = 18206032, upload_time = "2025-03-16T18:04:53.694Z" }, + { url = "https://files.pythonhosted.org/packages/0d/bd/6a092963fb82e6c5aa0d0440635827bbb2910da229545473bbb58c537ed3/numpy-2.2.4-cp310-cp310-win32.whl", hash = "sha256:a0258ad1f44f138b791327961caedffbf9612bfa504ab9597157806faa95194a", size = 6608517, upload_time = "2025-03-16T18:05:06.647Z" }, + { url = "https://files.pythonhosted.org/packages/01/e3/cb04627bc2a1638948bc13e818df26495aa18e20d5be1ed95ab2b10b6847/numpy-2.2.4-cp310-cp310-win_amd64.whl", hash = "sha256:0d54974f9cf14acf49c60f0f7f4084b6579d24d439453d5fc5805d46a165b542", size = 12943498, upload_time = "2025-03-16T18:05:28.591Z" }, + { url = "https://files.pythonhosted.org/packages/16/fb/09e778ee3a8ea0d4dc8329cca0a9c9e65fed847d08e37eba74cb7ed4b252/numpy-2.2.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e9e0a277bb2eb5d8a7407e14688b85fd8ad628ee4e0c7930415687b6564207a4", size = 21254989, upload_time = "2025-03-16T18:06:04.092Z" }, + { url = "https://files.pythonhosted.org/packages/a2/0a/1212befdbecab5d80eca3cde47d304cad986ad4eec7d85a42e0b6d2cc2ef/numpy-2.2.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9eeea959168ea555e556b8188da5fa7831e21d91ce031e95ce23747b7609f8a4", size = 14425910, upload_time = "2025-03-16T18:06:29.062Z" }, + { url = "https://files.pythonhosted.org/packages/2b/3e/e7247c1d4f15086bb106c8d43c925b0b2ea20270224f5186fa48d4fb5cbd/numpy-2.2.4-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:bd3ad3b0a40e713fc68f99ecfd07124195333f1e689387c180813f0e94309d6f", size = 5426490, upload_time = "2025-03-16T18:06:39.901Z" }, + { url = "https://files.pythonhosted.org/packages/5d/fa/aa7cd6be51419b894c5787a8a93c3302a1ed4f82d35beb0613ec15bdd0e2/numpy-2.2.4-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:cf28633d64294969c019c6df4ff37f5698e8326db68cc2b66576a51fad634880", size = 6967754, upload_time = "2025-03-16T18:06:52.658Z" }, + { url = "https://files.pythonhosted.org/packages/d5/ee/96457c943265de9fadeb3d2ffdbab003f7fba13d971084a9876affcda095/numpy-2.2.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2fa8fa7697ad1646b5c93de1719965844e004fcad23c91228aca1cf0800044a1", size = 14373079, upload_time = "2025-03-16T18:07:16.297Z" }, + { url = "https://files.pythonhosted.org/packages/c5/5c/ceefca458559f0ccc7a982319f37ed07b0d7b526964ae6cc61f8ad1b6119/numpy-2.2.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f4162988a360a29af158aeb4a2f4f09ffed6a969c9776f8f3bdee9b06a8ab7e5", size = 16428819, upload_time = "2025-03-16T18:07:44.188Z" }, + { url = "https://files.pythonhosted.org/packages/22/31/9b2ac8eee99e001eb6add9fa27514ef5e9faf176169057a12860af52704c/numpy-2.2.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:892c10d6a73e0f14935c31229e03325a7b3093fafd6ce0af704be7f894d95687", size = 15881470, upload_time = "2025-03-16T18:08:11.545Z" }, + { url = "https://files.pythonhosted.org/packages/f0/dc/8569b5f25ff30484b555ad8a3f537e0225d091abec386c9420cf5f7a2976/numpy-2.2.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db1f1c22173ac1c58db249ae48aa7ead29f534b9a948bc56828337aa84a32ed6", size = 18218144, upload_time = "2025-03-16T18:08:42.042Z" }, + { url = "https://files.pythonhosted.org/packages/5e/05/463c023a39bdeb9bb43a99e7dee2c664cb68d5bb87d14f92482b9f6011cc/numpy-2.2.4-cp311-cp311-win32.whl", hash = "sha256:ea2bb7e2ae9e37d96835b3576a4fa4b3a97592fbea8ef7c3587078b0068b8f09", size = 6606368, upload_time = "2025-03-16T18:08:55.074Z" }, + { url = "https://files.pythonhosted.org/packages/8b/72/10c1d2d82101c468a28adc35de6c77b308f288cfd0b88e1070f15b98e00c/numpy-2.2.4-cp311-cp311-win_amd64.whl", hash = "sha256:f7de08cbe5551911886d1ab60de58448c6df0f67d9feb7d1fb21e9875ef95e91", size = 12947526, upload_time = "2025-03-16T18:09:16.844Z" }, + { url = "https://files.pythonhosted.org/packages/a2/30/182db21d4f2a95904cec1a6f779479ea1ac07c0647f064dea454ec650c42/numpy-2.2.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a7b9084668aa0f64e64bd00d27ba5146ef1c3a8835f3bd912e7a9e01326804c4", size = 20947156, upload_time = "2025-03-16T18:09:51.975Z" }, + { url = "https://files.pythonhosted.org/packages/24/6d/9483566acfbda6c62c6bc74b6e981c777229d2af93c8eb2469b26ac1b7bc/numpy-2.2.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:dbe512c511956b893d2dacd007d955a3f03d555ae05cfa3ff1c1ff6df8851854", size = 14133092, upload_time = "2025-03-16T18:10:16.329Z" }, + { url = "https://files.pythonhosted.org/packages/27/f6/dba8a258acbf9d2bed2525cdcbb9493ef9bae5199d7a9cb92ee7e9b2aea6/numpy-2.2.4-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:bb649f8b207ab07caebba230d851b579a3c8711a851d29efe15008e31bb4de24", size = 5163515, upload_time = "2025-03-16T18:10:26.19Z" }, + { url = "https://files.pythonhosted.org/packages/62/30/82116199d1c249446723c68f2c9da40d7f062551036f50b8c4caa42ae252/numpy-2.2.4-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:f34dc300df798742b3d06515aa2a0aee20941c13579d7a2f2e10af01ae4901ee", size = 6696558, upload_time = "2025-03-16T18:10:38.996Z" }, + { url = "https://files.pythonhosted.org/packages/0e/b2/54122b3c6df5df3e87582b2e9430f1bdb63af4023c739ba300164c9ae503/numpy-2.2.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3f7ac96b16955634e223b579a3e5798df59007ca43e8d451a0e6a50f6bfdfba", size = 14084742, upload_time = "2025-03-16T18:11:02.76Z" }, + { url = "https://files.pythonhosted.org/packages/02/e2/e2cbb8d634151aab9528ef7b8bab52ee4ab10e076509285602c2a3a686e0/numpy-2.2.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f92084defa704deadd4e0a5ab1dc52d8ac9e8a8ef617f3fbb853e79b0ea3592", size = 16134051, upload_time = "2025-03-16T18:11:32.767Z" }, + { url = "https://files.pythonhosted.org/packages/8e/21/efd47800e4affc993e8be50c1b768de038363dd88865920439ef7b422c60/numpy-2.2.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7a4e84a6283b36632e2a5b56e121961f6542ab886bc9e12f8f9818b3c266bfbb", size = 15578972, upload_time = "2025-03-16T18:11:59.877Z" }, + { url = "https://files.pythonhosted.org/packages/04/1e/f8bb88f6157045dd5d9b27ccf433d016981032690969aa5c19e332b138c0/numpy-2.2.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:11c43995255eb4127115956495f43e9343736edb7fcdb0d973defd9de14cd84f", size = 17898106, upload_time = "2025-03-16T18:12:31.487Z" }, + { url = "https://files.pythonhosted.org/packages/2b/93/df59a5a3897c1f036ae8ff845e45f4081bb06943039ae28a3c1c7c780f22/numpy-2.2.4-cp312-cp312-win32.whl", hash = "sha256:65ef3468b53269eb5fdb3a5c09508c032b793da03251d5f8722b1194f1790c00", size = 6311190, upload_time = "2025-03-16T18:12:44.46Z" }, + { url = "https://files.pythonhosted.org/packages/46/69/8c4f928741c2a8efa255fdc7e9097527c6dc4e4df147e3cadc5d9357ce85/numpy-2.2.4-cp312-cp312-win_amd64.whl", hash = "sha256:2aad3c17ed2ff455b8eaafe06bcdae0062a1db77cb99f4b9cbb5f4ecb13c5146", size = 12644305, upload_time = "2025-03-16T18:13:06.864Z" }, + { url = "https://files.pythonhosted.org/packages/2a/d0/bd5ad792e78017f5decfb2ecc947422a3669a34f775679a76317af671ffc/numpy-2.2.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cf4e5c6a278d620dee9ddeb487dc6a860f9b199eadeecc567f777daace1e9e7", size = 20933623, upload_time = "2025-03-16T18:13:43.231Z" }, + { url = "https://files.pythonhosted.org/packages/c3/bc/2b3545766337b95409868f8e62053135bdc7fa2ce630aba983a2aa60b559/numpy-2.2.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1974afec0b479e50438fc3648974268f972e2d908ddb6d7fb634598cdb8260a0", size = 14148681, upload_time = "2025-03-16T18:14:08.031Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/67b24d68a56551d43a6ec9fe8c5f91b526d4c1a46a6387b956bf2d64744e/numpy-2.2.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:79bd5f0a02aa16808fcbc79a9a376a147cc1045f7dfe44c6e7d53fa8b8a79392", size = 5148759, upload_time = "2025-03-16T18:14:18.613Z" }, + { url = "https://files.pythonhosted.org/packages/1c/8b/e2fc8a75fcb7be12d90b31477c9356c0cbb44abce7ffb36be39a0017afad/numpy-2.2.4-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:3387dd7232804b341165cedcb90694565a6015433ee076c6754775e85d86f1fc", size = 6683092, upload_time = "2025-03-16T18:14:31.386Z" }, + { url = "https://files.pythonhosted.org/packages/13/73/41b7b27f169ecf368b52533edb72e56a133f9e86256e809e169362553b49/numpy-2.2.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f527d8fdb0286fd2fd97a2a96c6be17ba4232da346931d967a0630050dfd298", size = 14081422, upload_time = "2025-03-16T18:14:54.83Z" }, + { url = "https://files.pythonhosted.org/packages/4b/04/e208ff3ae3ddfbafc05910f89546382f15a3f10186b1f56bd99f159689c2/numpy-2.2.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bce43e386c16898b91e162e5baaad90c4b06f9dcbe36282490032cec98dc8ae7", size = 16132202, upload_time = "2025-03-16T18:15:22.035Z" }, + { url = "https://files.pythonhosted.org/packages/fe/bc/2218160574d862d5e55f803d88ddcad88beff94791f9c5f86d67bd8fbf1c/numpy-2.2.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:31504f970f563d99f71a3512d0c01a645b692b12a63630d6aafa0939e52361e6", size = 15573131, upload_time = "2025-03-16T18:15:48.546Z" }, + { url = "https://files.pythonhosted.org/packages/a5/78/97c775bc4f05abc8a8426436b7cb1be806a02a2994b195945600855e3a25/numpy-2.2.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:81413336ef121a6ba746892fad881a83351ee3e1e4011f52e97fba79233611fd", size = 17894270, upload_time = "2025-03-16T18:16:20.274Z" }, + { url = "https://files.pythonhosted.org/packages/b9/eb/38c06217a5f6de27dcb41524ca95a44e395e6a1decdc0c99fec0832ce6ae/numpy-2.2.4-cp313-cp313-win32.whl", hash = "sha256:f486038e44caa08dbd97275a9a35a283a8f1d2f0ee60ac260a1790e76660833c", size = 6308141, upload_time = "2025-03-16T18:20:15.297Z" }, + { url = "https://files.pythonhosted.org/packages/52/17/d0dd10ab6d125c6d11ffb6dfa3423c3571befab8358d4f85cd4471964fcd/numpy-2.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:207a2b8441cc8b6a2a78c9ddc64d00d20c303d79fba08c577752f080c4007ee3", size = 12636885, upload_time = "2025-03-16T18:20:36.982Z" }, + { url = "https://files.pythonhosted.org/packages/fa/e2/793288ede17a0fdc921172916efb40f3cbc2aa97e76c5c84aba6dc7e8747/numpy-2.2.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8120575cb4882318c791f839a4fd66161a6fa46f3f0a5e613071aae35b5dd8f8", size = 20961829, upload_time = "2025-03-16T18:16:56.191Z" }, + { url = "https://files.pythonhosted.org/packages/3a/75/bb4573f6c462afd1ea5cbedcc362fe3e9bdbcc57aefd37c681be1155fbaa/numpy-2.2.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a761ba0fa886a7bb33c6c8f6f20213735cb19642c580a931c625ee377ee8bd39", size = 14161419, upload_time = "2025-03-16T18:17:22.811Z" }, + { url = "https://files.pythonhosted.org/packages/03/68/07b4cd01090ca46c7a336958b413cdbe75002286295f2addea767b7f16c9/numpy-2.2.4-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:ac0280f1ba4a4bfff363a99a6aceed4f8e123f8a9b234c89140f5e894e452ecd", size = 5196414, upload_time = "2025-03-16T18:17:34.066Z" }, + { url = "https://files.pythonhosted.org/packages/a5/fd/d4a29478d622fedff5c4b4b4cedfc37a00691079623c0575978d2446db9e/numpy-2.2.4-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:879cf3a9a2b53a4672a168c21375166171bc3932b7e21f622201811c43cdd3b0", size = 6709379, upload_time = "2025-03-16T18:17:47.466Z" }, + { url = "https://files.pythonhosted.org/packages/41/78/96dddb75bb9be730b87c72f30ffdd62611aba234e4e460576a068c98eff6/numpy-2.2.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f05d4198c1bacc9124018109c5fba2f3201dbe7ab6e92ff100494f236209c960", size = 14051725, upload_time = "2025-03-16T18:18:11.904Z" }, + { url = "https://files.pythonhosted.org/packages/00/06/5306b8199bffac2a29d9119c11f457f6c7d41115a335b78d3f86fad4dbe8/numpy-2.2.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2f085ce2e813a50dfd0e01fbfc0c12bbe5d2063d99f8b29da30e544fb6483b8", size = 16101638, upload_time = "2025-03-16T18:18:40.749Z" }, + { url = "https://files.pythonhosted.org/packages/fa/03/74c5b631ee1ded596945c12027649e6344614144369fd3ec1aaced782882/numpy-2.2.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:92bda934a791c01d6d9d8e038363c50918ef7c40601552a58ac84c9613a665bc", size = 15571717, upload_time = "2025-03-16T18:19:04.512Z" }, + { url = "https://files.pythonhosted.org/packages/cb/dc/4fc7c0283abe0981e3b89f9b332a134e237dd476b0c018e1e21083310c31/numpy-2.2.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ee4d528022f4c5ff67332469e10efe06a267e32f4067dc76bb7e2cddf3cd25ff", size = 17879998, upload_time = "2025-03-16T18:19:32.52Z" }, + { url = "https://files.pythonhosted.org/packages/e5/2b/878576190c5cfa29ed896b518cc516aecc7c98a919e20706c12480465f43/numpy-2.2.4-cp313-cp313t-win32.whl", hash = "sha256:05c076d531e9998e7e694c36e8b349969c56eadd2cdcd07242958489d79a7286", size = 6366896, upload_time = "2025-03-16T18:19:43.55Z" }, + { url = "https://files.pythonhosted.org/packages/3e/05/eb7eec66b95cf697f08c754ef26c3549d03ebd682819f794cb039574a0a6/numpy-2.2.4-cp313-cp313t-win_amd64.whl", hash = "sha256:188dcbca89834cc2e14eb2f106c96d6d46f200fe0200310fc29089657379c58d", size = 12739119, upload_time = "2025-03-16T18:20:03.94Z" }, + { url = "https://files.pythonhosted.org/packages/b2/5c/f09c33a511aff41a098e6ef3498465d95f6360621034a3d95f47edbc9119/numpy-2.2.4-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7051ee569db5fbac144335e0f3b9c2337e0c8d5c9fee015f259a5bd70772b7e8", size = 21081956, upload_time = "2025-03-16T18:21:12.955Z" }, + { url = "https://files.pythonhosted.org/packages/ba/30/74c48b3b6494c4b820b7fa1781d441e94d87a08daa5b35d222f06ba41a6f/numpy-2.2.4-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:ab2939cd5bec30a7430cbdb2287b63151b77cf9624de0532d629c9a1c59b1d5c", size = 6827143, upload_time = "2025-03-16T18:21:26.748Z" }, + { url = "https://files.pythonhosted.org/packages/54/f5/ab0d2f48b490535c7a80e05da4a98902b632369efc04f0e47bb31ca97d8f/numpy-2.2.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0f35b19894a9e08639fd60a1ec1978cb7f5f7f1eace62f38dd36be8aecdef4d", size = 16233350, upload_time = "2025-03-16T18:21:53.902Z" }, + { url = "https://files.pythonhosted.org/packages/3b/3a/2f6d8c1f8e45d496bca6baaec93208035faeb40d5735c25afac092ec9a12/numpy-2.2.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b4adfbbc64014976d2f91084915ca4e626fbf2057fb81af209c1a6d776d23e3d", size = 12857565, upload_time = "2025-03-16T18:22:17.631Z" }, ] [[package]] @@ -980,18 +982,18 @@ dependencies = [ { name = "tqdm" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/87/f5/ae0f3cd226c2993b4ac1cc4b5f6ca099764689f403c14922c9356accec66/openai-1.70.0.tar.gz", hash = "sha256:e52a8d54c3efeb08cf58539b5b21a5abef25368b5432965e4de88cdf4e091b2b", size = 409640 } +sdist = { url = "https://files.pythonhosted.org/packages/87/f5/ae0f3cd226c2993b4ac1cc4b5f6ca099764689f403c14922c9356accec66/openai-1.70.0.tar.gz", hash = "sha256:e52a8d54c3efeb08cf58539b5b21a5abef25368b5432965e4de88cdf4e091b2b", size = 409640, upload_time = "2025-03-31T17:45:42.784Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e2/39/c4b38317d2c702c4bc763957735aaeaf30dfc43b5b824121c49a4ba7ba0f/openai-1.70.0-py3-none-any.whl", hash = "sha256:f6438d053fd8b2e05fd6bef70871e832d9bbdf55e119d0ac5b92726f1ae6f614", size = 599070 }, + { url = "https://files.pythonhosted.org/packages/e2/39/c4b38317d2c702c4bc763957735aaeaf30dfc43b5b824121c49a4ba7ba0f/openai-1.70.0-py3-none-any.whl", hash = "sha256:f6438d053fd8b2e05fd6bef70871e832d9bbdf55e119d0ac5b92726f1ae6f614", size = 599070, upload_time = "2025-03-31T17:45:40.649Z" }, ] [[package]] name = "packaging" version = "24.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 } +sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950, upload_time = "2024-11-08T09:47:47.202Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 }, + { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451, upload_time = "2024-11-08T09:47:44.722Z" }, ] [[package]] @@ -1002,18 +1004,18 @@ dependencies = [ { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "numpy", version = "2.2.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e2/40/296ffb7f97fc7ec7b7c34f46861a519c576d561fd31455fc75c5ce2fa8db/pgvector-0.4.0.tar.gz", hash = "sha256:f909f8e8081b57fb8a2442c36c3a1e521228d0d4ad66100c28c674806ff62688", size = 30688 } +sdist = { url = "https://files.pythonhosted.org/packages/e2/40/296ffb7f97fc7ec7b7c34f46861a519c576d561fd31455fc75c5ce2fa8db/pgvector-0.4.0.tar.gz", hash = "sha256:f909f8e8081b57fb8a2442c36c3a1e521228d0d4ad66100c28c674806ff62688", size = 30688, upload_time = "2025-03-16T00:56:01.321Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/90/fb/77d29e98b36f1a0c6f770157001d3747557cae46f0e6f6d282461e554b80/pgvector-0.4.0-py3-none-any.whl", hash = "sha256:9d3e0c27f676c61d2fd4270ac1bc520d39b947b199200babe4a56d6d00c74a07", size = 27027 }, + { url = "https://files.pythonhosted.org/packages/90/fb/77d29e98b36f1a0c6f770157001d3747557cae46f0e6f6d282461e554b80/pgvector-0.4.0-py3-none-any.whl", hash = "sha256:9d3e0c27f676c61d2fd4270ac1bc520d39b947b199200babe4a56d6d00c74a07", size = 27027, upload_time = "2025-03-16T00:55:59.948Z" }, ] [[package]] name = "pluggy" version = "1.5.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 } +sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955, upload_time = "2024-04-20T21:34:42.531Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, + { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556, upload_time = "2024-04-20T21:34:40.434Z" }, ] [[package]] @@ -1024,9 +1026,9 @@ dependencies = [ { name = "typing-extensions", marker = "python_full_version < '3.13'" }, { name = "tzdata", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/67/97/eea08f74f1c6dd2a02ee81b4ebfe5b558beb468ebbd11031adbf58d31be0/psycopg-3.2.6.tar.gz", hash = "sha256:16fa094efa2698f260f2af74f3710f781e4a6f226efe9d1fd0c37f384639ed8a", size = 156322 } +sdist = { url = "https://files.pythonhosted.org/packages/67/97/eea08f74f1c6dd2a02ee81b4ebfe5b558beb468ebbd11031adbf58d31be0/psycopg-3.2.6.tar.gz", hash = "sha256:16fa094efa2698f260f2af74f3710f781e4a6f226efe9d1fd0c37f384639ed8a", size = 156322, upload_time = "2025-03-12T20:43:12.228Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d7/7d/0ba52deff71f65df8ec8038adad86ba09368c945424a9bd8145d679a2c6a/psycopg-3.2.6-py3-none-any.whl", hash = "sha256:f3ff5488525890abb0566c429146add66b329e20d6d4835662b920cbbf90ac58", size = 199077 }, + { url = "https://files.pythonhosted.org/packages/d7/7d/0ba52deff71f65df8ec8038adad86ba09368c945424a9bd8145d679a2c6a/psycopg-3.2.6-py3-none-any.whl", hash = "sha256:f3ff5488525890abb0566c429146add66b329e20d6d4835662b920cbbf90ac58", size = 199077, upload_time = "2025-03-12T20:38:07.112Z" }, ] [package.optional-dependencies] @@ -1039,94 +1041,94 @@ name = "psycopg-binary" version = "3.2.6" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4b/7b/48afdcb14bf828c4006f573845fbbd98df701bff9043fbb0b8caab261b6f/psycopg_binary-3.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1b639acb3e24243c23f75700bf6e3af7b76da92523ec7c3196a13aaf0b578453", size = 3868985 }, - { url = "https://files.pythonhosted.org/packages/de/45/9e777c61ef3ac5e7fb42618afbd9f41464c1c396ec85c79c48086ace437a/psycopg_binary-3.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1b5c359173726b38d7acbb9f73270f269591d8031d099c1a70dd3f3d22b0e8a8", size = 3938244 }, - { url = "https://files.pythonhosted.org/packages/d6/93/e48962aca19af1f3d2cb0c2ff890ca305c51d1759a2e89c90a527228cf1d/psycopg_binary-3.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3434efe7c00f505f4c1e531519dac6c701df738ba7a1328eac81118d80019132", size = 4523096 }, - { url = "https://files.pythonhosted.org/packages/fe/52/21f4a9bb7123e07e06a712338eb6cc5794a23a56813deb4a8cd3de8ec91c/psycopg_binary-3.2.6-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bca8d9643191b13193940bbf84d51ac5a747e965c230177258fb02b8043fb7a", size = 4329659 }, - { url = "https://files.pythonhosted.org/packages/9e/72/8da1c98b4e0d4c3649f037101b70ae52e4f821597919dabc72c889e60ca9/psycopg_binary-3.2.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55fa40f11d37e6e5149a282a5fd7e0734ce55c623673bfba638480914fd1414c", size = 4575359 }, - { url = "https://files.pythonhosted.org/packages/83/5a/a85c98a5b2b3f771d7478ac0081b48749d4c07ce41d51f89f592f87cfbeb/psycopg_binary-3.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0690ac1061c655b1bcbe9284d07bf5276bc9c0d788a6c74aaf3b042e64984b83", size = 4287138 }, - { url = "https://files.pythonhosted.org/packages/b0/c3/0abafd3f300e5ff952dd9b3be95b4e2527ae1e2ea7fd7a7421e6bc1c0e37/psycopg_binary-3.2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e9a4a9967ff650d2821d5fad6bec7b15f4c2072603e9fa3f89a39f351ade1fd3", size = 3872142 }, - { url = "https://files.pythonhosted.org/packages/0f/16/029aa400c4b7f4b7042307d8a854768463a65326d061ad2145f7b3989ca5/psycopg_binary-3.2.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:d6f2894cc7aee8a15fe591e8536911d9c015cb404432cf7bdac2797e54cb2ba8", size = 3340033 }, - { url = "https://files.pythonhosted.org/packages/cd/a1/28e86b832d696ba5fd79c4d704b8ca46b827428f7ea063063ca280a678a4/psycopg_binary-3.2.6-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:05560c81312d7c2bee95a9860cd25198677f2320fb4a3527bc04e8cae7fcfb64", size = 3438823 }, - { url = "https://files.pythonhosted.org/packages/93/31/73546c999725b397bb7e7fd55f83a9c78787c6fe7fe457e4179d19a115dc/psycopg_binary-3.2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:4269cd23a485d6dd6eb6b10841c94551a53091cf0b1b6d5247a6a341f53f0d95", size = 3464031 }, - { url = "https://files.pythonhosted.org/packages/85/38/957bd4bdde976c9a38d61896bf9d2c8f5752b98d8f4d879a7902588a8583/psycopg_binary-3.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:7942f35a6f314608720116bcd9de240110ceadffd2ac5c34f68f74a31e52e46a", size = 2792159 }, - { url = "https://files.pythonhosted.org/packages/5a/71/5bfa1ffc4d59f0454b114ce0d017eca269b079ca2753a96302c2117067c7/psycopg_binary-3.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7afe181f6b3eb714362e9b6a2dc2a589bff60471a1d8639fd231a4e426e01523", size = 3876608 }, - { url = "https://files.pythonhosted.org/packages/7e/07/1724d842b876af7bef442f0853d6cbf951264229414e4d0a57b8e3787847/psycopg_binary-3.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:34bb0fceba0773dc0bfb53224bb2c0b19dc97ea0a997a223615484cf02cae55c", size = 3942785 }, - { url = "https://files.pythonhosted.org/packages/09/51/a251a356f10c7947bcc2285ebf1541e1c2d851b8db20eb8f29ed3a5974bf/psycopg_binary-3.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54120122d2779dcd307f49e1f921d757fe5dacdced27deab37f277eef0c52a5b", size = 4519448 }, - { url = "https://files.pythonhosted.org/packages/6e/cf/0c92ab1270664a1341e52f5794ecc636b1f4ac67bf1743075091795151f8/psycopg_binary-3.2.6-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:816aa556f63b2303e66ba6c8888a8b3f3e6e4e47049ec7a4d62c84ac60b091ca", size = 4324382 }, - { url = "https://files.pythonhosted.org/packages/bf/2b/6921bd4a57fe19d4618798a8a8648e1a516c92563c37b2073639fffac5d5/psycopg_binary-3.2.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d19a0ba351eda9a59babf8c7c9d89c7bbc5b26bf096bc349b096bd0dd2482088", size = 4578720 }, - { url = "https://files.pythonhosted.org/packages/5c/30/1034d164e2be09f650a86eccc93625e51568e307c855bf6f94759c298303/psycopg_binary-3.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d6e197e01290ef818a092c877025fc28096adbb6d0743e313491a21aab31bd96", size = 4281871 }, - { url = "https://files.pythonhosted.org/packages/c4/d0/67fdf0174c334a9a85a9672590d7da83e85d9cedfc35f473a557e310a1ca/psycopg_binary-3.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:274794b4b29ef426e09086404446b61a146f5e756da71366c5a6d57abec31f7d", size = 3870582 }, - { url = "https://files.pythonhosted.org/packages/9f/4e/3a4fd2d1fd715b11e7287023edde916e1174b58b37873c531f782a49803b/psycopg_binary-3.2.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:69845bdc0db519e1dfc27932cd3d5b1ecb3f72950af52a1987508ab0b52b3b55", size = 3334464 }, - { url = "https://files.pythonhosted.org/packages/4a/22/90a8032276fa5b215ce26cefb44abafa8fb09de396c6dc6f62a5e53fe2ad/psycopg_binary-3.2.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:66c3bed2caf0d1cabcb9365064de183b5209a7cbeaa131e79e68f350c9c963c2", size = 3431945 }, - { url = "https://files.pythonhosted.org/packages/e7/b0/e547e9a851ab19c79869c1d84a533e225d284e70c222720fed4529fcda60/psycopg_binary-3.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e3ae3201fe85c7f901349a2cf52f02ceca4cb97a5e2e2ac8b8a1c9a6eb747bed", size = 3463278 }, - { url = "https://files.pythonhosted.org/packages/e7/ce/e555bd8dd6fce8b34bbc3856125600f0842c85a8364702ebe0dc39372087/psycopg_binary-3.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:58f443b4df2adb59937c96775fadf4967f93d952fbcc82394446985faec11041", size = 2795094 }, - { url = "https://files.pythonhosted.org/packages/a3/c7/220b1273f0befb2cd9fe83d379b3484ae029a88798a90bc0d36f10bea5df/psycopg_binary-3.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f27a46ff0497e882e8c0286e8833c785b4d1a80f23e1bf606f4c90e5f9f3ce75", size = 3857986 }, - { url = "https://files.pythonhosted.org/packages/8a/d8/30176532826cf87c608a6f79dd668bf9aff0cdf8eb80209eddf4c5aa7229/psycopg_binary-3.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b30ee4821ded7de48b8048b14952512588e7c5477b0a5965221e1798afba61a1", size = 3940060 }, - { url = "https://files.pythonhosted.org/packages/54/7c/fa7cd1f057f33f7ae483d6bc5a03ec6eff111f8aa5c678d9aaef92705247/psycopg_binary-3.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e57edf3b1f5427f39660225b01f8e7b97f5cfab132092f014bf1638bc85d81d2", size = 4499082 }, - { url = "https://files.pythonhosted.org/packages/b8/81/1606966f6146187c273993ea6f88f2151b26741df8f4e01349a625983be9/psycopg_binary-3.2.6-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c5172ce3e4ae7a4fd450070210f801e2ce6bc0f11d1208d29268deb0cda34de", size = 4307509 }, - { url = "https://files.pythonhosted.org/packages/69/ad/01c87aab17a4b89128b8036800d11ab296c7c2c623940cc7e6f2668f375a/psycopg_binary-3.2.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bcfab3804c43571a6615e559cdc4c4115785d258a4dd71a721be033f5f5f378d", size = 4547813 }, - { url = "https://files.pythonhosted.org/packages/65/30/f93a193846ee738ffe5d2a4837e7ddeb7279707af81d088cee96cae853a0/psycopg_binary-3.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8fa1c920cce16f1205f37b20c685c58b9656b170b8b4c93629100d342d0d118e", size = 4259847 }, - { url = "https://files.pythonhosted.org/packages/8e/73/65c4ae71be86675a62154407c92af4b917146f9ff3baaf0e4166c0734aeb/psycopg_binary-3.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2e118d818101c1608c6b5ba52a6c977614d8f05aa89467501172ba4d10588e11", size = 3846550 }, - { url = "https://files.pythonhosted.org/packages/53/cc/a24626cac3f208c776bb22e15e9a5e483aa81145221e6427e50381f40811/psycopg_binary-3.2.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:763319a8bfeca77d31512da71f5a33459b9568a7621c481c3828c62f9c38f351", size = 3320269 }, - { url = "https://files.pythonhosted.org/packages/55/e6/68c76fb9d6c53d5e4170a0c9216c7aa6c2903808f626d84d002b47a16931/psycopg_binary-3.2.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:2fbc05819560389dbece046966bc88e0f2ea77673497e274c4293b8b4c1d0703", size = 3399365 }, - { url = "https://files.pythonhosted.org/packages/b4/2c/55b140f5a2c582dae42ef38502c45ef69c938274242a40bd04c143081029/psycopg_binary-3.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5a57f99bb953b4bd6f32d0a9844664e7f6ca5ead9ba40e96635be3cd30794813", size = 3438908 }, - { url = "https://files.pythonhosted.org/packages/ae/f6/589c95cceccee2ab408b6b2e16f1ed6db4536fb24f2f5c9ce568cf43270c/psycopg_binary-3.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:5de6809e19a465dcb9c269675bded46a135f2d600cd99f0735afbb21ddad2af4", size = 2782886 }, - { url = "https://files.pythonhosted.org/packages/bf/32/3d06c478fd3070ac25a49c2e8ca46b6d76b0048fa9fa255b99ee32f32312/psycopg_binary-3.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:54af3fbf871baa2eb19df96fd7dc0cbd88e628a692063c3d1ab5cdd00aa04322", size = 3852672 }, - { url = "https://files.pythonhosted.org/packages/34/97/e581030e279500ede3096adb510f0e6071874b97cfc047a9a87b7d71fc77/psycopg_binary-3.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ad5da1e4636776c21eaeacdec42f25fa4612631a12f25cd9ab34ddf2c346ffb9", size = 3936562 }, - { url = "https://files.pythonhosted.org/packages/74/b6/6a8df4cb23c3d327403a83406c06c9140f311cb56c4e4d720ee7abf6fddc/psycopg_binary-3.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7956b9ea56f79cd86eddcfbfc65ae2af1e4fe7932fa400755005d903c709370", size = 4499167 }, - { url = "https://files.pythonhosted.org/packages/e4/5b/950eafef61e5e0b8ddb5afc5b6b279756411aa4bf70a346a6f091ad679bb/psycopg_binary-3.2.6-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e2efb763188008cf2914820dcb9fb23c10fe2be0d2c97ef0fac7cec28e281d8", size = 4311651 }, - { url = "https://files.pythonhosted.org/packages/72/b9/b366c49afc854c26b3053d4d35376046eea9aebdc48ded18ea249ea1f80c/psycopg_binary-3.2.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4b3aab3451679f1e7932270e950259ed48c3b79390022d3f660491c0e65e4838", size = 4547852 }, - { url = "https://files.pythonhosted.org/packages/ab/d4/0e047360e2ea387dc7171ca017ffcee5214a0762f74b9dd982035f2e52fb/psycopg_binary-3.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:849a370ac4e125f55f2ad37f928e588291a67ccf91fa33d0b1e042bb3ee1f986", size = 4261725 }, - { url = "https://files.pythonhosted.org/packages/e3/ea/a1b969804250183900959ebe845d86be7fed2cbd9be58f64cd0fc24b2892/psycopg_binary-3.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:566d4ace928419d91f1eb3227fc9ef7b41cf0ad22e93dd2c3368d693cf144408", size = 3850073 }, - { url = "https://files.pythonhosted.org/packages/e5/71/ec2907342f0675092b76aea74365b56f38d960c4c635984dcfe25d8178c8/psycopg_binary-3.2.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f1981f13b10de2f11cfa2f99a8738b35b3f0a0f3075861446894a8d3042430c0", size = 3320323 }, - { url = "https://files.pythonhosted.org/packages/d7/d7/0d2cb4b42f231e2efe8ea1799ce917973d47486212a2c4d33cd331e7ac28/psycopg_binary-3.2.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:36f598300b55b3c983ae8df06473ad27333d2fd9f3e2cfdb913b3a5aaa3a8bcf", size = 3402335 }, - { url = "https://files.pythonhosted.org/packages/66/92/7050c372f78e53eba14695cec6c3a91b2d9ca56feaf0bfe95fe90facf730/psycopg_binary-3.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0f4699fa5fe1fffb0d6b2d14b31fd8c29b7ea7375f89d5989f002aaf21728b21", size = 3440442 }, - { url = "https://files.pythonhosted.org/packages/5f/4c/bebcaf754189283b2f3d457822a3d9b233d08ff50973d8f1e8d51f4d35ed/psycopg_binary-3.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:afe697b8b0071f497c5d4c0f41df9e038391534f5614f7fb3a8c1ca32d66e860", size = 2783465 }, - { url = "https://files.pythonhosted.org/packages/be/ac/5023320c46e0d233faff6cd2d902a60d6c09034f136e7cc0876463a97d50/psycopg_binary-3.2.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:260c43c329e668606388cee78ec0dab083a25c2c6e6f9cf74a130fd5a27b0f87", size = 3870121 }, - { url = "https://files.pythonhosted.org/packages/8d/2d/63cdd41112c954582d6bb8905970abe5e5a69b53d6dd2a38fc7ec624a6b4/psycopg_binary-3.2.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9870e51fad4684dbdec057fa757d65e61cb2acb16236836e9360044c2a1ec880", size = 3939150 }, - { url = "https://files.pythonhosted.org/packages/a8/c6/0a0fe3b5506d30d597c82b16b8c1e023393a10701109e917c4cb529ccc71/psycopg_binary-3.2.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:030e9c3082a931e972b029b3cef085784a3bf7f8e18367ae50d5b809aa6e1d87", size = 4525092 }, - { url = "https://files.pythonhosted.org/packages/0d/66/66c3c05a0535c022b1b8af501aec05161084e6a4ab44edebd99ff82de976/psycopg_binary-3.2.6-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b60c9ed291fbd5e777c2c630dcfd10b7a87d68512b0757d5e7406d9c4895a82a", size = 4330350 }, - { url = "https://files.pythonhosted.org/packages/90/f3/15ce9800b54c50ff01af1c39d0a727083bb3640067a54b58631ca6666b24/psycopg_binary-3.2.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e0f4a17a9c376c195e403b4826c18f325bd28f425231d36d1036258bf893e23", size = 4577917 }, - { url = "https://files.pythonhosted.org/packages/a0/aa/ffe622452a43376193d3d1e63b479e0f90ce9732c91366c05a16ce49e513/psycopg_binary-3.2.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac46da609624b16d961f604b3cbc3233ef43211ef1456a188f8c427109c9c3e1", size = 4286328 }, - { url = "https://files.pythonhosted.org/packages/bf/bf/53da912ba8ace3136318718112c5db85bf0ed4f079f92849536a78384875/psycopg_binary-3.2.6-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:e77949b8e7014b85cee0bf6e9e041bcae7719b2693ebf59236368fb0b2a08814", size = 3874534 }, - { url = "https://files.pythonhosted.org/packages/1a/b5/e659e96ffc6846d3f90c4af785c84898e625f974c7f67255688addda6167/psycopg_binary-3.2.6-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:532322d9ef6e7d178a4f344970b017110633bcc3dc1c3403efcef55aad612517", size = 3339634 }, - { url = "https://files.pythonhosted.org/packages/9b/d6/92858290a8d710a27caf1b92183f4fa2ef3b99c9b6959fc73fd9639b8412/psycopg_binary-3.2.6-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:880c5fd76dcb50bdcc8f87359e5a6c7eb416697cc9aa02854c91223bd999c045", size = 3437930 }, - { url = "https://files.pythonhosted.org/packages/f7/a5/60b5a6519f58b2deca261d2e4566bce4beeeb30f6e7c3f72e4d8f204dcf6/psycopg_binary-3.2.6-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3c0cddc7458b8416d77cd8829d0192466502f31d1fb853d58613cf13ac64f41c", size = 3464810 }, - { url = "https://files.pythonhosted.org/packages/27/b6/e48cafe90e46b82b1393276ce6d92ddb7650c39aba8aa0256affbaa94c36/psycopg_binary-3.2.6-cp39-cp39-win_amd64.whl", hash = "sha256:ea158665676f42b19585dfe948071d3c5f28276f84a97522fb2e82c1d9194563", size = 2793902 }, + { url = "https://files.pythonhosted.org/packages/4b/7b/48afdcb14bf828c4006f573845fbbd98df701bff9043fbb0b8caab261b6f/psycopg_binary-3.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1b639acb3e24243c23f75700bf6e3af7b76da92523ec7c3196a13aaf0b578453", size = 3868985, upload_time = "2025-03-12T20:38:12.396Z" }, + { url = "https://files.pythonhosted.org/packages/de/45/9e777c61ef3ac5e7fb42618afbd9f41464c1c396ec85c79c48086ace437a/psycopg_binary-3.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1b5c359173726b38d7acbb9f73270f269591d8031d099c1a70dd3f3d22b0e8a8", size = 3938244, upload_time = "2025-03-12T20:38:17.937Z" }, + { url = "https://files.pythonhosted.org/packages/d6/93/e48962aca19af1f3d2cb0c2ff890ca305c51d1759a2e89c90a527228cf1d/psycopg_binary-3.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3434efe7c00f505f4c1e531519dac6c701df738ba7a1328eac81118d80019132", size = 4523096, upload_time = "2025-03-12T20:38:24.054Z" }, + { url = "https://files.pythonhosted.org/packages/fe/52/21f4a9bb7123e07e06a712338eb6cc5794a23a56813deb4a8cd3de8ec91c/psycopg_binary-3.2.6-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bca8d9643191b13193940bbf84d51ac5a747e965c230177258fb02b8043fb7a", size = 4329659, upload_time = "2025-03-12T20:38:29.509Z" }, + { url = "https://files.pythonhosted.org/packages/9e/72/8da1c98b4e0d4c3649f037101b70ae52e4f821597919dabc72c889e60ca9/psycopg_binary-3.2.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55fa40f11d37e6e5149a282a5fd7e0734ce55c623673bfba638480914fd1414c", size = 4575359, upload_time = "2025-03-12T20:38:33.493Z" }, + { url = "https://files.pythonhosted.org/packages/83/5a/a85c98a5b2b3f771d7478ac0081b48749d4c07ce41d51f89f592f87cfbeb/psycopg_binary-3.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0690ac1061c655b1bcbe9284d07bf5276bc9c0d788a6c74aaf3b042e64984b83", size = 4287138, upload_time = "2025-03-12T20:38:39.283Z" }, + { url = "https://files.pythonhosted.org/packages/b0/c3/0abafd3f300e5ff952dd9b3be95b4e2527ae1e2ea7fd7a7421e6bc1c0e37/psycopg_binary-3.2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e9a4a9967ff650d2821d5fad6bec7b15f4c2072603e9fa3f89a39f351ade1fd3", size = 3872142, upload_time = "2025-03-12T20:38:44.473Z" }, + { url = "https://files.pythonhosted.org/packages/0f/16/029aa400c4b7f4b7042307d8a854768463a65326d061ad2145f7b3989ca5/psycopg_binary-3.2.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:d6f2894cc7aee8a15fe591e8536911d9c015cb404432cf7bdac2797e54cb2ba8", size = 3340033, upload_time = "2025-03-12T20:38:48.139Z" }, + { url = "https://files.pythonhosted.org/packages/cd/a1/28e86b832d696ba5fd79c4d704b8ca46b827428f7ea063063ca280a678a4/psycopg_binary-3.2.6-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:05560c81312d7c2bee95a9860cd25198677f2320fb4a3527bc04e8cae7fcfb64", size = 3438823, upload_time = "2025-03-12T20:38:51.76Z" }, + { url = "https://files.pythonhosted.org/packages/93/31/73546c999725b397bb7e7fd55f83a9c78787c6fe7fe457e4179d19a115dc/psycopg_binary-3.2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:4269cd23a485d6dd6eb6b10841c94551a53091cf0b1b6d5247a6a341f53f0d95", size = 3464031, upload_time = "2025-03-12T20:38:56.384Z" }, + { url = "https://files.pythonhosted.org/packages/85/38/957bd4bdde976c9a38d61896bf9d2c8f5752b98d8f4d879a7902588a8583/psycopg_binary-3.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:7942f35a6f314608720116bcd9de240110ceadffd2ac5c34f68f74a31e52e46a", size = 2792159, upload_time = "2025-03-12T20:39:01Z" }, + { url = "https://files.pythonhosted.org/packages/5a/71/5bfa1ffc4d59f0454b114ce0d017eca269b079ca2753a96302c2117067c7/psycopg_binary-3.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7afe181f6b3eb714362e9b6a2dc2a589bff60471a1d8639fd231a4e426e01523", size = 3876608, upload_time = "2025-03-12T20:39:06.505Z" }, + { url = "https://files.pythonhosted.org/packages/7e/07/1724d842b876af7bef442f0853d6cbf951264229414e4d0a57b8e3787847/psycopg_binary-3.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:34bb0fceba0773dc0bfb53224bb2c0b19dc97ea0a997a223615484cf02cae55c", size = 3942785, upload_time = "2025-03-12T20:39:11.593Z" }, + { url = "https://files.pythonhosted.org/packages/09/51/a251a356f10c7947bcc2285ebf1541e1c2d851b8db20eb8f29ed3a5974bf/psycopg_binary-3.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54120122d2779dcd307f49e1f921d757fe5dacdced27deab37f277eef0c52a5b", size = 4519448, upload_time = "2025-03-12T20:39:17.693Z" }, + { url = "https://files.pythonhosted.org/packages/6e/cf/0c92ab1270664a1341e52f5794ecc636b1f4ac67bf1743075091795151f8/psycopg_binary-3.2.6-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:816aa556f63b2303e66ba6c8888a8b3f3e6e4e47049ec7a4d62c84ac60b091ca", size = 4324382, upload_time = "2025-03-12T20:39:23.044Z" }, + { url = "https://files.pythonhosted.org/packages/bf/2b/6921bd4a57fe19d4618798a8a8648e1a516c92563c37b2073639fffac5d5/psycopg_binary-3.2.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d19a0ba351eda9a59babf8c7c9d89c7bbc5b26bf096bc349b096bd0dd2482088", size = 4578720, upload_time = "2025-03-12T20:39:27.305Z" }, + { url = "https://files.pythonhosted.org/packages/5c/30/1034d164e2be09f650a86eccc93625e51568e307c855bf6f94759c298303/psycopg_binary-3.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d6e197e01290ef818a092c877025fc28096adbb6d0743e313491a21aab31bd96", size = 4281871, upload_time = "2025-03-12T20:39:31.59Z" }, + { url = "https://files.pythonhosted.org/packages/c4/d0/67fdf0174c334a9a85a9672590d7da83e85d9cedfc35f473a557e310a1ca/psycopg_binary-3.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:274794b4b29ef426e09086404446b61a146f5e756da71366c5a6d57abec31f7d", size = 3870582, upload_time = "2025-03-12T20:39:35.779Z" }, + { url = "https://files.pythonhosted.org/packages/9f/4e/3a4fd2d1fd715b11e7287023edde916e1174b58b37873c531f782a49803b/psycopg_binary-3.2.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:69845bdc0db519e1dfc27932cd3d5b1ecb3f72950af52a1987508ab0b52b3b55", size = 3334464, upload_time = "2025-03-12T20:39:39.318Z" }, + { url = "https://files.pythonhosted.org/packages/4a/22/90a8032276fa5b215ce26cefb44abafa8fb09de396c6dc6f62a5e53fe2ad/psycopg_binary-3.2.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:66c3bed2caf0d1cabcb9365064de183b5209a7cbeaa131e79e68f350c9c963c2", size = 3431945, upload_time = "2025-03-12T20:39:43.17Z" }, + { url = "https://files.pythonhosted.org/packages/e7/b0/e547e9a851ab19c79869c1d84a533e225d284e70c222720fed4529fcda60/psycopg_binary-3.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e3ae3201fe85c7f901349a2cf52f02ceca4cb97a5e2e2ac8b8a1c9a6eb747bed", size = 3463278, upload_time = "2025-03-12T20:39:46.436Z" }, + { url = "https://files.pythonhosted.org/packages/e7/ce/e555bd8dd6fce8b34bbc3856125600f0842c85a8364702ebe0dc39372087/psycopg_binary-3.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:58f443b4df2adb59937c96775fadf4967f93d952fbcc82394446985faec11041", size = 2795094, upload_time = "2025-03-12T20:39:49.856Z" }, + { url = "https://files.pythonhosted.org/packages/a3/c7/220b1273f0befb2cd9fe83d379b3484ae029a88798a90bc0d36f10bea5df/psycopg_binary-3.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f27a46ff0497e882e8c0286e8833c785b4d1a80f23e1bf606f4c90e5f9f3ce75", size = 3857986, upload_time = "2025-03-12T20:39:54.482Z" }, + { url = "https://files.pythonhosted.org/packages/8a/d8/30176532826cf87c608a6f79dd668bf9aff0cdf8eb80209eddf4c5aa7229/psycopg_binary-3.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b30ee4821ded7de48b8048b14952512588e7c5477b0a5965221e1798afba61a1", size = 3940060, upload_time = "2025-03-12T20:39:58.835Z" }, + { url = "https://files.pythonhosted.org/packages/54/7c/fa7cd1f057f33f7ae483d6bc5a03ec6eff111f8aa5c678d9aaef92705247/psycopg_binary-3.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e57edf3b1f5427f39660225b01f8e7b97f5cfab132092f014bf1638bc85d81d2", size = 4499082, upload_time = "2025-03-12T20:40:03.605Z" }, + { url = "https://files.pythonhosted.org/packages/b8/81/1606966f6146187c273993ea6f88f2151b26741df8f4e01349a625983be9/psycopg_binary-3.2.6-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c5172ce3e4ae7a4fd450070210f801e2ce6bc0f11d1208d29268deb0cda34de", size = 4307509, upload_time = "2025-03-12T20:40:07.996Z" }, + { url = "https://files.pythonhosted.org/packages/69/ad/01c87aab17a4b89128b8036800d11ab296c7c2c623940cc7e6f2668f375a/psycopg_binary-3.2.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bcfab3804c43571a6615e559cdc4c4115785d258a4dd71a721be033f5f5f378d", size = 4547813, upload_time = "2025-03-12T20:40:12.48Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/f93a193846ee738ffe5d2a4837e7ddeb7279707af81d088cee96cae853a0/psycopg_binary-3.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8fa1c920cce16f1205f37b20c685c58b9656b170b8b4c93629100d342d0d118e", size = 4259847, upload_time = "2025-03-12T20:40:17.684Z" }, + { url = "https://files.pythonhosted.org/packages/8e/73/65c4ae71be86675a62154407c92af4b917146f9ff3baaf0e4166c0734aeb/psycopg_binary-3.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2e118d818101c1608c6b5ba52a6c977614d8f05aa89467501172ba4d10588e11", size = 3846550, upload_time = "2025-03-12T20:40:22.336Z" }, + { url = "https://files.pythonhosted.org/packages/53/cc/a24626cac3f208c776bb22e15e9a5e483aa81145221e6427e50381f40811/psycopg_binary-3.2.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:763319a8bfeca77d31512da71f5a33459b9568a7621c481c3828c62f9c38f351", size = 3320269, upload_time = "2025-03-12T20:40:26.919Z" }, + { url = "https://files.pythonhosted.org/packages/55/e6/68c76fb9d6c53d5e4170a0c9216c7aa6c2903808f626d84d002b47a16931/psycopg_binary-3.2.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:2fbc05819560389dbece046966bc88e0f2ea77673497e274c4293b8b4c1d0703", size = 3399365, upload_time = "2025-03-12T20:40:30.945Z" }, + { url = "https://files.pythonhosted.org/packages/b4/2c/55b140f5a2c582dae42ef38502c45ef69c938274242a40bd04c143081029/psycopg_binary-3.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5a57f99bb953b4bd6f32d0a9844664e7f6ca5ead9ba40e96635be3cd30794813", size = 3438908, upload_time = "2025-03-12T20:40:34.926Z" }, + { url = "https://files.pythonhosted.org/packages/ae/f6/589c95cceccee2ab408b6b2e16f1ed6db4536fb24f2f5c9ce568cf43270c/psycopg_binary-3.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:5de6809e19a465dcb9c269675bded46a135f2d600cd99f0735afbb21ddad2af4", size = 2782886, upload_time = "2025-03-12T20:40:38.493Z" }, + { url = "https://files.pythonhosted.org/packages/bf/32/3d06c478fd3070ac25a49c2e8ca46b6d76b0048fa9fa255b99ee32f32312/psycopg_binary-3.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:54af3fbf871baa2eb19df96fd7dc0cbd88e628a692063c3d1ab5cdd00aa04322", size = 3852672, upload_time = "2025-03-12T20:40:42.083Z" }, + { url = "https://files.pythonhosted.org/packages/34/97/e581030e279500ede3096adb510f0e6071874b97cfc047a9a87b7d71fc77/psycopg_binary-3.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ad5da1e4636776c21eaeacdec42f25fa4612631a12f25cd9ab34ddf2c346ffb9", size = 3936562, upload_time = "2025-03-12T20:40:46.709Z" }, + { url = "https://files.pythonhosted.org/packages/74/b6/6a8df4cb23c3d327403a83406c06c9140f311cb56c4e4d720ee7abf6fddc/psycopg_binary-3.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7956b9ea56f79cd86eddcfbfc65ae2af1e4fe7932fa400755005d903c709370", size = 4499167, upload_time = "2025-03-12T20:40:51.978Z" }, + { url = "https://files.pythonhosted.org/packages/e4/5b/950eafef61e5e0b8ddb5afc5b6b279756411aa4bf70a346a6f091ad679bb/psycopg_binary-3.2.6-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e2efb763188008cf2914820dcb9fb23c10fe2be0d2c97ef0fac7cec28e281d8", size = 4311651, upload_time = "2025-03-12T20:40:56.99Z" }, + { url = "https://files.pythonhosted.org/packages/72/b9/b366c49afc854c26b3053d4d35376046eea9aebdc48ded18ea249ea1f80c/psycopg_binary-3.2.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4b3aab3451679f1e7932270e950259ed48c3b79390022d3f660491c0e65e4838", size = 4547852, upload_time = "2025-03-12T20:41:01.379Z" }, + { url = "https://files.pythonhosted.org/packages/ab/d4/0e047360e2ea387dc7171ca017ffcee5214a0762f74b9dd982035f2e52fb/psycopg_binary-3.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:849a370ac4e125f55f2ad37f928e588291a67ccf91fa33d0b1e042bb3ee1f986", size = 4261725, upload_time = "2025-03-12T20:41:05.576Z" }, + { url = "https://files.pythonhosted.org/packages/e3/ea/a1b969804250183900959ebe845d86be7fed2cbd9be58f64cd0fc24b2892/psycopg_binary-3.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:566d4ace928419d91f1eb3227fc9ef7b41cf0ad22e93dd2c3368d693cf144408", size = 3850073, upload_time = "2025-03-12T20:41:10.362Z" }, + { url = "https://files.pythonhosted.org/packages/e5/71/ec2907342f0675092b76aea74365b56f38d960c4c635984dcfe25d8178c8/psycopg_binary-3.2.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f1981f13b10de2f11cfa2f99a8738b35b3f0a0f3075861446894a8d3042430c0", size = 3320323, upload_time = "2025-03-12T20:41:14.729Z" }, + { url = "https://files.pythonhosted.org/packages/d7/d7/0d2cb4b42f231e2efe8ea1799ce917973d47486212a2c4d33cd331e7ac28/psycopg_binary-3.2.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:36f598300b55b3c983ae8df06473ad27333d2fd9f3e2cfdb913b3a5aaa3a8bcf", size = 3402335, upload_time = "2025-03-12T20:41:19.103Z" }, + { url = "https://files.pythonhosted.org/packages/66/92/7050c372f78e53eba14695cec6c3a91b2d9ca56feaf0bfe95fe90facf730/psycopg_binary-3.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0f4699fa5fe1fffb0d6b2d14b31fd8c29b7ea7375f89d5989f002aaf21728b21", size = 3440442, upload_time = "2025-03-12T20:41:23.979Z" }, + { url = "https://files.pythonhosted.org/packages/5f/4c/bebcaf754189283b2f3d457822a3d9b233d08ff50973d8f1e8d51f4d35ed/psycopg_binary-3.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:afe697b8b0071f497c5d4c0f41df9e038391534f5614f7fb3a8c1ca32d66e860", size = 2783465, upload_time = "2025-03-12T20:41:30.32Z" }, + { url = "https://files.pythonhosted.org/packages/be/ac/5023320c46e0d233faff6cd2d902a60d6c09034f136e7cc0876463a97d50/psycopg_binary-3.2.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:260c43c329e668606388cee78ec0dab083a25c2c6e6f9cf74a130fd5a27b0f87", size = 3870121, upload_time = "2025-03-12T20:42:23.415Z" }, + { url = "https://files.pythonhosted.org/packages/8d/2d/63cdd41112c954582d6bb8905970abe5e5a69b53d6dd2a38fc7ec624a6b4/psycopg_binary-3.2.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9870e51fad4684dbdec057fa757d65e61cb2acb16236836e9360044c2a1ec880", size = 3939150, upload_time = "2025-03-12T20:42:29.926Z" }, + { url = "https://files.pythonhosted.org/packages/a8/c6/0a0fe3b5506d30d597c82b16b8c1e023393a10701109e917c4cb529ccc71/psycopg_binary-3.2.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:030e9c3082a931e972b029b3cef085784a3bf7f8e18367ae50d5b809aa6e1d87", size = 4525092, upload_time = "2025-03-12T20:42:35.657Z" }, + { url = "https://files.pythonhosted.org/packages/0d/66/66c3c05a0535c022b1b8af501aec05161084e6a4ab44edebd99ff82de976/psycopg_binary-3.2.6-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b60c9ed291fbd5e777c2c630dcfd10b7a87d68512b0757d5e7406d9c4895a82a", size = 4330350, upload_time = "2025-03-12T20:42:40.696Z" }, + { url = "https://files.pythonhosted.org/packages/90/f3/15ce9800b54c50ff01af1c39d0a727083bb3640067a54b58631ca6666b24/psycopg_binary-3.2.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e0f4a17a9c376c195e403b4826c18f325bd28f425231d36d1036258bf893e23", size = 4577917, upload_time = "2025-03-12T20:42:45.669Z" }, + { url = "https://files.pythonhosted.org/packages/a0/aa/ffe622452a43376193d3d1e63b479e0f90ce9732c91366c05a16ce49e513/psycopg_binary-3.2.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac46da609624b16d961f604b3cbc3233ef43211ef1456a188f8c427109c9c3e1", size = 4286328, upload_time = "2025-03-12T20:42:50.336Z" }, + { url = "https://files.pythonhosted.org/packages/bf/bf/53da912ba8ace3136318718112c5db85bf0ed4f079f92849536a78384875/psycopg_binary-3.2.6-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:e77949b8e7014b85cee0bf6e9e041bcae7719b2693ebf59236368fb0b2a08814", size = 3874534, upload_time = "2025-03-12T20:42:54.649Z" }, + { url = "https://files.pythonhosted.org/packages/1a/b5/e659e96ffc6846d3f90c4af785c84898e625f974c7f67255688addda6167/psycopg_binary-3.2.6-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:532322d9ef6e7d178a4f344970b017110633bcc3dc1c3403efcef55aad612517", size = 3339634, upload_time = "2025-03-12T20:42:58.755Z" }, + { url = "https://files.pythonhosted.org/packages/9b/d6/92858290a8d710a27caf1b92183f4fa2ef3b99c9b6959fc73fd9639b8412/psycopg_binary-3.2.6-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:880c5fd76dcb50bdcc8f87359e5a6c7eb416697cc9aa02854c91223bd999c045", size = 3437930, upload_time = "2025-03-12T20:43:02.955Z" }, + { url = "https://files.pythonhosted.org/packages/f7/a5/60b5a6519f58b2deca261d2e4566bce4beeeb30f6e7c3f72e4d8f204dcf6/psycopg_binary-3.2.6-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3c0cddc7458b8416d77cd8829d0192466502f31d1fb853d58613cf13ac64f41c", size = 3464810, upload_time = "2025-03-12T20:43:07.18Z" }, + { url = "https://files.pythonhosted.org/packages/27/b6/e48cafe90e46b82b1393276ce6d92ddb7650c39aba8aa0256affbaa94c36/psycopg_binary-3.2.6-cp39-cp39-win_amd64.whl", hash = "sha256:ea158665676f42b19585dfe948071d3c5f28276f84a97522fb2e82c1d9194563", size = 2793902, upload_time = "2025-03-12T20:43:10.416Z" }, ] [[package]] name = "py" version = "1.11.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/98/ff/fec109ceb715d2a6b4c4a85a61af3b40c723a961e8828319fbcb15b868dc/py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719", size = 207796 } +sdist = { url = "https://files.pythonhosted.org/packages/98/ff/fec109ceb715d2a6b4c4a85a61af3b40c723a961e8828319fbcb15b868dc/py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719", size = 207796, upload_time = "2021-11-04T17:17:01.377Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f6/f0/10642828a8dfb741e5f3fbaac830550a518a775c7fff6f04a007259b0548/py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378", size = 98708 }, + { url = "https://files.pythonhosted.org/packages/f6/f0/10642828a8dfb741e5f3fbaac830550a518a775c7fff6f04a007259b0548/py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378", size = 98708, upload_time = "2021-11-04T17:17:00.152Z" }, ] [[package]] name = "py-spy" version = "0.4.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7c/cd/9dacc04604dc4398ce5bed77ed59918ad0940f15165954d4aaa651cc640c/py_spy-0.4.0.tar.gz", hash = "sha256:806602ce7972782cc9c1e383f339bfc27bfb822d42485e6a3e0530ae5040e1f0", size = 253236 } +sdist = { url = "https://files.pythonhosted.org/packages/7c/cd/9dacc04604dc4398ce5bed77ed59918ad0940f15165954d4aaa651cc640c/py_spy-0.4.0.tar.gz", hash = "sha256:806602ce7972782cc9c1e383f339bfc27bfb822d42485e6a3e0530ae5040e1f0", size = 253236, upload_time = "2024-11-01T19:08:51.487Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/7e/02ca3ee68507db47afce769504060d71b4dc1455f0f9faa8d32fc7762221/py_spy-0.4.0-py2.py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:f2cf3f7130e7d780471faa5957441d3b4e0ec39a79b2c00f4c33d494f7728428", size = 3617847 }, - { url = "https://files.pythonhosted.org/packages/65/7c/d9e26cc4c8e91f96a3a65de04d2e2e4131fbcaf6830d10917d4fab9d6788/py_spy-0.4.0-py2.py3-none-macosx_11_0_arm64.whl", hash = "sha256:47cdda4c34d9b6cb01f3aaeceb2e88faf57da880207fe72ff6ff97e9bb6cc8a9", size = 1761955 }, - { url = "https://files.pythonhosted.org/packages/d2/e4/8fbfd219b7f282b80e6b2e74c9197850d2c51db8555705567bb65507b060/py_spy-0.4.0-py2.py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eee3d0bde85ca5cf4f01f012d461180ca76c24835a96f7b5c4ded64eb6a008ab", size = 2059471 }, - { url = "https://files.pythonhosted.org/packages/a7/1d/79a94a5ace810c13b730ce96765ca465c171b4952034f1be7402d8accbc1/py_spy-0.4.0-py2.py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c5f06ffce4c9c98b7fc9f5e67e5e7db591173f1351837633f3f23d9378b1d18a", size = 2067486 }, - { url = "https://files.pythonhosted.org/packages/6d/90/fbbb038f826a83ed15ebc4ae606815d6cad6c5c6399c86c7ab96f6c60817/py_spy-0.4.0-py2.py3-none-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:87573e64dbfdfc89ba2e0f5e2f525aa84e0299c7eb6454b47ea335fde583a7a0", size = 2141433 }, - { url = "https://files.pythonhosted.org/packages/c9/c1/5e012669ebb687e546dc99fcfc4861ebfcf3a337b7a41af945df23140bb5/py_spy-0.4.0-py2.py3-none-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:8bf2f3702cef367a489faa45177b41a6c31b2a3e5bd78c978d44e29340152f5a", size = 2732951 }, - { url = "https://files.pythonhosted.org/packages/74/8b/dd8490660019a6b0be28d9ffd2bf1db967604b19f3f2719c0e283a16ac7f/py_spy-0.4.0-py2.py3-none-win_amd64.whl", hash = "sha256:77d8f637ade38367d944874776f45b703b7ac5938b1f7be8891f3a5876ddbb96", size = 1810770 }, + { url = "https://files.pythonhosted.org/packages/6a/7e/02ca3ee68507db47afce769504060d71b4dc1455f0f9faa8d32fc7762221/py_spy-0.4.0-py2.py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:f2cf3f7130e7d780471faa5957441d3b4e0ec39a79b2c00f4c33d494f7728428", size = 3617847, upload_time = "2024-11-01T19:08:37.44Z" }, + { url = "https://files.pythonhosted.org/packages/65/7c/d9e26cc4c8e91f96a3a65de04d2e2e4131fbcaf6830d10917d4fab9d6788/py_spy-0.4.0-py2.py3-none-macosx_11_0_arm64.whl", hash = "sha256:47cdda4c34d9b6cb01f3aaeceb2e88faf57da880207fe72ff6ff97e9bb6cc8a9", size = 1761955, upload_time = "2024-11-01T19:08:39.632Z" }, + { url = "https://files.pythonhosted.org/packages/d2/e4/8fbfd219b7f282b80e6b2e74c9197850d2c51db8555705567bb65507b060/py_spy-0.4.0-py2.py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eee3d0bde85ca5cf4f01f012d461180ca76c24835a96f7b5c4ded64eb6a008ab", size = 2059471, upload_time = "2024-11-01T19:08:41.818Z" }, + { url = "https://files.pythonhosted.org/packages/a7/1d/79a94a5ace810c13b730ce96765ca465c171b4952034f1be7402d8accbc1/py_spy-0.4.0-py2.py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c5f06ffce4c9c98b7fc9f5e67e5e7db591173f1351837633f3f23d9378b1d18a", size = 2067486, upload_time = "2024-11-01T19:08:43.673Z" }, + { url = "https://files.pythonhosted.org/packages/6d/90/fbbb038f826a83ed15ebc4ae606815d6cad6c5c6399c86c7ab96f6c60817/py_spy-0.4.0-py2.py3-none-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:87573e64dbfdfc89ba2e0f5e2f525aa84e0299c7eb6454b47ea335fde583a7a0", size = 2141433, upload_time = "2024-11-01T19:08:45.988Z" }, + { url = "https://files.pythonhosted.org/packages/c9/c1/5e012669ebb687e546dc99fcfc4861ebfcf3a337b7a41af945df23140bb5/py_spy-0.4.0-py2.py3-none-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:8bf2f3702cef367a489faa45177b41a6c31b2a3e5bd78c978d44e29340152f5a", size = 2732951, upload_time = "2024-11-01T19:08:48.109Z" }, + { url = "https://files.pythonhosted.org/packages/74/8b/dd8490660019a6b0be28d9ffd2bf1db967604b19f3f2719c0e283a16ac7f/py_spy-0.4.0-py2.py3-none-win_amd64.whl", hash = "sha256:77d8f637ade38367d944874776f45b703b7ac5938b1f7be8891f3a5876ddbb96", size = 1810770, upload_time = "2024-11-01T19:08:50.229Z" }, ] [[package]] name = "pyasn1" version = "0.6.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ba/e9/01f1a64245b89f039897cb0130016d79f77d52669aae6ee7b159a6c4c018/pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034", size = 145322 } +sdist = { url = "https://files.pythonhosted.org/packages/ba/e9/01f1a64245b89f039897cb0130016d79f77d52669aae6ee7b159a6c4c018/pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034", size = 145322, upload_time = "2024-09-10T22:41:42.55Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c8/f1/d6a797abb14f6283c0ddff96bbdd46937f64122b8c925cab503dd37f8214/pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629", size = 83135 }, + { url = "https://files.pythonhosted.org/packages/c8/f1/d6a797abb14f6283c0ddff96bbdd46937f64122b8c925cab503dd37f8214/pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629", size = 83135, upload_time = "2024-09-11T16:00:36.122Z" }, ] [[package]] @@ -1136,9 +1138,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyasn1" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e9/e6/78ebbb10a8c8e4b61a59249394a4a594c1a7af95593dc933a349c8d00964/pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6", size = 307892 } +sdist = { url = "https://files.pythonhosted.org/packages/e9/e6/78ebbb10a8c8e4b61a59249394a4a594c1a7af95593dc933a349c8d00964/pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6", size = 307892, upload_time = "2025-03-28T02:41:22.17Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/47/8d/d529b5d697919ba8c11ad626e835d4039be708a35b0d22de83a269a6682c/pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a", size = 181259 }, + { url = "https://files.pythonhosted.org/packages/47/8d/d529b5d697919ba8c11ad626e835d4039be708a35b0d22de83a269a6682c/pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a", size = 181259, upload_time = "2025-03-28T02:41:19.028Z" }, ] [[package]] @@ -1151,9 +1153,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b0/41/832125a41fe098b58d1fdd04ae819b4dc6b34d6b09ed78304fd93d4bc051/pydantic-2.11.2.tar.gz", hash = "sha256:2138628e050bd7a1e70b91d4bf4a91167f4ad76fdb83209b107c8d84b854917e", size = 784742 } +sdist = { url = "https://files.pythonhosted.org/packages/b0/41/832125a41fe098b58d1fdd04ae819b4dc6b34d6b09ed78304fd93d4bc051/pydantic-2.11.2.tar.gz", hash = "sha256:2138628e050bd7a1e70b91d4bf4a91167f4ad76fdb83209b107c8d84b854917e", size = 784742, upload_time = "2025-04-03T13:12:49.947Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bf/c2/0f3baea344d0b15e35cb3e04ad5b953fa05106b76efbf4c782a3f47f22f5/pydantic-2.11.2-py3-none-any.whl", hash = "sha256:7f17d25846bcdf89b670a86cdfe7b29a9f1c9ca23dee154221c9aa81845cfca7", size = 443295 }, + { url = "https://files.pythonhosted.org/packages/bf/c2/0f3baea344d0b15e35cb3e04ad5b953fa05106b76efbf4c782a3f47f22f5/pydantic-2.11.2-py3-none-any.whl", hash = "sha256:7f17d25846bcdf89b670a86cdfe7b29a9f1c9ca23dee154221c9aa81845cfca7", size = 443295, upload_time = "2025-04-03T13:12:47.995Z" }, ] [[package]] @@ -1163,124 +1165,124 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/17/19/ed6a078a5287aea7922de6841ef4c06157931622c89c2a47940837b5eecd/pydantic_core-2.33.1.tar.gz", hash = "sha256:bcc9c6fdb0ced789245b02b7d6603e17d1563064ddcfc36f046b61c0c05dd9df", size = 434395 } +sdist = { url = "https://files.pythonhosted.org/packages/17/19/ed6a078a5287aea7922de6841ef4c06157931622c89c2a47940837b5eecd/pydantic_core-2.33.1.tar.gz", hash = "sha256:bcc9c6fdb0ced789245b02b7d6603e17d1563064ddcfc36f046b61c0c05dd9df", size = 434395, upload_time = "2025-04-02T09:49:41.8Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/38/ea/5f572806ab4d4223d11551af814d243b0e3e02cc6913def4d1fe4a5ca41c/pydantic_core-2.33.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3077cfdb6125cc8dab61b155fdd714663e401f0e6883f9632118ec12cf42df26", size = 2044021 }, - { url = "https://files.pythonhosted.org/packages/8c/d1/f86cc96d2aa80e3881140d16d12ef2b491223f90b28b9a911346c04ac359/pydantic_core-2.33.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8ffab8b2908d152e74862d276cf5017c81a2f3719f14e8e3e8d6b83fda863927", size = 1861742 }, - { url = "https://files.pythonhosted.org/packages/37/08/fbd2cd1e9fc735a0df0142fac41c114ad9602d1c004aea340169ae90973b/pydantic_core-2.33.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5183e4f6a2d468787243ebcd70cf4098c247e60d73fb7d68d5bc1e1beaa0c4db", size = 1910414 }, - { url = "https://files.pythonhosted.org/packages/7f/73/3ac217751decbf8d6cb9443cec9b9eb0130eeada6ae56403e11b486e277e/pydantic_core-2.33.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:398a38d323f37714023be1e0285765f0a27243a8b1506b7b7de87b647b517e48", size = 1996848 }, - { url = "https://files.pythonhosted.org/packages/9a/f5/5c26b265cdcff2661e2520d2d1e9db72d117ea00eb41e00a76efe68cb009/pydantic_core-2.33.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:87d3776f0001b43acebfa86f8c64019c043b55cc5a6a2e313d728b5c95b46969", size = 2141055 }, - { url = "https://files.pythonhosted.org/packages/5d/14/a9c3cee817ef2f8347c5ce0713e91867a0dceceefcb2973942855c917379/pydantic_core-2.33.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c566dd9c5f63d22226409553531f89de0cac55397f2ab8d97d6f06cfce6d947e", size = 2753806 }, - { url = "https://files.pythonhosted.org/packages/f2/68/866ce83a51dd37e7c604ce0050ff6ad26de65a7799df89f4db87dd93d1d6/pydantic_core-2.33.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0d5f3acc81452c56895e90643a625302bd6be351e7010664151cc55b7b97f89", size = 2007777 }, - { url = "https://files.pythonhosted.org/packages/b6/a8/36771f4404bb3e49bd6d4344da4dede0bf89cc1e01f3b723c47248a3761c/pydantic_core-2.33.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d3a07fadec2a13274a8d861d3d37c61e97a816beae717efccaa4b36dfcaadcde", size = 2122803 }, - { url = "https://files.pythonhosted.org/packages/18/9c/730a09b2694aa89360d20756369822d98dc2f31b717c21df33b64ffd1f50/pydantic_core-2.33.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f99aeda58dce827f76963ee87a0ebe75e648c72ff9ba1174a253f6744f518f65", size = 2086755 }, - { url = "https://files.pythonhosted.org/packages/54/8e/2dccd89602b5ec31d1c58138d02340ecb2ebb8c2cac3cc66b65ce3edb6ce/pydantic_core-2.33.1-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:902dbc832141aa0ec374f4310f1e4e7febeebc3256f00dc359a9ac3f264a45dc", size = 2257358 }, - { url = "https://files.pythonhosted.org/packages/d1/9c/126e4ac1bfad8a95a9837acdd0963695d69264179ba4ede8b8c40d741702/pydantic_core-2.33.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fe44d56aa0b00d66640aa84a3cbe80b7a3ccdc6f0b1ca71090696a6d4777c091", size = 2257916 }, - { url = "https://files.pythonhosted.org/packages/7d/ba/91eea2047e681a6853c81c20aeca9dcdaa5402ccb7404a2097c2adf9d038/pydantic_core-2.33.1-cp310-cp310-win32.whl", hash = "sha256:ed3eb16d51257c763539bde21e011092f127a2202692afaeaccb50db55a31383", size = 1923823 }, - { url = "https://files.pythonhosted.org/packages/94/c0/fcdf739bf60d836a38811476f6ecd50374880b01e3014318b6e809ddfd52/pydantic_core-2.33.1-cp310-cp310-win_amd64.whl", hash = "sha256:694ad99a7f6718c1a498dc170ca430687a39894a60327f548e02a9c7ee4b6504", size = 1952494 }, - { url = "https://files.pythonhosted.org/packages/d6/7f/c6298830cb780c46b4f46bb24298d01019ffa4d21769f39b908cd14bbd50/pydantic_core-2.33.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:6e966fc3caaf9f1d96b349b0341c70c8d6573bf1bac7261f7b0ba88f96c56c24", size = 2044224 }, - { url = "https://files.pythonhosted.org/packages/a8/65/6ab3a536776cad5343f625245bd38165d6663256ad43f3a200e5936afd6c/pydantic_core-2.33.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bfd0adeee563d59c598ceabddf2c92eec77abcb3f4a391b19aa7366170bd9e30", size = 1858845 }, - { url = "https://files.pythonhosted.org/packages/e9/15/9a22fd26ba5ee8c669d4b8c9c244238e940cd5d818649603ca81d1c69861/pydantic_core-2.33.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91815221101ad3c6b507804178a7bb5cb7b2ead9ecd600041669c8d805ebd595", size = 1910029 }, - { url = "https://files.pythonhosted.org/packages/d5/33/8cb1a62818974045086f55f604044bf35b9342900318f9a2a029a1bec460/pydantic_core-2.33.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9fea9c1869bb4742d174a57b4700c6dadea951df8b06de40c2fedb4f02931c2e", size = 1997784 }, - { url = "https://files.pythonhosted.org/packages/c0/ca/49958e4df7715c71773e1ea5be1c74544923d10319173264e6db122543f9/pydantic_core-2.33.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d20eb4861329bb2484c021b9d9a977566ab16d84000a57e28061151c62b349a", size = 2141075 }, - { url = "https://files.pythonhosted.org/packages/7b/a6/0b3a167a9773c79ba834b959b4e18c3ae9216b8319bd8422792abc8a41b1/pydantic_core-2.33.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb935c5591573ae3201640579f30128ccc10739b45663f93c06796854405505", size = 2745849 }, - { url = "https://files.pythonhosted.org/packages/0b/60/516484135173aa9e5861d7a0663dce82e4746d2e7f803627d8c25dfa5578/pydantic_core-2.33.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c964fd24e6166420d18fb53996d8c9fd6eac9bf5ae3ec3d03015be4414ce497f", size = 2005794 }, - { url = "https://files.pythonhosted.org/packages/86/70/05b1eb77459ad47de00cf78ee003016da0cedf8b9170260488d7c21e9181/pydantic_core-2.33.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:681d65e9011f7392db5aa002b7423cc442d6a673c635668c227c6c8d0e5a4f77", size = 2123237 }, - { url = "https://files.pythonhosted.org/packages/c7/57/12667a1409c04ae7dc95d3b43158948eb0368e9c790be8b095cb60611459/pydantic_core-2.33.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e100c52f7355a48413e2999bfb4e139d2977a904495441b374f3d4fb4a170961", size = 2086351 }, - { url = "https://files.pythonhosted.org/packages/57/61/cc6d1d1c1664b58fdd6ecc64c84366c34ec9b606aeb66cafab6f4088974c/pydantic_core-2.33.1-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:048831bd363490be79acdd3232f74a0e9951b11b2b4cc058aeb72b22fdc3abe1", size = 2258914 }, - { url = "https://files.pythonhosted.org/packages/d1/0a/edb137176a1f5419b2ddee8bde6a0a548cfa3c74f657f63e56232df8de88/pydantic_core-2.33.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:bdc84017d28459c00db6f918a7272a5190bec3090058334e43a76afb279eac7c", size = 2257385 }, - { url = "https://files.pythonhosted.org/packages/26/3c/48ca982d50e4b0e1d9954919c887bdc1c2b462801bf408613ccc641b3daa/pydantic_core-2.33.1-cp311-cp311-win32.whl", hash = "sha256:32cd11c5914d1179df70406427097c7dcde19fddf1418c787540f4b730289896", size = 1923765 }, - { url = "https://files.pythonhosted.org/packages/33/cd/7ab70b99e5e21559f5de38a0928ea84e6f23fdef2b0d16a6feaf942b003c/pydantic_core-2.33.1-cp311-cp311-win_amd64.whl", hash = "sha256:2ea62419ba8c397e7da28a9170a16219d310d2cf4970dbc65c32faf20d828c83", size = 1950688 }, - { url = "https://files.pythonhosted.org/packages/4b/ae/db1fc237b82e2cacd379f63e3335748ab88b5adde98bf7544a1b1bd10a84/pydantic_core-2.33.1-cp311-cp311-win_arm64.whl", hash = "sha256:fc903512177361e868bc1f5b80ac8c8a6e05fcdd574a5fb5ffeac5a9982b9e89", size = 1908185 }, - { url = "https://files.pythonhosted.org/packages/c8/ce/3cb22b07c29938f97ff5f5bb27521f95e2ebec399b882392deb68d6c440e/pydantic_core-2.33.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:1293d7febb995e9d3ec3ea09caf1a26214eec45b0f29f6074abb004723fc1de8", size = 2026640 }, - { url = "https://files.pythonhosted.org/packages/19/78/f381d643b12378fee782a72126ec5d793081ef03791c28a0fd542a5bee64/pydantic_core-2.33.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:99b56acd433386c8f20be5c4000786d1e7ca0523c8eefc995d14d79c7a081498", size = 1852649 }, - { url = "https://files.pythonhosted.org/packages/9d/2b/98a37b80b15aac9eb2c6cfc6dbd35e5058a352891c5cce3a8472d77665a6/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35a5ec3fa8c2fe6c53e1b2ccc2454398f95d5393ab398478f53e1afbbeb4d939", size = 1892472 }, - { url = "https://files.pythonhosted.org/packages/4e/d4/3c59514e0f55a161004792b9ff3039da52448f43f5834f905abef9db6e4a/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b172f7b9d2f3abc0efd12e3386f7e48b576ef309544ac3a63e5e9cdd2e24585d", size = 1977509 }, - { url = "https://files.pythonhosted.org/packages/a9/b6/c2c7946ef70576f79a25db59a576bce088bdc5952d1b93c9789b091df716/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9097b9f17f91eea659b9ec58148c0747ec354a42f7389b9d50701610d86f812e", size = 2128702 }, - { url = "https://files.pythonhosted.org/packages/88/fe/65a880f81e3f2a974312b61f82a03d85528f89a010ce21ad92f109d94deb/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cc77ec5b7e2118b152b0d886c7514a4653bcb58c6b1d760134a9fab915f777b3", size = 2679428 }, - { url = "https://files.pythonhosted.org/packages/6f/ff/4459e4146afd0462fb483bb98aa2436d69c484737feaceba1341615fb0ac/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5e3d15245b08fa4a84cefc6c9222e6f37c98111c8679fbd94aa145f9a0ae23d", size = 2008753 }, - { url = "https://files.pythonhosted.org/packages/7c/76/1c42e384e8d78452ededac8b583fe2550c84abfef83a0552e0e7478ccbc3/pydantic_core-2.33.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ef99779001d7ac2e2461d8ab55d3373fe7315caefdbecd8ced75304ae5a6fc6b", size = 2114849 }, - { url = "https://files.pythonhosted.org/packages/00/72/7d0cf05095c15f7ffe0eb78914b166d591c0eed72f294da68378da205101/pydantic_core-2.33.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:fc6bf8869e193855e8d91d91f6bf59699a5cdfaa47a404e278e776dd7f168b39", size = 2069541 }, - { url = "https://files.pythonhosted.org/packages/b3/69/94a514066bb7d8be499aa764926937409d2389c09be0b5107a970286ef81/pydantic_core-2.33.1-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:b1caa0bc2741b043db7823843e1bde8aaa58a55a58fda06083b0569f8b45693a", size = 2239225 }, - { url = "https://files.pythonhosted.org/packages/84/b0/e390071eadb44b41f4f54c3cef64d8bf5f9612c92686c9299eaa09e267e2/pydantic_core-2.33.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ec259f62538e8bf364903a7d0d0239447059f9434b284f5536e8402b7dd198db", size = 2248373 }, - { url = "https://files.pythonhosted.org/packages/d6/b2/288b3579ffc07e92af66e2f1a11be3b056fe1214aab314748461f21a31c3/pydantic_core-2.33.1-cp312-cp312-win32.whl", hash = "sha256:e14f369c98a7c15772b9da98987f58e2b509a93235582838bd0d1d8c08b68fda", size = 1907034 }, - { url = "https://files.pythonhosted.org/packages/02/28/58442ad1c22b5b6742b992ba9518420235adced665513868f99a1c2638a5/pydantic_core-2.33.1-cp312-cp312-win_amd64.whl", hash = "sha256:1c607801d85e2e123357b3893f82c97a42856192997b95b4d8325deb1cd0c5f4", size = 1956848 }, - { url = "https://files.pythonhosted.org/packages/a1/eb/f54809b51c7e2a1d9f439f158b8dd94359321abcc98767e16fc48ae5a77e/pydantic_core-2.33.1-cp312-cp312-win_arm64.whl", hash = "sha256:8d13f0276806ee722e70a1c93da19748594f19ac4299c7e41237fc791d1861ea", size = 1903986 }, - { url = "https://files.pythonhosted.org/packages/7a/24/eed3466a4308d79155f1cdd5c7432c80ddcc4530ba8623b79d5ced021641/pydantic_core-2.33.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:70af6a21237b53d1fe7b9325b20e65cbf2f0a848cf77bed492b029139701e66a", size = 2033551 }, - { url = "https://files.pythonhosted.org/packages/ab/14/df54b1a0bc9b6ded9b758b73139d2c11b4e8eb43e8ab9c5847c0a2913ada/pydantic_core-2.33.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:282b3fe1bbbe5ae35224a0dbd05aed9ccabccd241e8e6b60370484234b456266", size = 1852785 }, - { url = "https://files.pythonhosted.org/packages/fa/96/e275f15ff3d34bb04b0125d9bc8848bf69f25d784d92a63676112451bfb9/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b315e596282bbb5822d0c7ee9d255595bd7506d1cb20c2911a4da0b970187d3", size = 1897758 }, - { url = "https://files.pythonhosted.org/packages/b7/d8/96bc536e975b69e3a924b507d2a19aedbf50b24e08c80fb00e35f9baaed8/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1dfae24cf9921875ca0ca6a8ecb4bb2f13c855794ed0d468d6abbec6e6dcd44a", size = 1986109 }, - { url = "https://files.pythonhosted.org/packages/90/72/ab58e43ce7e900b88cb571ed057b2fcd0e95b708a2e0bed475b10130393e/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6dd8ecfde08d8bfadaea669e83c63939af76f4cf5538a72597016edfa3fad516", size = 2129159 }, - { url = "https://files.pythonhosted.org/packages/dc/3f/52d85781406886c6870ac995ec0ba7ccc028b530b0798c9080531b409fdb/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2f593494876eae852dc98c43c6f260f45abdbfeec9e4324e31a481d948214764", size = 2680222 }, - { url = "https://files.pythonhosted.org/packages/f4/56/6e2ef42f363a0eec0fd92f74a91e0ac48cd2e49b695aac1509ad81eee86a/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:948b73114f47fd7016088e5186d13faf5e1b2fe83f5e320e371f035557fd264d", size = 2006980 }, - { url = "https://files.pythonhosted.org/packages/4c/c0/604536c4379cc78359f9ee0aa319f4aedf6b652ec2854953f5a14fc38c5a/pydantic_core-2.33.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e11f3864eb516af21b01e25fac915a82e9ddad3bb0fb9e95a246067398b435a4", size = 2120840 }, - { url = "https://files.pythonhosted.org/packages/1f/46/9eb764814f508f0edfb291a0f75d10854d78113fa13900ce13729aaec3ae/pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:549150be302428b56fdad0c23c2741dcdb5572413776826c965619a25d9c6bde", size = 2072518 }, - { url = "https://files.pythonhosted.org/packages/42/e3/fb6b2a732b82d1666fa6bf53e3627867ea3131c5f39f98ce92141e3e3dc1/pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:495bc156026efafd9ef2d82372bd38afce78ddd82bf28ef5276c469e57c0c83e", size = 2248025 }, - { url = "https://files.pythonhosted.org/packages/5c/9d/fbe8fe9d1aa4dac88723f10a921bc7418bd3378a567cb5e21193a3c48b43/pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ec79de2a8680b1a67a07490bddf9636d5c2fab609ba8c57597e855fa5fa4dacd", size = 2254991 }, - { url = "https://files.pythonhosted.org/packages/aa/99/07e2237b8a66438d9b26482332cda99a9acccb58d284af7bc7c946a42fd3/pydantic_core-2.33.1-cp313-cp313-win32.whl", hash = "sha256:ee12a7be1742f81b8a65b36c6921022301d466b82d80315d215c4c691724986f", size = 1915262 }, - { url = "https://files.pythonhosted.org/packages/8a/f4/e457a7849beeed1e5defbcf5051c6f7b3c91a0624dd31543a64fc9adcf52/pydantic_core-2.33.1-cp313-cp313-win_amd64.whl", hash = "sha256:ede9b407e39949d2afc46385ce6bd6e11588660c26f80576c11c958e6647bc40", size = 1956626 }, - { url = "https://files.pythonhosted.org/packages/20/d0/e8d567a7cff7b04e017ae164d98011f1e1894269fe8e90ea187a3cbfb562/pydantic_core-2.33.1-cp313-cp313-win_arm64.whl", hash = "sha256:aa687a23d4b7871a00e03ca96a09cad0f28f443690d300500603bd0adba4b523", size = 1909590 }, - { url = "https://files.pythonhosted.org/packages/ef/fd/24ea4302d7a527d672c5be06e17df16aabfb4e9fdc6e0b345c21580f3d2a/pydantic_core-2.33.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:401d7b76e1000d0dd5538e6381d28febdcacb097c8d340dde7d7fc6e13e9f95d", size = 1812963 }, - { url = "https://files.pythonhosted.org/packages/5f/95/4fbc2ecdeb5c1c53f1175a32d870250194eb2fdf6291b795ab08c8646d5d/pydantic_core-2.33.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7aeb055a42d734c0255c9e489ac67e75397d59c6fbe60d155851e9782f276a9c", size = 1986896 }, - { url = "https://files.pythonhosted.org/packages/71/ae/fe31e7f4a62431222d8f65a3bd02e3fa7e6026d154a00818e6d30520ea77/pydantic_core-2.33.1-cp313-cp313t-win_amd64.whl", hash = "sha256:338ea9b73e6e109f15ab439e62cb3b78aa752c7fd9536794112e14bee02c8d18", size = 1931810 }, - { url = "https://files.pythonhosted.org/packages/49/78/b86bad645cc3e8dfa6858c70ec38939bf350e54004837c48de09474b2b9e/pydantic_core-2.33.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:5ab77f45d33d264de66e1884fca158bc920cb5e27fd0764a72f72f5756ae8bdb", size = 2044282 }, - { url = "https://files.pythonhosted.org/packages/3b/00/a02531331773b2bf08743d84c6b776bd6a449d23b3ae6b0e3229d568bac4/pydantic_core-2.33.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e7aaba1b4b03aaea7bb59e1b5856d734be011d3e6d98f5bcaa98cb30f375f2ad", size = 1877598 }, - { url = "https://files.pythonhosted.org/packages/a1/fa/32cc152b84a1f420f8a7d80161373e8d87d4ffa077e67d6c8aab3ce1a6ab/pydantic_core-2.33.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7fb66263e9ba8fea2aa85e1e5578980d127fb37d7f2e292773e7bc3a38fb0c7b", size = 1911021 }, - { url = "https://files.pythonhosted.org/packages/5e/87/ea553e0d98bce6c4876f8c50f65cb45597eff6e0aaa8b15813e9972bb19d/pydantic_core-2.33.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3f2648b9262607a7fb41d782cc263b48032ff7a03a835581abbf7a3bec62bcf5", size = 1997276 }, - { url = "https://files.pythonhosted.org/packages/f7/9b/60cb9f4b52158b3adac0066492bbadd0b8473f4f8da5bcc73972655b76ef/pydantic_core-2.33.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:723c5630c4259400818b4ad096735a829074601805d07f8cafc366d95786d331", size = 2141348 }, - { url = "https://files.pythonhosted.org/packages/9b/38/374d254e270d4de0add68a8239f4ed0f444fdd7b766ea69244fb9491dccb/pydantic_core-2.33.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d100e3ae783d2167782391e0c1c7a20a31f55f8015f3293647544df3f9c67824", size = 2753708 }, - { url = "https://files.pythonhosted.org/packages/05/a8/fd79111eb5ab9bc4ef98d8fb0b3a2ffdc80107b2c59859a741ab379c96f8/pydantic_core-2.33.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:177d50460bc976a0369920b6c744d927b0ecb8606fb56858ff542560251b19e5", size = 2008699 }, - { url = "https://files.pythonhosted.org/packages/35/31/2e06619868eb4c18642c5601db420599c1cf9cf50fe868c9ac09cd298e24/pydantic_core-2.33.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a3edde68d1a1f9af1273b2fe798997b33f90308fb6d44d8550c89fc6a3647cf6", size = 2123426 }, - { url = "https://files.pythonhosted.org/packages/4a/d0/3531e8783a311802e3db7ee5a1a5ed79e5706e930b1b4e3109ce15eeb681/pydantic_core-2.33.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a62c3c3ef6a7e2c45f7853b10b5bc4ddefd6ee3cd31024754a1a5842da7d598d", size = 2087330 }, - { url = "https://files.pythonhosted.org/packages/ac/32/5ff252ed73bacd7677a706ab17723e261a76793f98b305aa20cfc10bbd56/pydantic_core-2.33.1-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:c91dbb0ab683fa0cd64a6e81907c8ff41d6497c346890e26b23de7ee55353f96", size = 2258171 }, - { url = "https://files.pythonhosted.org/packages/c9/f9/e96e00f92b8f5b3e2cddc80c5ee6cf038f8a0f238c44b67b01759943a7b4/pydantic_core-2.33.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9f466e8bf0a62dc43e068c12166281c2eca72121dd2adc1040f3aa1e21ef8599", size = 2258745 }, - { url = "https://files.pythonhosted.org/packages/54/1e/51c86688e809d94797fdf0efc41514f001caec982a05f62d90c180a9639d/pydantic_core-2.33.1-cp39-cp39-win32.whl", hash = "sha256:ab0277cedb698749caada82e5d099dc9fed3f906a30d4c382d1a21725777a1e5", size = 1923626 }, - { url = "https://files.pythonhosted.org/packages/57/18/c2da959fd8d019b70cadafdda2bf845378ada47973e0bad6cc84f56dbe6e/pydantic_core-2.33.1-cp39-cp39-win_amd64.whl", hash = "sha256:5773da0ee2d17136b1f1c6fbde543398d452a6ad2a7b54ea1033e2daa739b8d2", size = 1953703 }, - { url = "https://files.pythonhosted.org/packages/9c/c7/8b311d5adb0fe00a93ee9b4e92a02b0ec08510e9838885ef781ccbb20604/pydantic_core-2.33.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5c834f54f8f4640fd7e4b193f80eb25a0602bba9e19b3cd2fc7ffe8199f5ae02", size = 2041659 }, - { url = "https://files.pythonhosted.org/packages/8a/d6/4f58d32066a9e26530daaf9adc6664b01875ae0691570094968aaa7b8fcc/pydantic_core-2.33.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:049e0de24cf23766f12cc5cc71d8abc07d4a9deb9061b334b62093dedc7cb068", size = 1873294 }, - { url = "https://files.pythonhosted.org/packages/f7/3f/53cc9c45d9229da427909c751f8ed2bf422414f7664ea4dde2d004f596ba/pydantic_core-2.33.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a28239037b3d6f16916a4c831a5a0eadf856bdd6d2e92c10a0da3a59eadcf3e", size = 1903771 }, - { url = "https://files.pythonhosted.org/packages/f0/49/bf0783279ce674eb9903fb9ae43f6c614cb2f1c4951370258823f795368b/pydantic_core-2.33.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d3da303ab5f378a268fa7d45f37d7d85c3ec19769f28d2cc0c61826a8de21fe", size = 2083558 }, - { url = "https://files.pythonhosted.org/packages/9c/5b/0d998367687f986c7d8484a2c476d30f07bf5b8b1477649a6092bd4c540e/pydantic_core-2.33.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:25626fb37b3c543818c14821afe0fd3830bc327a43953bc88db924b68c5723f1", size = 2118038 }, - { url = "https://files.pythonhosted.org/packages/b3/33/039287d410230ee125daee57373ac01940d3030d18dba1c29cd3089dc3ca/pydantic_core-2.33.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:3ab2d36e20fbfcce8f02d73c33a8a7362980cff717926bbae030b93ae46b56c7", size = 2079315 }, - { url = "https://files.pythonhosted.org/packages/1f/85/6d8b2646d99c062d7da2d0ab2faeb0d6ca9cca4c02da6076376042a20da3/pydantic_core-2.33.1-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:2f9284e11c751b003fd4215ad92d325d92c9cb19ee6729ebd87e3250072cdcde", size = 2249063 }, - { url = "https://files.pythonhosted.org/packages/17/d7/c37d208d5738f7b9ad8f22ae8a727d88ebf9c16c04ed2475122cc3f7224a/pydantic_core-2.33.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:048c01eee07d37cbd066fc512b9d8b5ea88ceeb4e629ab94b3e56965ad655add", size = 2254631 }, - { url = "https://files.pythonhosted.org/packages/13/e0/bafa46476d328e4553b85ab9b2f7409e7aaef0ce4c937c894821c542d347/pydantic_core-2.33.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5ccd429694cf26af7997595d627dd2637e7932214486f55b8a357edaac9dae8c", size = 2080877 }, - { url = "https://files.pythonhosted.org/packages/0b/76/1794e440c1801ed35415238d2c728f26cd12695df9057154ad768b7b991c/pydantic_core-2.33.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3a371dc00282c4b84246509a5ddc808e61b9864aa1eae9ecc92bb1268b82db4a", size = 2042858 }, - { url = "https://files.pythonhosted.org/packages/73/b4/9cd7b081fb0b1b4f8150507cd59d27b275c3e22ad60b35cb19ea0977d9b9/pydantic_core-2.33.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:f59295ecc75a1788af8ba92f2e8c6eeaa5a94c22fc4d151e8d9638814f85c8fc", size = 1873745 }, - { url = "https://files.pythonhosted.org/packages/e1/d7/9ddb7575d4321e40d0363903c2576c8c0c3280ebea137777e5ab58d723e3/pydantic_core-2.33.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08530b8ac922003033f399128505f513e30ca770527cc8bbacf75a84fcc2c74b", size = 1904188 }, - { url = "https://files.pythonhosted.org/packages/d1/a8/3194ccfe461bb08da19377ebec8cb4f13c9bd82e13baebc53c5c7c39a029/pydantic_core-2.33.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bae370459da6a5466978c0eacf90690cb57ec9d533f8e63e564ef3822bfa04fe", size = 2083479 }, - { url = "https://files.pythonhosted.org/packages/42/c7/84cb569555d7179ca0b3f838cef08f66f7089b54432f5b8599aac6e9533e/pydantic_core-2.33.1-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e3de2777e3b9f4d603112f78006f4ae0acb936e95f06da6cb1a45fbad6bdb4b5", size = 2118415 }, - { url = "https://files.pythonhosted.org/packages/3b/67/72abb8c73e0837716afbb58a59cc9e3ae43d1aa8677f3b4bc72c16142716/pydantic_core-2.33.1-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:3a64e81e8cba118e108d7126362ea30e021291b7805d47e4896e52c791be2761", size = 2079623 }, - { url = "https://files.pythonhosted.org/packages/0b/cd/c59707e35a47ba4cbbf153c3f7c56420c58653b5801b055dc52cccc8e2dc/pydantic_core-2.33.1-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:52928d8c1b6bda03cc6d811e8923dffc87a2d3c8b3bfd2ce16471c7147a24850", size = 2250175 }, - { url = "https://files.pythonhosted.org/packages/84/32/e4325a6676b0bed32d5b084566ec86ed7fd1e9bcbfc49c578b1755bde920/pydantic_core-2.33.1-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:1b30d92c9412beb5ac6b10a3eb7ef92ccb14e3f2a8d7732e2d739f58b3aa7544", size = 2254674 }, - { url = "https://files.pythonhosted.org/packages/12/6f/5596dc418f2e292ffc661d21931ab34591952e2843e7168ea5a52591f6ff/pydantic_core-2.33.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:f995719707e0e29f0f41a8aa3bcea6e761a36c9136104d3189eafb83f5cec5e5", size = 2080951 }, - { url = "https://files.pythonhosted.org/packages/2d/a8/c2c8f29bd18f7ef52de32a6deb9e3ee87ba18b7b2122636aa9f4438cf627/pydantic_core-2.33.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7edbc454a29fc6aeae1e1eecba4f07b63b8d76e76a748532233c4c167b4cb9ea", size = 2041791 }, - { url = "https://files.pythonhosted.org/packages/08/ad/328081b1c82543ae49d0650048305058583c51f1a9a56a0d6e87bb3a2443/pydantic_core-2.33.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:ad05b683963f69a1d5d2c2bdab1274a31221ca737dbbceaa32bcb67359453cdd", size = 1873579 }, - { url = "https://files.pythonhosted.org/packages/6e/8a/bc65dbf7e501e88367cdab06a2c1340457c785f0c72288cae737fd80c0fa/pydantic_core-2.33.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df6a94bf9452c6da9b5d76ed229a5683d0306ccb91cca8e1eea883189780d568", size = 1904189 }, - { url = "https://files.pythonhosted.org/packages/9a/db/30ca6aefda211fb01ef185ca73cb7a0c6e7fe952c524025c8782b5acd771/pydantic_core-2.33.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7965c13b3967909a09ecc91f21d09cfc4576bf78140b988904e94f130f188396", size = 2084446 }, - { url = "https://files.pythonhosted.org/packages/f2/89/a12b55286e30c9f476eab7c53c9249ec76faf70430596496ab0309f28629/pydantic_core-2.33.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3f1fdb790440a34f6ecf7679e1863b825cb5ffde858a9197f851168ed08371e5", size = 2118215 }, - { url = "https://files.pythonhosted.org/packages/8e/55/12721c4a8d7951584ad3d9848b44442559cf1876e0bb424148d1060636b3/pydantic_core-2.33.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:5277aec8d879f8d05168fdd17ae811dd313b8ff894aeeaf7cd34ad28b4d77e33", size = 2079963 }, - { url = "https://files.pythonhosted.org/packages/bd/0c/3391bd5d6ff62ea998db94732528d9bc32c560b0ed861c39119759461946/pydantic_core-2.33.1-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:8ab581d3530611897d863d1a649fb0644b860286b4718db919bfd51ece41f10b", size = 2249388 }, - { url = "https://files.pythonhosted.org/packages/d3/5f/3e4feb042998d7886a9b523b372d83955cbc192a07013dcd24276db078ee/pydantic_core-2.33.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0483847fa9ad5e3412265c1bd72aad35235512d9ce9d27d81a56d935ef489672", size = 2255226 }, - { url = "https://files.pythonhosted.org/packages/25/f2/1647933efaaad61846109a27619f3704929e758a09e6431b8f932a053d40/pydantic_core-2.33.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:de9e06abe3cc5ec6a2d5f75bc99b0bdca4f5c719a5b34026f8c57efbdecd2ee3", size = 2081073 }, + { url = "https://files.pythonhosted.org/packages/38/ea/5f572806ab4d4223d11551af814d243b0e3e02cc6913def4d1fe4a5ca41c/pydantic_core-2.33.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3077cfdb6125cc8dab61b155fdd714663e401f0e6883f9632118ec12cf42df26", size = 2044021, upload_time = "2025-04-02T09:46:45.065Z" }, + { url = "https://files.pythonhosted.org/packages/8c/d1/f86cc96d2aa80e3881140d16d12ef2b491223f90b28b9a911346c04ac359/pydantic_core-2.33.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8ffab8b2908d152e74862d276cf5017c81a2f3719f14e8e3e8d6b83fda863927", size = 1861742, upload_time = "2025-04-02T09:46:46.684Z" }, + { url = "https://files.pythonhosted.org/packages/37/08/fbd2cd1e9fc735a0df0142fac41c114ad9602d1c004aea340169ae90973b/pydantic_core-2.33.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5183e4f6a2d468787243ebcd70cf4098c247e60d73fb7d68d5bc1e1beaa0c4db", size = 1910414, upload_time = "2025-04-02T09:46:48.263Z" }, + { url = "https://files.pythonhosted.org/packages/7f/73/3ac217751decbf8d6cb9443cec9b9eb0130eeada6ae56403e11b486e277e/pydantic_core-2.33.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:398a38d323f37714023be1e0285765f0a27243a8b1506b7b7de87b647b517e48", size = 1996848, upload_time = "2025-04-02T09:46:49.441Z" }, + { url = "https://files.pythonhosted.org/packages/9a/f5/5c26b265cdcff2661e2520d2d1e9db72d117ea00eb41e00a76efe68cb009/pydantic_core-2.33.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:87d3776f0001b43acebfa86f8c64019c043b55cc5a6a2e313d728b5c95b46969", size = 2141055, upload_time = "2025-04-02T09:46:50.602Z" }, + { url = "https://files.pythonhosted.org/packages/5d/14/a9c3cee817ef2f8347c5ce0713e91867a0dceceefcb2973942855c917379/pydantic_core-2.33.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c566dd9c5f63d22226409553531f89de0cac55397f2ab8d97d6f06cfce6d947e", size = 2753806, upload_time = "2025-04-02T09:46:52.116Z" }, + { url = "https://files.pythonhosted.org/packages/f2/68/866ce83a51dd37e7c604ce0050ff6ad26de65a7799df89f4db87dd93d1d6/pydantic_core-2.33.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0d5f3acc81452c56895e90643a625302bd6be351e7010664151cc55b7b97f89", size = 2007777, upload_time = "2025-04-02T09:46:53.675Z" }, + { url = "https://files.pythonhosted.org/packages/b6/a8/36771f4404bb3e49bd6d4344da4dede0bf89cc1e01f3b723c47248a3761c/pydantic_core-2.33.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d3a07fadec2a13274a8d861d3d37c61e97a816beae717efccaa4b36dfcaadcde", size = 2122803, upload_time = "2025-04-02T09:46:55.789Z" }, + { url = "https://files.pythonhosted.org/packages/18/9c/730a09b2694aa89360d20756369822d98dc2f31b717c21df33b64ffd1f50/pydantic_core-2.33.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f99aeda58dce827f76963ee87a0ebe75e648c72ff9ba1174a253f6744f518f65", size = 2086755, upload_time = "2025-04-02T09:46:56.956Z" }, + { url = "https://files.pythonhosted.org/packages/54/8e/2dccd89602b5ec31d1c58138d02340ecb2ebb8c2cac3cc66b65ce3edb6ce/pydantic_core-2.33.1-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:902dbc832141aa0ec374f4310f1e4e7febeebc3256f00dc359a9ac3f264a45dc", size = 2257358, upload_time = "2025-04-02T09:46:58.445Z" }, + { url = "https://files.pythonhosted.org/packages/d1/9c/126e4ac1bfad8a95a9837acdd0963695d69264179ba4ede8b8c40d741702/pydantic_core-2.33.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fe44d56aa0b00d66640aa84a3cbe80b7a3ccdc6f0b1ca71090696a6d4777c091", size = 2257916, upload_time = "2025-04-02T09:46:59.726Z" }, + { url = "https://files.pythonhosted.org/packages/7d/ba/91eea2047e681a6853c81c20aeca9dcdaa5402ccb7404a2097c2adf9d038/pydantic_core-2.33.1-cp310-cp310-win32.whl", hash = "sha256:ed3eb16d51257c763539bde21e011092f127a2202692afaeaccb50db55a31383", size = 1923823, upload_time = "2025-04-02T09:47:01.278Z" }, + { url = "https://files.pythonhosted.org/packages/94/c0/fcdf739bf60d836a38811476f6ecd50374880b01e3014318b6e809ddfd52/pydantic_core-2.33.1-cp310-cp310-win_amd64.whl", hash = "sha256:694ad99a7f6718c1a498dc170ca430687a39894a60327f548e02a9c7ee4b6504", size = 1952494, upload_time = "2025-04-02T09:47:02.976Z" }, + { url = "https://files.pythonhosted.org/packages/d6/7f/c6298830cb780c46b4f46bb24298d01019ffa4d21769f39b908cd14bbd50/pydantic_core-2.33.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:6e966fc3caaf9f1d96b349b0341c70c8d6573bf1bac7261f7b0ba88f96c56c24", size = 2044224, upload_time = "2025-04-02T09:47:04.199Z" }, + { url = "https://files.pythonhosted.org/packages/a8/65/6ab3a536776cad5343f625245bd38165d6663256ad43f3a200e5936afd6c/pydantic_core-2.33.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bfd0adeee563d59c598ceabddf2c92eec77abcb3f4a391b19aa7366170bd9e30", size = 1858845, upload_time = "2025-04-02T09:47:05.686Z" }, + { url = "https://files.pythonhosted.org/packages/e9/15/9a22fd26ba5ee8c669d4b8c9c244238e940cd5d818649603ca81d1c69861/pydantic_core-2.33.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91815221101ad3c6b507804178a7bb5cb7b2ead9ecd600041669c8d805ebd595", size = 1910029, upload_time = "2025-04-02T09:47:07.042Z" }, + { url = "https://files.pythonhosted.org/packages/d5/33/8cb1a62818974045086f55f604044bf35b9342900318f9a2a029a1bec460/pydantic_core-2.33.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9fea9c1869bb4742d174a57b4700c6dadea951df8b06de40c2fedb4f02931c2e", size = 1997784, upload_time = "2025-04-02T09:47:08.63Z" }, + { url = "https://files.pythonhosted.org/packages/c0/ca/49958e4df7715c71773e1ea5be1c74544923d10319173264e6db122543f9/pydantic_core-2.33.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d20eb4861329bb2484c021b9d9a977566ab16d84000a57e28061151c62b349a", size = 2141075, upload_time = "2025-04-02T09:47:10.267Z" }, + { url = "https://files.pythonhosted.org/packages/7b/a6/0b3a167a9773c79ba834b959b4e18c3ae9216b8319bd8422792abc8a41b1/pydantic_core-2.33.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb935c5591573ae3201640579f30128ccc10739b45663f93c06796854405505", size = 2745849, upload_time = "2025-04-02T09:47:11.724Z" }, + { url = "https://files.pythonhosted.org/packages/0b/60/516484135173aa9e5861d7a0663dce82e4746d2e7f803627d8c25dfa5578/pydantic_core-2.33.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c964fd24e6166420d18fb53996d8c9fd6eac9bf5ae3ec3d03015be4414ce497f", size = 2005794, upload_time = "2025-04-02T09:47:13.099Z" }, + { url = "https://files.pythonhosted.org/packages/86/70/05b1eb77459ad47de00cf78ee003016da0cedf8b9170260488d7c21e9181/pydantic_core-2.33.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:681d65e9011f7392db5aa002b7423cc442d6a673c635668c227c6c8d0e5a4f77", size = 2123237, upload_time = "2025-04-02T09:47:14.355Z" }, + { url = "https://files.pythonhosted.org/packages/c7/57/12667a1409c04ae7dc95d3b43158948eb0368e9c790be8b095cb60611459/pydantic_core-2.33.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e100c52f7355a48413e2999bfb4e139d2977a904495441b374f3d4fb4a170961", size = 2086351, upload_time = "2025-04-02T09:47:15.676Z" }, + { url = "https://files.pythonhosted.org/packages/57/61/cc6d1d1c1664b58fdd6ecc64c84366c34ec9b606aeb66cafab6f4088974c/pydantic_core-2.33.1-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:048831bd363490be79acdd3232f74a0e9951b11b2b4cc058aeb72b22fdc3abe1", size = 2258914, upload_time = "2025-04-02T09:47:17Z" }, + { url = "https://files.pythonhosted.org/packages/d1/0a/edb137176a1f5419b2ddee8bde6a0a548cfa3c74f657f63e56232df8de88/pydantic_core-2.33.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:bdc84017d28459c00db6f918a7272a5190bec3090058334e43a76afb279eac7c", size = 2257385, upload_time = "2025-04-02T09:47:18.631Z" }, + { url = "https://files.pythonhosted.org/packages/26/3c/48ca982d50e4b0e1d9954919c887bdc1c2b462801bf408613ccc641b3daa/pydantic_core-2.33.1-cp311-cp311-win32.whl", hash = "sha256:32cd11c5914d1179df70406427097c7dcde19fddf1418c787540f4b730289896", size = 1923765, upload_time = "2025-04-02T09:47:20.34Z" }, + { url = "https://files.pythonhosted.org/packages/33/cd/7ab70b99e5e21559f5de38a0928ea84e6f23fdef2b0d16a6feaf942b003c/pydantic_core-2.33.1-cp311-cp311-win_amd64.whl", hash = "sha256:2ea62419ba8c397e7da28a9170a16219d310d2cf4970dbc65c32faf20d828c83", size = 1950688, upload_time = "2025-04-02T09:47:22.029Z" }, + { url = "https://files.pythonhosted.org/packages/4b/ae/db1fc237b82e2cacd379f63e3335748ab88b5adde98bf7544a1b1bd10a84/pydantic_core-2.33.1-cp311-cp311-win_arm64.whl", hash = "sha256:fc903512177361e868bc1f5b80ac8c8a6e05fcdd574a5fb5ffeac5a9982b9e89", size = 1908185, upload_time = "2025-04-02T09:47:23.385Z" }, + { url = "https://files.pythonhosted.org/packages/c8/ce/3cb22b07c29938f97ff5f5bb27521f95e2ebec399b882392deb68d6c440e/pydantic_core-2.33.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:1293d7febb995e9d3ec3ea09caf1a26214eec45b0f29f6074abb004723fc1de8", size = 2026640, upload_time = "2025-04-02T09:47:25.394Z" }, + { url = "https://files.pythonhosted.org/packages/19/78/f381d643b12378fee782a72126ec5d793081ef03791c28a0fd542a5bee64/pydantic_core-2.33.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:99b56acd433386c8f20be5c4000786d1e7ca0523c8eefc995d14d79c7a081498", size = 1852649, upload_time = "2025-04-02T09:47:27.417Z" }, + { url = "https://files.pythonhosted.org/packages/9d/2b/98a37b80b15aac9eb2c6cfc6dbd35e5058a352891c5cce3a8472d77665a6/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35a5ec3fa8c2fe6c53e1b2ccc2454398f95d5393ab398478f53e1afbbeb4d939", size = 1892472, upload_time = "2025-04-02T09:47:29.006Z" }, + { url = "https://files.pythonhosted.org/packages/4e/d4/3c59514e0f55a161004792b9ff3039da52448f43f5834f905abef9db6e4a/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b172f7b9d2f3abc0efd12e3386f7e48b576ef309544ac3a63e5e9cdd2e24585d", size = 1977509, upload_time = "2025-04-02T09:47:33.464Z" }, + { url = "https://files.pythonhosted.org/packages/a9/b6/c2c7946ef70576f79a25db59a576bce088bdc5952d1b93c9789b091df716/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9097b9f17f91eea659b9ec58148c0747ec354a42f7389b9d50701610d86f812e", size = 2128702, upload_time = "2025-04-02T09:47:34.812Z" }, + { url = "https://files.pythonhosted.org/packages/88/fe/65a880f81e3f2a974312b61f82a03d85528f89a010ce21ad92f109d94deb/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cc77ec5b7e2118b152b0d886c7514a4653bcb58c6b1d760134a9fab915f777b3", size = 2679428, upload_time = "2025-04-02T09:47:37.315Z" }, + { url = "https://files.pythonhosted.org/packages/6f/ff/4459e4146afd0462fb483bb98aa2436d69c484737feaceba1341615fb0ac/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5e3d15245b08fa4a84cefc6c9222e6f37c98111c8679fbd94aa145f9a0ae23d", size = 2008753, upload_time = "2025-04-02T09:47:39.013Z" }, + { url = "https://files.pythonhosted.org/packages/7c/76/1c42e384e8d78452ededac8b583fe2550c84abfef83a0552e0e7478ccbc3/pydantic_core-2.33.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ef99779001d7ac2e2461d8ab55d3373fe7315caefdbecd8ced75304ae5a6fc6b", size = 2114849, upload_time = "2025-04-02T09:47:40.427Z" }, + { url = "https://files.pythonhosted.org/packages/00/72/7d0cf05095c15f7ffe0eb78914b166d591c0eed72f294da68378da205101/pydantic_core-2.33.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:fc6bf8869e193855e8d91d91f6bf59699a5cdfaa47a404e278e776dd7f168b39", size = 2069541, upload_time = "2025-04-02T09:47:42.01Z" }, + { url = "https://files.pythonhosted.org/packages/b3/69/94a514066bb7d8be499aa764926937409d2389c09be0b5107a970286ef81/pydantic_core-2.33.1-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:b1caa0bc2741b043db7823843e1bde8aaa58a55a58fda06083b0569f8b45693a", size = 2239225, upload_time = "2025-04-02T09:47:43.425Z" }, + { url = "https://files.pythonhosted.org/packages/84/b0/e390071eadb44b41f4f54c3cef64d8bf5f9612c92686c9299eaa09e267e2/pydantic_core-2.33.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ec259f62538e8bf364903a7d0d0239447059f9434b284f5536e8402b7dd198db", size = 2248373, upload_time = "2025-04-02T09:47:44.979Z" }, + { url = "https://files.pythonhosted.org/packages/d6/b2/288b3579ffc07e92af66e2f1a11be3b056fe1214aab314748461f21a31c3/pydantic_core-2.33.1-cp312-cp312-win32.whl", hash = "sha256:e14f369c98a7c15772b9da98987f58e2b509a93235582838bd0d1d8c08b68fda", size = 1907034, upload_time = "2025-04-02T09:47:46.843Z" }, + { url = "https://files.pythonhosted.org/packages/02/28/58442ad1c22b5b6742b992ba9518420235adced665513868f99a1c2638a5/pydantic_core-2.33.1-cp312-cp312-win_amd64.whl", hash = "sha256:1c607801d85e2e123357b3893f82c97a42856192997b95b4d8325deb1cd0c5f4", size = 1956848, upload_time = "2025-04-02T09:47:48.404Z" }, + { url = "https://files.pythonhosted.org/packages/a1/eb/f54809b51c7e2a1d9f439f158b8dd94359321abcc98767e16fc48ae5a77e/pydantic_core-2.33.1-cp312-cp312-win_arm64.whl", hash = "sha256:8d13f0276806ee722e70a1c93da19748594f19ac4299c7e41237fc791d1861ea", size = 1903986, upload_time = "2025-04-02T09:47:49.839Z" }, + { url = "https://files.pythonhosted.org/packages/7a/24/eed3466a4308d79155f1cdd5c7432c80ddcc4530ba8623b79d5ced021641/pydantic_core-2.33.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:70af6a21237b53d1fe7b9325b20e65cbf2f0a848cf77bed492b029139701e66a", size = 2033551, upload_time = "2025-04-02T09:47:51.648Z" }, + { url = "https://files.pythonhosted.org/packages/ab/14/df54b1a0bc9b6ded9b758b73139d2c11b4e8eb43e8ab9c5847c0a2913ada/pydantic_core-2.33.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:282b3fe1bbbe5ae35224a0dbd05aed9ccabccd241e8e6b60370484234b456266", size = 1852785, upload_time = "2025-04-02T09:47:53.149Z" }, + { url = "https://files.pythonhosted.org/packages/fa/96/e275f15ff3d34bb04b0125d9bc8848bf69f25d784d92a63676112451bfb9/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b315e596282bbb5822d0c7ee9d255595bd7506d1cb20c2911a4da0b970187d3", size = 1897758, upload_time = "2025-04-02T09:47:55.006Z" }, + { url = "https://files.pythonhosted.org/packages/b7/d8/96bc536e975b69e3a924b507d2a19aedbf50b24e08c80fb00e35f9baaed8/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1dfae24cf9921875ca0ca6a8ecb4bb2f13c855794ed0d468d6abbec6e6dcd44a", size = 1986109, upload_time = "2025-04-02T09:47:56.532Z" }, + { url = "https://files.pythonhosted.org/packages/90/72/ab58e43ce7e900b88cb571ed057b2fcd0e95b708a2e0bed475b10130393e/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6dd8ecfde08d8bfadaea669e83c63939af76f4cf5538a72597016edfa3fad516", size = 2129159, upload_time = "2025-04-02T09:47:58.088Z" }, + { url = "https://files.pythonhosted.org/packages/dc/3f/52d85781406886c6870ac995ec0ba7ccc028b530b0798c9080531b409fdb/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2f593494876eae852dc98c43c6f260f45abdbfeec9e4324e31a481d948214764", size = 2680222, upload_time = "2025-04-02T09:47:59.591Z" }, + { url = "https://files.pythonhosted.org/packages/f4/56/6e2ef42f363a0eec0fd92f74a91e0ac48cd2e49b695aac1509ad81eee86a/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:948b73114f47fd7016088e5186d13faf5e1b2fe83f5e320e371f035557fd264d", size = 2006980, upload_time = "2025-04-02T09:48:01.397Z" }, + { url = "https://files.pythonhosted.org/packages/4c/c0/604536c4379cc78359f9ee0aa319f4aedf6b652ec2854953f5a14fc38c5a/pydantic_core-2.33.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e11f3864eb516af21b01e25fac915a82e9ddad3bb0fb9e95a246067398b435a4", size = 2120840, upload_time = "2025-04-02T09:48:03.056Z" }, + { url = "https://files.pythonhosted.org/packages/1f/46/9eb764814f508f0edfb291a0f75d10854d78113fa13900ce13729aaec3ae/pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:549150be302428b56fdad0c23c2741dcdb5572413776826c965619a25d9c6bde", size = 2072518, upload_time = "2025-04-02T09:48:04.662Z" }, + { url = "https://files.pythonhosted.org/packages/42/e3/fb6b2a732b82d1666fa6bf53e3627867ea3131c5f39f98ce92141e3e3dc1/pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:495bc156026efafd9ef2d82372bd38afce78ddd82bf28ef5276c469e57c0c83e", size = 2248025, upload_time = "2025-04-02T09:48:06.226Z" }, + { url = "https://files.pythonhosted.org/packages/5c/9d/fbe8fe9d1aa4dac88723f10a921bc7418bd3378a567cb5e21193a3c48b43/pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ec79de2a8680b1a67a07490bddf9636d5c2fab609ba8c57597e855fa5fa4dacd", size = 2254991, upload_time = "2025-04-02T09:48:08.114Z" }, + { url = "https://files.pythonhosted.org/packages/aa/99/07e2237b8a66438d9b26482332cda99a9acccb58d284af7bc7c946a42fd3/pydantic_core-2.33.1-cp313-cp313-win32.whl", hash = "sha256:ee12a7be1742f81b8a65b36c6921022301d466b82d80315d215c4c691724986f", size = 1915262, upload_time = "2025-04-02T09:48:09.708Z" }, + { url = "https://files.pythonhosted.org/packages/8a/f4/e457a7849beeed1e5defbcf5051c6f7b3c91a0624dd31543a64fc9adcf52/pydantic_core-2.33.1-cp313-cp313-win_amd64.whl", hash = "sha256:ede9b407e39949d2afc46385ce6bd6e11588660c26f80576c11c958e6647bc40", size = 1956626, upload_time = "2025-04-02T09:48:11.288Z" }, + { url = "https://files.pythonhosted.org/packages/20/d0/e8d567a7cff7b04e017ae164d98011f1e1894269fe8e90ea187a3cbfb562/pydantic_core-2.33.1-cp313-cp313-win_arm64.whl", hash = "sha256:aa687a23d4b7871a00e03ca96a09cad0f28f443690d300500603bd0adba4b523", size = 1909590, upload_time = "2025-04-02T09:48:12.861Z" }, + { url = "https://files.pythonhosted.org/packages/ef/fd/24ea4302d7a527d672c5be06e17df16aabfb4e9fdc6e0b345c21580f3d2a/pydantic_core-2.33.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:401d7b76e1000d0dd5538e6381d28febdcacb097c8d340dde7d7fc6e13e9f95d", size = 1812963, upload_time = "2025-04-02T09:48:14.553Z" }, + { url = "https://files.pythonhosted.org/packages/5f/95/4fbc2ecdeb5c1c53f1175a32d870250194eb2fdf6291b795ab08c8646d5d/pydantic_core-2.33.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7aeb055a42d734c0255c9e489ac67e75397d59c6fbe60d155851e9782f276a9c", size = 1986896, upload_time = "2025-04-02T09:48:16.222Z" }, + { url = "https://files.pythonhosted.org/packages/71/ae/fe31e7f4a62431222d8f65a3bd02e3fa7e6026d154a00818e6d30520ea77/pydantic_core-2.33.1-cp313-cp313t-win_amd64.whl", hash = "sha256:338ea9b73e6e109f15ab439e62cb3b78aa752c7fd9536794112e14bee02c8d18", size = 1931810, upload_time = "2025-04-02T09:48:17.97Z" }, + { url = "https://files.pythonhosted.org/packages/49/78/b86bad645cc3e8dfa6858c70ec38939bf350e54004837c48de09474b2b9e/pydantic_core-2.33.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:5ab77f45d33d264de66e1884fca158bc920cb5e27fd0764a72f72f5756ae8bdb", size = 2044282, upload_time = "2025-04-02T09:48:19.849Z" }, + { url = "https://files.pythonhosted.org/packages/3b/00/a02531331773b2bf08743d84c6b776bd6a449d23b3ae6b0e3229d568bac4/pydantic_core-2.33.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e7aaba1b4b03aaea7bb59e1b5856d734be011d3e6d98f5bcaa98cb30f375f2ad", size = 1877598, upload_time = "2025-04-02T09:48:22.863Z" }, + { url = "https://files.pythonhosted.org/packages/a1/fa/32cc152b84a1f420f8a7d80161373e8d87d4ffa077e67d6c8aab3ce1a6ab/pydantic_core-2.33.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7fb66263e9ba8fea2aa85e1e5578980d127fb37d7f2e292773e7bc3a38fb0c7b", size = 1911021, upload_time = "2025-04-02T09:48:24.592Z" }, + { url = "https://files.pythonhosted.org/packages/5e/87/ea553e0d98bce6c4876f8c50f65cb45597eff6e0aaa8b15813e9972bb19d/pydantic_core-2.33.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3f2648b9262607a7fb41d782cc263b48032ff7a03a835581abbf7a3bec62bcf5", size = 1997276, upload_time = "2025-04-02T09:48:26.314Z" }, + { url = "https://files.pythonhosted.org/packages/f7/9b/60cb9f4b52158b3adac0066492bbadd0b8473f4f8da5bcc73972655b76ef/pydantic_core-2.33.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:723c5630c4259400818b4ad096735a829074601805d07f8cafc366d95786d331", size = 2141348, upload_time = "2025-04-02T09:48:28.298Z" }, + { url = "https://files.pythonhosted.org/packages/9b/38/374d254e270d4de0add68a8239f4ed0f444fdd7b766ea69244fb9491dccb/pydantic_core-2.33.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d100e3ae783d2167782391e0c1c7a20a31f55f8015f3293647544df3f9c67824", size = 2753708, upload_time = "2025-04-02T09:48:29.987Z" }, + { url = "https://files.pythonhosted.org/packages/05/a8/fd79111eb5ab9bc4ef98d8fb0b3a2ffdc80107b2c59859a741ab379c96f8/pydantic_core-2.33.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:177d50460bc976a0369920b6c744d927b0ecb8606fb56858ff542560251b19e5", size = 2008699, upload_time = "2025-04-02T09:48:31.76Z" }, + { url = "https://files.pythonhosted.org/packages/35/31/2e06619868eb4c18642c5601db420599c1cf9cf50fe868c9ac09cd298e24/pydantic_core-2.33.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a3edde68d1a1f9af1273b2fe798997b33f90308fb6d44d8550c89fc6a3647cf6", size = 2123426, upload_time = "2025-04-02T09:48:33.623Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d0/3531e8783a311802e3db7ee5a1a5ed79e5706e930b1b4e3109ce15eeb681/pydantic_core-2.33.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a62c3c3ef6a7e2c45f7853b10b5bc4ddefd6ee3cd31024754a1a5842da7d598d", size = 2087330, upload_time = "2025-04-02T09:48:35.387Z" }, + { url = "https://files.pythonhosted.org/packages/ac/32/5ff252ed73bacd7677a706ab17723e261a76793f98b305aa20cfc10bbd56/pydantic_core-2.33.1-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:c91dbb0ab683fa0cd64a6e81907c8ff41d6497c346890e26b23de7ee55353f96", size = 2258171, upload_time = "2025-04-02T09:48:37.559Z" }, + { url = "https://files.pythonhosted.org/packages/c9/f9/e96e00f92b8f5b3e2cddc80c5ee6cf038f8a0f238c44b67b01759943a7b4/pydantic_core-2.33.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9f466e8bf0a62dc43e068c12166281c2eca72121dd2adc1040f3aa1e21ef8599", size = 2258745, upload_time = "2025-04-02T09:48:39.413Z" }, + { url = "https://files.pythonhosted.org/packages/54/1e/51c86688e809d94797fdf0efc41514f001caec982a05f62d90c180a9639d/pydantic_core-2.33.1-cp39-cp39-win32.whl", hash = "sha256:ab0277cedb698749caada82e5d099dc9fed3f906a30d4c382d1a21725777a1e5", size = 1923626, upload_time = "2025-04-02T09:48:41.24Z" }, + { url = "https://files.pythonhosted.org/packages/57/18/c2da959fd8d019b70cadafdda2bf845378ada47973e0bad6cc84f56dbe6e/pydantic_core-2.33.1-cp39-cp39-win_amd64.whl", hash = "sha256:5773da0ee2d17136b1f1c6fbde543398d452a6ad2a7b54ea1033e2daa739b8d2", size = 1953703, upload_time = "2025-04-02T09:48:43.196Z" }, + { url = "https://files.pythonhosted.org/packages/9c/c7/8b311d5adb0fe00a93ee9b4e92a02b0ec08510e9838885ef781ccbb20604/pydantic_core-2.33.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5c834f54f8f4640fd7e4b193f80eb25a0602bba9e19b3cd2fc7ffe8199f5ae02", size = 2041659, upload_time = "2025-04-02T09:48:45.342Z" }, + { url = "https://files.pythonhosted.org/packages/8a/d6/4f58d32066a9e26530daaf9adc6664b01875ae0691570094968aaa7b8fcc/pydantic_core-2.33.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:049e0de24cf23766f12cc5cc71d8abc07d4a9deb9061b334b62093dedc7cb068", size = 1873294, upload_time = "2025-04-02T09:48:47.548Z" }, + { url = "https://files.pythonhosted.org/packages/f7/3f/53cc9c45d9229da427909c751f8ed2bf422414f7664ea4dde2d004f596ba/pydantic_core-2.33.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a28239037b3d6f16916a4c831a5a0eadf856bdd6d2e92c10a0da3a59eadcf3e", size = 1903771, upload_time = "2025-04-02T09:48:49.468Z" }, + { url = "https://files.pythonhosted.org/packages/f0/49/bf0783279ce674eb9903fb9ae43f6c614cb2f1c4951370258823f795368b/pydantic_core-2.33.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d3da303ab5f378a268fa7d45f37d7d85c3ec19769f28d2cc0c61826a8de21fe", size = 2083558, upload_time = "2025-04-02T09:48:51.409Z" }, + { url = "https://files.pythonhosted.org/packages/9c/5b/0d998367687f986c7d8484a2c476d30f07bf5b8b1477649a6092bd4c540e/pydantic_core-2.33.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:25626fb37b3c543818c14821afe0fd3830bc327a43953bc88db924b68c5723f1", size = 2118038, upload_time = "2025-04-02T09:48:53.702Z" }, + { url = "https://files.pythonhosted.org/packages/b3/33/039287d410230ee125daee57373ac01940d3030d18dba1c29cd3089dc3ca/pydantic_core-2.33.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:3ab2d36e20fbfcce8f02d73c33a8a7362980cff717926bbae030b93ae46b56c7", size = 2079315, upload_time = "2025-04-02T09:48:55.555Z" }, + { url = "https://files.pythonhosted.org/packages/1f/85/6d8b2646d99c062d7da2d0ab2faeb0d6ca9cca4c02da6076376042a20da3/pydantic_core-2.33.1-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:2f9284e11c751b003fd4215ad92d325d92c9cb19ee6729ebd87e3250072cdcde", size = 2249063, upload_time = "2025-04-02T09:48:57.479Z" }, + { url = "https://files.pythonhosted.org/packages/17/d7/c37d208d5738f7b9ad8f22ae8a727d88ebf9c16c04ed2475122cc3f7224a/pydantic_core-2.33.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:048c01eee07d37cbd066fc512b9d8b5ea88ceeb4e629ab94b3e56965ad655add", size = 2254631, upload_time = "2025-04-02T09:48:59.581Z" }, + { url = "https://files.pythonhosted.org/packages/13/e0/bafa46476d328e4553b85ab9b2f7409e7aaef0ce4c937c894821c542d347/pydantic_core-2.33.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5ccd429694cf26af7997595d627dd2637e7932214486f55b8a357edaac9dae8c", size = 2080877, upload_time = "2025-04-02T09:49:01.52Z" }, + { url = "https://files.pythonhosted.org/packages/0b/76/1794e440c1801ed35415238d2c728f26cd12695df9057154ad768b7b991c/pydantic_core-2.33.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3a371dc00282c4b84246509a5ddc808e61b9864aa1eae9ecc92bb1268b82db4a", size = 2042858, upload_time = "2025-04-02T09:49:03.419Z" }, + { url = "https://files.pythonhosted.org/packages/73/b4/9cd7b081fb0b1b4f8150507cd59d27b275c3e22ad60b35cb19ea0977d9b9/pydantic_core-2.33.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:f59295ecc75a1788af8ba92f2e8c6eeaa5a94c22fc4d151e8d9638814f85c8fc", size = 1873745, upload_time = "2025-04-02T09:49:05.391Z" }, + { url = "https://files.pythonhosted.org/packages/e1/d7/9ddb7575d4321e40d0363903c2576c8c0c3280ebea137777e5ab58d723e3/pydantic_core-2.33.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08530b8ac922003033f399128505f513e30ca770527cc8bbacf75a84fcc2c74b", size = 1904188, upload_time = "2025-04-02T09:49:07.352Z" }, + { url = "https://files.pythonhosted.org/packages/d1/a8/3194ccfe461bb08da19377ebec8cb4f13c9bd82e13baebc53c5c7c39a029/pydantic_core-2.33.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bae370459da6a5466978c0eacf90690cb57ec9d533f8e63e564ef3822bfa04fe", size = 2083479, upload_time = "2025-04-02T09:49:09.304Z" }, + { url = "https://files.pythonhosted.org/packages/42/c7/84cb569555d7179ca0b3f838cef08f66f7089b54432f5b8599aac6e9533e/pydantic_core-2.33.1-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e3de2777e3b9f4d603112f78006f4ae0acb936e95f06da6cb1a45fbad6bdb4b5", size = 2118415, upload_time = "2025-04-02T09:49:11.25Z" }, + { url = "https://files.pythonhosted.org/packages/3b/67/72abb8c73e0837716afbb58a59cc9e3ae43d1aa8677f3b4bc72c16142716/pydantic_core-2.33.1-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:3a64e81e8cba118e108d7126362ea30e021291b7805d47e4896e52c791be2761", size = 2079623, upload_time = "2025-04-02T09:49:13.292Z" }, + { url = "https://files.pythonhosted.org/packages/0b/cd/c59707e35a47ba4cbbf153c3f7c56420c58653b5801b055dc52cccc8e2dc/pydantic_core-2.33.1-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:52928d8c1b6bda03cc6d811e8923dffc87a2d3c8b3bfd2ce16471c7147a24850", size = 2250175, upload_time = "2025-04-02T09:49:15.597Z" }, + { url = "https://files.pythonhosted.org/packages/84/32/e4325a6676b0bed32d5b084566ec86ed7fd1e9bcbfc49c578b1755bde920/pydantic_core-2.33.1-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:1b30d92c9412beb5ac6b10a3eb7ef92ccb14e3f2a8d7732e2d739f58b3aa7544", size = 2254674, upload_time = "2025-04-02T09:49:17.61Z" }, + { url = "https://files.pythonhosted.org/packages/12/6f/5596dc418f2e292ffc661d21931ab34591952e2843e7168ea5a52591f6ff/pydantic_core-2.33.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:f995719707e0e29f0f41a8aa3bcea6e761a36c9136104d3189eafb83f5cec5e5", size = 2080951, upload_time = "2025-04-02T09:49:19.559Z" }, + { url = "https://files.pythonhosted.org/packages/2d/a8/c2c8f29bd18f7ef52de32a6deb9e3ee87ba18b7b2122636aa9f4438cf627/pydantic_core-2.33.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7edbc454a29fc6aeae1e1eecba4f07b63b8d76e76a748532233c4c167b4cb9ea", size = 2041791, upload_time = "2025-04-02T09:49:21.617Z" }, + { url = "https://files.pythonhosted.org/packages/08/ad/328081b1c82543ae49d0650048305058583c51f1a9a56a0d6e87bb3a2443/pydantic_core-2.33.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:ad05b683963f69a1d5d2c2bdab1274a31221ca737dbbceaa32bcb67359453cdd", size = 1873579, upload_time = "2025-04-02T09:49:23.667Z" }, + { url = "https://files.pythonhosted.org/packages/6e/8a/bc65dbf7e501e88367cdab06a2c1340457c785f0c72288cae737fd80c0fa/pydantic_core-2.33.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df6a94bf9452c6da9b5d76ed229a5683d0306ccb91cca8e1eea883189780d568", size = 1904189, upload_time = "2025-04-02T09:49:25.821Z" }, + { url = "https://files.pythonhosted.org/packages/9a/db/30ca6aefda211fb01ef185ca73cb7a0c6e7fe952c524025c8782b5acd771/pydantic_core-2.33.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7965c13b3967909a09ecc91f21d09cfc4576bf78140b988904e94f130f188396", size = 2084446, upload_time = "2025-04-02T09:49:27.866Z" }, + { url = "https://files.pythonhosted.org/packages/f2/89/a12b55286e30c9f476eab7c53c9249ec76faf70430596496ab0309f28629/pydantic_core-2.33.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3f1fdb790440a34f6ecf7679e1863b825cb5ffde858a9197f851168ed08371e5", size = 2118215, upload_time = "2025-04-02T09:49:30.321Z" }, + { url = "https://files.pythonhosted.org/packages/8e/55/12721c4a8d7951584ad3d9848b44442559cf1876e0bb424148d1060636b3/pydantic_core-2.33.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:5277aec8d879f8d05168fdd17ae811dd313b8ff894aeeaf7cd34ad28b4d77e33", size = 2079963, upload_time = "2025-04-02T09:49:32.804Z" }, + { url = "https://files.pythonhosted.org/packages/bd/0c/3391bd5d6ff62ea998db94732528d9bc32c560b0ed861c39119759461946/pydantic_core-2.33.1-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:8ab581d3530611897d863d1a649fb0644b860286b4718db919bfd51ece41f10b", size = 2249388, upload_time = "2025-04-02T09:49:34.906Z" }, + { url = "https://files.pythonhosted.org/packages/d3/5f/3e4feb042998d7886a9b523b372d83955cbc192a07013dcd24276db078ee/pydantic_core-2.33.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0483847fa9ad5e3412265c1bd72aad35235512d9ce9d27d81a56d935ef489672", size = 2255226, upload_time = "2025-04-02T09:49:37.412Z" }, + { url = "https://files.pythonhosted.org/packages/25/f2/1647933efaaad61846109a27619f3704929e758a09e6431b8f932a053d40/pydantic_core-2.33.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:de9e06abe3cc5ec6a2d5f75bc99b0bdca4f5c719a5b34026f8c57efbdecd2ee3", size = 2081073, upload_time = "2025-04-02T09:49:39.531Z" }, ] [[package]] name = "pygments" version = "2.19.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581 } +sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581, upload_time = "2025-01-06T17:26:30.443Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293 }, + { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293, upload_time = "2025-01-06T17:26:25.553Z" }, ] [[package]] name = "pyjwt" version = "2.10.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e7/46/bd74733ff231675599650d3e47f361794b22ef3e3770998dda30d3b63726/pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953", size = 87785 } +sdist = { url = "https://files.pythonhosted.org/packages/e7/46/bd74733ff231675599650d3e47f361794b22ef3e3770998dda30d3b63726/pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953", size = 87785, upload_time = "2024-11-28T03:43:29.933Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/61/ad/689f02752eeec26aed679477e80e632ef1b682313be70793d798c1d5fc8f/PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb", size = 22997 }, + { url = "https://files.pythonhosted.org/packages/61/ad/689f02752eeec26aed679477e80e632ef1b682313be70793d798c1d5fc8f/PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb", size = 22997, upload_time = "2024-11-28T03:43:27.893Z" }, ] [[package]] @@ -1295,9 +1297,9 @@ dependencies = [ { name = "pluggy" }, { name = "tomli", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891 } +sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891, upload_time = "2025-03-02T12:54:54.503Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634 }, + { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634, upload_time = "2025-03-02T12:54:52.069Z" }, ] [[package]] @@ -1308,80 +1310,165 @@ dependencies = [ { name = "pytest" }, { name = "typing-extensions", marker = "python_full_version < '3.10'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8e/c4/453c52c659521066969523e87d85d54139bbd17b78f09532fb8eb8cdb58e/pytest_asyncio-0.26.0.tar.gz", hash = "sha256:c4df2a697648241ff39e7f0e4a73050b03f123f760673956cf0d72a4990e312f", size = 54156 } +sdist = { url = "https://files.pythonhosted.org/packages/8e/c4/453c52c659521066969523e87d85d54139bbd17b78f09532fb8eb8cdb58e/pytest_asyncio-0.26.0.tar.gz", hash = "sha256:c4df2a697648241ff39e7f0e4a73050b03f123f760673956cf0d72a4990e312f", size = 54156, upload_time = "2025-03-25T06:22:28.883Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/20/7f/338843f449ace853647ace35870874f69a764d251872ed1b4de9f234822c/pytest_asyncio-0.26.0-py3-none-any.whl", hash = "sha256:7b51ed894f4fbea1340262bdae5135797ebbe21d8638978e35d31c6d19f72fb0", size = 19694 }, + { url = "https://files.pythonhosted.org/packages/20/7f/338843f449ace853647ace35870874f69a764d251872ed1b4de9f234822c/pytest_asyncio-0.26.0-py3-none-any.whl", hash = "sha256:7b51ed894f4fbea1340262bdae5135797ebbe21d8638978e35d31c6d19f72fb0", size = 19694, upload_time = "2025-03-25T06:22:27.807Z" }, ] [[package]] name = "python-dotenv" version = "1.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/88/2c/7bb1416c5620485aa793f2de31d3df393d3686aa8a8506d11e10e13c5baf/python_dotenv-1.1.0.tar.gz", hash = "sha256:41f90bc6f5f177fb41f53e87666db362025010eb28f60a01c9143bfa33a2b2d5", size = 39920 } +sdist = { url = "https://files.pythonhosted.org/packages/88/2c/7bb1416c5620485aa793f2de31d3df393d3686aa8a8506d11e10e13c5baf/python_dotenv-1.1.0.tar.gz", hash = "sha256:41f90bc6f5f177fb41f53e87666db362025010eb28f60a01c9143bfa33a2b2d5", size = 39920, upload_time = "2025-03-25T10:14:56.835Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/18/98a99ad95133c6a6e2005fe89faedf294a748bd5dc803008059409ac9b1e/python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d", size = 20256 }, + { url = "https://files.pythonhosted.org/packages/1e/18/98a99ad95133c6a6e2005fe89faedf294a748bd5dc803008059409ac9b1e/python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d", size = 20256, upload_time = "2025-03-25T10:14:55.034Z" }, ] [[package]] name = "python-multipart" version = "0.0.20" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f3/87/f44d7c9f274c7ee665a29b885ec97089ec5dc034c7f3fafa03da9e39a09e/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13", size = 37158 } +sdist = { url = "https://files.pythonhosted.org/packages/f3/87/f44d7c9f274c7ee665a29b885ec97089ec5dc034c7f3fafa03da9e39a09e/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13", size = 37158, upload_time = "2024-12-16T19:45:46.972Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546 }, + { url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546, upload_time = "2024-12-16T19:45:44.423Z" }, ] [[package]] name = "pyyaml" version = "6.0.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 } +sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload_time = "2024-08-06T20:33:50.674Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9b/95/a3fac87cb7158e231b5a6012e438c647e1a87f09f8e0d123acec8ab8bf71/PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", size = 184199 }, - { url = "https://files.pythonhosted.org/packages/c7/7a/68bd47624dab8fd4afbfd3c48e3b79efe09098ae941de5b58abcbadff5cb/PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", size = 171758 }, - { url = "https://files.pythonhosted.org/packages/49/ee/14c54df452143b9ee9f0f29074d7ca5516a36edb0b4cc40c3f280131656f/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", size = 718463 }, - { url = "https://files.pythonhosted.org/packages/4d/61/de363a97476e766574650d742205be468921a7b532aa2499fcd886b62530/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", size = 719280 }, - { url = "https://files.pythonhosted.org/packages/6b/4e/1523cb902fd98355e2e9ea5e5eb237cbc5f3ad5f3075fa65087aa0ecb669/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", size = 751239 }, - { url = "https://files.pythonhosted.org/packages/b7/33/5504b3a9a4464893c32f118a9cc045190a91637b119a9c881da1cf6b7a72/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", size = 695802 }, - { url = "https://files.pythonhosted.org/packages/5c/20/8347dcabd41ef3a3cdc4f7b7a2aff3d06598c8779faa189cdbf878b626a4/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", size = 720527 }, - { url = "https://files.pythonhosted.org/packages/be/aa/5afe99233fb360d0ff37377145a949ae258aaab831bde4792b32650a4378/PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", size = 144052 }, - { url = "https://files.pythonhosted.org/packages/b5/84/0fa4b06f6d6c958d207620fc60005e241ecedceee58931bb20138e1e5776/PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", size = 161774 }, - { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612 }, - { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040 }, - { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829 }, - { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167 }, - { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952 }, - { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301 }, - { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638 }, - { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850 }, - { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980 }, - { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873 }, - { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302 }, - { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154 }, - { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223 }, - { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542 }, - { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164 }, - { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611 }, - { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591 }, - { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338 }, - { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309 }, - { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679 }, - { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428 }, - { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361 }, - { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523 }, - { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660 }, - { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597 }, - { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527 }, - { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 }, - { url = "https://files.pythonhosted.org/packages/65/d8/b7a1db13636d7fb7d4ff431593c510c8b8fca920ade06ca8ef20015493c5/PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d", size = 184777 }, - { url = "https://files.pythonhosted.org/packages/0a/02/6ec546cd45143fdf9840b2c6be8d875116a64076218b61d68e12548e5839/PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f", size = 172318 }, - { url = "https://files.pythonhosted.org/packages/0e/9a/8cc68be846c972bda34f6c2a93abb644fb2476f4dcc924d52175786932c9/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290", size = 720891 }, - { url = "https://files.pythonhosted.org/packages/e9/6c/6e1b7f40181bc4805e2e07f4abc10a88ce4648e7e95ff1abe4ae4014a9b2/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12", size = 722614 }, - { url = "https://files.pythonhosted.org/packages/3d/32/e7bd8535d22ea2874cef6a81021ba019474ace0d13a4819c2a4bce79bd6a/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19", size = 737360 }, - { url = "https://files.pythonhosted.org/packages/d7/12/7322c1e30b9be969670b672573d45479edef72c9a0deac3bb2868f5d7469/PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e", size = 699006 }, - { url = "https://files.pythonhosted.org/packages/82/72/04fcad41ca56491995076630c3ec1e834be241664c0c09a64c9a2589b507/PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725", size = 723577 }, - { url = "https://files.pythonhosted.org/packages/ed/5e/46168b1f2757f1fcd442bc3029cd8767d88a98c9c05770d8b420948743bb/PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631", size = 144593 }, - { url = "https://files.pythonhosted.org/packages/19/87/5124b1c1f2412bb95c59ec481eaf936cd32f0fe2a7b16b97b81c4c017a6a/PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8", size = 162312 }, + { url = "https://files.pythonhosted.org/packages/9b/95/a3fac87cb7158e231b5a6012e438c647e1a87f09f8e0d123acec8ab8bf71/PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", size = 184199, upload_time = "2024-08-06T20:31:40.178Z" }, + { url = "https://files.pythonhosted.org/packages/c7/7a/68bd47624dab8fd4afbfd3c48e3b79efe09098ae941de5b58abcbadff5cb/PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", size = 171758, upload_time = "2024-08-06T20:31:42.173Z" }, + { url = "https://files.pythonhosted.org/packages/49/ee/14c54df452143b9ee9f0f29074d7ca5516a36edb0b4cc40c3f280131656f/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", size = 718463, upload_time = "2024-08-06T20:31:44.263Z" }, + { url = "https://files.pythonhosted.org/packages/4d/61/de363a97476e766574650d742205be468921a7b532aa2499fcd886b62530/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", size = 719280, upload_time = "2024-08-06T20:31:50.199Z" }, + { url = "https://files.pythonhosted.org/packages/6b/4e/1523cb902fd98355e2e9ea5e5eb237cbc5f3ad5f3075fa65087aa0ecb669/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", size = 751239, upload_time = "2024-08-06T20:31:52.292Z" }, + { url = "https://files.pythonhosted.org/packages/b7/33/5504b3a9a4464893c32f118a9cc045190a91637b119a9c881da1cf6b7a72/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", size = 695802, upload_time = "2024-08-06T20:31:53.836Z" }, + { url = "https://files.pythonhosted.org/packages/5c/20/8347dcabd41ef3a3cdc4f7b7a2aff3d06598c8779faa189cdbf878b626a4/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", size = 720527, upload_time = "2024-08-06T20:31:55.565Z" }, + { url = "https://files.pythonhosted.org/packages/be/aa/5afe99233fb360d0ff37377145a949ae258aaab831bde4792b32650a4378/PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", size = 144052, upload_time = "2024-08-06T20:31:56.914Z" }, + { url = "https://files.pythonhosted.org/packages/b5/84/0fa4b06f6d6c958d207620fc60005e241ecedceee58931bb20138e1e5776/PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", size = 161774, upload_time = "2024-08-06T20:31:58.304Z" }, + { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612, upload_time = "2024-08-06T20:32:03.408Z" }, + { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040, upload_time = "2024-08-06T20:32:04.926Z" }, + { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829, upload_time = "2024-08-06T20:32:06.459Z" }, + { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167, upload_time = "2024-08-06T20:32:08.338Z" }, + { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952, upload_time = "2024-08-06T20:32:14.124Z" }, + { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301, upload_time = "2024-08-06T20:32:16.17Z" }, + { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638, upload_time = "2024-08-06T20:32:18.555Z" }, + { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850, upload_time = "2024-08-06T20:32:19.889Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980, upload_time = "2024-08-06T20:32:21.273Z" }, + { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873, upload_time = "2024-08-06T20:32:25.131Z" }, + { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302, upload_time = "2024-08-06T20:32:26.511Z" }, + { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154, upload_time = "2024-08-06T20:32:28.363Z" }, + { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223, upload_time = "2024-08-06T20:32:30.058Z" }, + { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542, upload_time = "2024-08-06T20:32:31.881Z" }, + { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164, upload_time = "2024-08-06T20:32:37.083Z" }, + { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611, upload_time = "2024-08-06T20:32:38.898Z" }, + { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591, upload_time = "2024-08-06T20:32:40.241Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338, upload_time = "2024-08-06T20:32:41.93Z" }, + { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309, upload_time = "2024-08-06T20:32:43.4Z" }, + { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679, upload_time = "2024-08-06T20:32:44.801Z" }, + { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428, upload_time = "2024-08-06T20:32:46.432Z" }, + { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361, upload_time = "2024-08-06T20:32:51.188Z" }, + { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523, upload_time = "2024-08-06T20:32:53.019Z" }, + { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660, upload_time = "2024-08-06T20:32:54.708Z" }, + { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597, upload_time = "2024-08-06T20:32:56.985Z" }, + { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527, upload_time = "2024-08-06T20:33:03.001Z" }, + { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload_time = "2024-08-06T20:33:04.33Z" }, + { url = "https://files.pythonhosted.org/packages/65/d8/b7a1db13636d7fb7d4ff431593c510c8b8fca920ade06ca8ef20015493c5/PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d", size = 184777, upload_time = "2024-08-06T20:33:25.896Z" }, + { url = "https://files.pythonhosted.org/packages/0a/02/6ec546cd45143fdf9840b2c6be8d875116a64076218b61d68e12548e5839/PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f", size = 172318, upload_time = "2024-08-06T20:33:27.212Z" }, + { url = "https://files.pythonhosted.org/packages/0e/9a/8cc68be846c972bda34f6c2a93abb644fb2476f4dcc924d52175786932c9/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290", size = 720891, upload_time = "2024-08-06T20:33:28.974Z" }, + { url = "https://files.pythonhosted.org/packages/e9/6c/6e1b7f40181bc4805e2e07f4abc10a88ce4648e7e95ff1abe4ae4014a9b2/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12", size = 722614, upload_time = "2024-08-06T20:33:34.157Z" }, + { url = "https://files.pythonhosted.org/packages/3d/32/e7bd8535d22ea2874cef6a81021ba019474ace0d13a4819c2a4bce79bd6a/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19", size = 737360, upload_time = "2024-08-06T20:33:35.84Z" }, + { url = "https://files.pythonhosted.org/packages/d7/12/7322c1e30b9be969670b672573d45479edef72c9a0deac3bb2868f5d7469/PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e", size = 699006, upload_time = "2024-08-06T20:33:37.501Z" }, + { url = "https://files.pythonhosted.org/packages/82/72/04fcad41ca56491995076630c3ec1e834be241664c0c09a64c9a2589b507/PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725", size = 723577, upload_time = "2024-08-06T20:33:39.389Z" }, + { url = "https://files.pythonhosted.org/packages/ed/5e/46168b1f2757f1fcd442bc3029cd8767d88a98c9c05770d8b420948743bb/PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631", size = 144593, upload_time = "2024-08-06T20:33:46.63Z" }, + { url = "https://files.pythonhosted.org/packages/19/87/5124b1c1f2412bb95c59ec481eaf936cd32f0fe2a7b16b97b81c4c017a6a/PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8", size = 162312, upload_time = "2024-08-06T20:33:49.073Z" }, +] + +[[package]] +name = "regex" +version = "2024.11.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/5f/bd69653fbfb76cf8604468d3b4ec4c403197144c7bfe0e6a5fc9e02a07cb/regex-2024.11.6.tar.gz", hash = "sha256:7ab159b063c52a0333c884e4679f8d7a85112ee3078fe3d9004b2dd875585519", size = 399494, upload_time = "2024-11-06T20:12:31.635Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/3c/4651f6b130c6842a8f3df82461a8950f923925db8b6961063e82744bddcc/regex-2024.11.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ff590880083d60acc0433f9c3f713c51f7ac6ebb9adf889c79a261ecf541aa91", size = 482674, upload_time = "2024-11-06T20:08:57.575Z" }, + { url = "https://files.pythonhosted.org/packages/15/51/9f35d12da8434b489c7b7bffc205c474a0a9432a889457026e9bc06a297a/regex-2024.11.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:658f90550f38270639e83ce492f27d2c8d2cd63805c65a13a14d36ca126753f0", size = 287684, upload_time = "2024-11-06T20:08:59.787Z" }, + { url = "https://files.pythonhosted.org/packages/bd/18/b731f5510d1b8fb63c6b6d3484bfa9a59b84cc578ac8b5172970e05ae07c/regex-2024.11.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:164d8b7b3b4bcb2068b97428060b2a53be050085ef94eca7f240e7947f1b080e", size = 284589, upload_time = "2024-11-06T20:09:01.896Z" }, + { url = "https://files.pythonhosted.org/packages/78/a2/6dd36e16341ab95e4c6073426561b9bfdeb1a9c9b63ab1b579c2e96cb105/regex-2024.11.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3660c82f209655a06b587d55e723f0b813d3a7db2e32e5e7dc64ac2a9e86fde", size = 782511, upload_time = "2024-11-06T20:09:04.062Z" }, + { url = "https://files.pythonhosted.org/packages/1b/2b/323e72d5d2fd8de0d9baa443e1ed70363ed7e7b2fb526f5950c5cb99c364/regex-2024.11.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d22326fcdef5e08c154280b71163ced384b428343ae16a5ab2b3354aed12436e", size = 821149, upload_time = "2024-11-06T20:09:06.237Z" }, + { url = "https://files.pythonhosted.org/packages/90/30/63373b9ea468fbef8a907fd273e5c329b8c9535fee36fc8dba5fecac475d/regex-2024.11.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1ac758ef6aebfc8943560194e9fd0fa18bcb34d89fd8bd2af18183afd8da3a2", size = 809707, upload_time = "2024-11-06T20:09:07.715Z" }, + { url = "https://files.pythonhosted.org/packages/f2/98/26d3830875b53071f1f0ae6d547f1d98e964dd29ad35cbf94439120bb67a/regex-2024.11.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:997d6a487ff00807ba810e0f8332c18b4eb8d29463cfb7c820dc4b6e7562d0cf", size = 781702, upload_time = "2024-11-06T20:09:10.101Z" }, + { url = "https://files.pythonhosted.org/packages/87/55/eb2a068334274db86208ab9d5599ffa63631b9f0f67ed70ea7c82a69bbc8/regex-2024.11.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:02a02d2bb04fec86ad61f3ea7f49c015a0681bf76abb9857f945d26159d2968c", size = 771976, upload_time = "2024-11-06T20:09:11.566Z" }, + { url = "https://files.pythonhosted.org/packages/74/c0/be707bcfe98254d8f9d2cff55d216e946f4ea48ad2fd8cf1428f8c5332ba/regex-2024.11.6-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f02f93b92358ee3f78660e43b4b0091229260c5d5c408d17d60bf26b6c900e86", size = 697397, upload_time = "2024-11-06T20:09:13.119Z" }, + { url = "https://files.pythonhosted.org/packages/49/dc/bb45572ceb49e0f6509f7596e4ba7031f6819ecb26bc7610979af5a77f45/regex-2024.11.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:06eb1be98df10e81ebaded73fcd51989dcf534e3c753466e4b60c4697a003b67", size = 768726, upload_time = "2024-11-06T20:09:14.85Z" }, + { url = "https://files.pythonhosted.org/packages/5a/db/f43fd75dc4c0c2d96d0881967897926942e935d700863666f3c844a72ce6/regex-2024.11.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:040df6fe1a5504eb0f04f048e6d09cd7c7110fef851d7c567a6b6e09942feb7d", size = 775098, upload_time = "2024-11-06T20:09:16.504Z" }, + { url = "https://files.pythonhosted.org/packages/99/d7/f94154db29ab5a89d69ff893159b19ada89e76b915c1293e98603d39838c/regex-2024.11.6-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabbfc59f2c6edba2a6622c647b716e34e8e3867e0ab975412c5c2f79b82da2", size = 839325, upload_time = "2024-11-06T20:09:18.698Z" }, + { url = "https://files.pythonhosted.org/packages/f7/17/3cbfab1f23356fbbf07708220ab438a7efa1e0f34195bf857433f79f1788/regex-2024.11.6-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8447d2d39b5abe381419319f942de20b7ecd60ce86f16a23b0698f22e1b70008", size = 843277, upload_time = "2024-11-06T20:09:21.725Z" }, + { url = "https://files.pythonhosted.org/packages/7e/f2/48b393b51900456155de3ad001900f94298965e1cad1c772b87f9cfea011/regex-2024.11.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:da8f5fc57d1933de22a9e23eec290a0d8a5927a5370d24bda9a6abe50683fe62", size = 773197, upload_time = "2024-11-06T20:09:24.092Z" }, + { url = "https://files.pythonhosted.org/packages/45/3f/ef9589aba93e084cd3f8471fded352826dcae8489b650d0b9b27bc5bba8a/regex-2024.11.6-cp310-cp310-win32.whl", hash = "sha256:b489578720afb782f6ccf2840920f3a32e31ba28a4b162e13900c3e6bd3f930e", size = 261714, upload_time = "2024-11-06T20:09:26.36Z" }, + { url = "https://files.pythonhosted.org/packages/42/7e/5f1b92c8468290c465fd50c5318da64319133231415a8aa6ea5ab995a815/regex-2024.11.6-cp310-cp310-win_amd64.whl", hash = "sha256:5071b2093e793357c9d8b2929dfc13ac5f0a6c650559503bb81189d0a3814519", size = 274042, upload_time = "2024-11-06T20:09:28.762Z" }, + { url = "https://files.pythonhosted.org/packages/58/58/7e4d9493a66c88a7da6d205768119f51af0f684fe7be7bac8328e217a52c/regex-2024.11.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5478c6962ad548b54a591778e93cd7c456a7a29f8eca9c49e4f9a806dcc5d638", size = 482669, upload_time = "2024-11-06T20:09:31.064Z" }, + { url = "https://files.pythonhosted.org/packages/34/4c/8f8e631fcdc2ff978609eaeef1d6994bf2f028b59d9ac67640ed051f1218/regex-2024.11.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c89a8cc122b25ce6945f0423dc1352cb9593c68abd19223eebbd4e56612c5b7", size = 287684, upload_time = "2024-11-06T20:09:32.915Z" }, + { url = "https://files.pythonhosted.org/packages/c5/1b/f0e4d13e6adf866ce9b069e191f303a30ab1277e037037a365c3aad5cc9c/regex-2024.11.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:94d87b689cdd831934fa3ce16cc15cd65748e6d689f5d2b8f4f4df2065c9fa20", size = 284589, upload_time = "2024-11-06T20:09:35.504Z" }, + { url = "https://files.pythonhosted.org/packages/25/4d/ab21047f446693887f25510887e6820b93f791992994f6498b0318904d4a/regex-2024.11.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1062b39a0a2b75a9c694f7a08e7183a80c63c0d62b301418ffd9c35f55aaa114", size = 792121, upload_time = "2024-11-06T20:09:37.701Z" }, + { url = "https://files.pythonhosted.org/packages/45/ee/c867e15cd894985cb32b731d89576c41a4642a57850c162490ea34b78c3b/regex-2024.11.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:167ed4852351d8a750da48712c3930b031f6efdaa0f22fa1933716bfcd6bf4a3", size = 831275, upload_time = "2024-11-06T20:09:40.371Z" }, + { url = "https://files.pythonhosted.org/packages/b3/12/b0f480726cf1c60f6536fa5e1c95275a77624f3ac8fdccf79e6727499e28/regex-2024.11.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d548dafee61f06ebdb584080621f3e0c23fff312f0de1afc776e2a2ba99a74f", size = 818257, upload_time = "2024-11-06T20:09:43.059Z" }, + { url = "https://files.pythonhosted.org/packages/bf/ce/0d0e61429f603bac433910d99ef1a02ce45a8967ffbe3cbee48599e62d88/regex-2024.11.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a19f302cd1ce5dd01a9099aaa19cae6173306d1302a43b627f62e21cf18ac0", size = 792727, upload_time = "2024-11-06T20:09:48.19Z" }, + { url = "https://files.pythonhosted.org/packages/e4/c1/243c83c53d4a419c1556f43777ccb552bccdf79d08fda3980e4e77dd9137/regex-2024.11.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bec9931dfb61ddd8ef2ebc05646293812cb6b16b60cf7c9511a832b6f1854b55", size = 780667, upload_time = "2024-11-06T20:09:49.828Z" }, + { url = "https://files.pythonhosted.org/packages/c5/f4/75eb0dd4ce4b37f04928987f1d22547ddaf6c4bae697623c1b05da67a8aa/regex-2024.11.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9714398225f299aa85267fd222f7142fcb5c769e73d7733344efc46f2ef5cf89", size = 776963, upload_time = "2024-11-06T20:09:51.819Z" }, + { url = "https://files.pythonhosted.org/packages/16/5d/95c568574e630e141a69ff8a254c2f188b4398e813c40d49228c9bbd9875/regex-2024.11.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:202eb32e89f60fc147a41e55cb086db2a3f8cb82f9a9a88440dcfc5d37faae8d", size = 784700, upload_time = "2024-11-06T20:09:53.982Z" }, + { url = "https://files.pythonhosted.org/packages/8e/b5/f8495c7917f15cc6fee1e7f395e324ec3e00ab3c665a7dc9d27562fd5290/regex-2024.11.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:4181b814e56078e9b00427ca358ec44333765f5ca1b45597ec7446d3a1ef6e34", size = 848592, upload_time = "2024-11-06T20:09:56.222Z" }, + { url = "https://files.pythonhosted.org/packages/1c/80/6dd7118e8cb212c3c60b191b932dc57db93fb2e36fb9e0e92f72a5909af9/regex-2024.11.6-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:068376da5a7e4da51968ce4c122a7cd31afaaec4fccc7856c92f63876e57b51d", size = 852929, upload_time = "2024-11-06T20:09:58.642Z" }, + { url = "https://files.pythonhosted.org/packages/11/9b/5a05d2040297d2d254baf95eeeb6df83554e5e1df03bc1a6687fc4ba1f66/regex-2024.11.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ac10f2c4184420d881a3475fb2c6f4d95d53a8d50209a2500723d831036f7c45", size = 781213, upload_time = "2024-11-06T20:10:00.867Z" }, + { url = "https://files.pythonhosted.org/packages/26/b7/b14e2440156ab39e0177506c08c18accaf2b8932e39fb092074de733d868/regex-2024.11.6-cp311-cp311-win32.whl", hash = "sha256:c36f9b6f5f8649bb251a5f3f66564438977b7ef8386a52460ae77e6070d309d9", size = 261734, upload_time = "2024-11-06T20:10:03.361Z" }, + { url = "https://files.pythonhosted.org/packages/80/32/763a6cc01d21fb3819227a1cc3f60fd251c13c37c27a73b8ff4315433a8e/regex-2024.11.6-cp311-cp311-win_amd64.whl", hash = "sha256:02e28184be537f0e75c1f9b2f8847dc51e08e6e171c6bde130b2687e0c33cf60", size = 274052, upload_time = "2024-11-06T20:10:05.179Z" }, + { url = "https://files.pythonhosted.org/packages/ba/30/9a87ce8336b172cc232a0db89a3af97929d06c11ceaa19d97d84fa90a8f8/regex-2024.11.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:52fb28f528778f184f870b7cf8f225f5eef0a8f6e3778529bdd40c7b3920796a", size = 483781, upload_time = "2024-11-06T20:10:07.07Z" }, + { url = "https://files.pythonhosted.org/packages/01/e8/00008ad4ff4be8b1844786ba6636035f7ef926db5686e4c0f98093612add/regex-2024.11.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdd6028445d2460f33136c55eeb1f601ab06d74cb3347132e1c24250187500d9", size = 288455, upload_time = "2024-11-06T20:10:09.117Z" }, + { url = "https://files.pythonhosted.org/packages/60/85/cebcc0aff603ea0a201667b203f13ba75d9fc8668fab917ac5b2de3967bc/regex-2024.11.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:805e6b60c54bf766b251e94526ebad60b7de0c70f70a4e6210ee2891acb70bf2", size = 284759, upload_time = "2024-11-06T20:10:11.155Z" }, + { url = "https://files.pythonhosted.org/packages/94/2b/701a4b0585cb05472a4da28ee28fdfe155f3638f5e1ec92306d924e5faf0/regex-2024.11.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b85c2530be953a890eaffde05485238f07029600e8f098cdf1848d414a8b45e4", size = 794976, upload_time = "2024-11-06T20:10:13.24Z" }, + { url = "https://files.pythonhosted.org/packages/4b/bf/fa87e563bf5fee75db8915f7352e1887b1249126a1be4813837f5dbec965/regex-2024.11.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb26437975da7dc36b7efad18aa9dd4ea569d2357ae6b783bf1118dabd9ea577", size = 833077, upload_time = "2024-11-06T20:10:15.37Z" }, + { url = "https://files.pythonhosted.org/packages/a1/56/7295e6bad94b047f4d0834e4779491b81216583c00c288252ef625c01d23/regex-2024.11.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abfa5080c374a76a251ba60683242bc17eeb2c9818d0d30117b4486be10c59d3", size = 823160, upload_time = "2024-11-06T20:10:19.027Z" }, + { url = "https://files.pythonhosted.org/packages/fb/13/e3b075031a738c9598c51cfbc4c7879e26729c53aa9cca59211c44235314/regex-2024.11.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b7fa6606c2881c1db9479b0eaa11ed5dfa11c8d60a474ff0e095099f39d98e", size = 796896, upload_time = "2024-11-06T20:10:21.85Z" }, + { url = "https://files.pythonhosted.org/packages/24/56/0b3f1b66d592be6efec23a795b37732682520b47c53da5a32c33ed7d84e3/regex-2024.11.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c32f75920cf99fe6b6c539c399a4a128452eaf1af27f39bce8909c9a3fd8cbe", size = 783997, upload_time = "2024-11-06T20:10:24.329Z" }, + { url = "https://files.pythonhosted.org/packages/f9/a1/eb378dada8b91c0e4c5f08ffb56f25fcae47bf52ad18f9b2f33b83e6d498/regex-2024.11.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:982e6d21414e78e1f51cf595d7f321dcd14de1f2881c5dc6a6e23bbbbd68435e", size = 781725, upload_time = "2024-11-06T20:10:28.067Z" }, + { url = "https://files.pythonhosted.org/packages/83/f2/033e7dec0cfd6dda93390089864732a3409246ffe8b042e9554afa9bff4e/regex-2024.11.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a7c2155f790e2fb448faed6dd241386719802296ec588a8b9051c1f5c481bc29", size = 789481, upload_time = "2024-11-06T20:10:31.612Z" }, + { url = "https://files.pythonhosted.org/packages/83/23/15d4552ea28990a74e7696780c438aadd73a20318c47e527b47a4a5a596d/regex-2024.11.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149f5008d286636e48cd0b1dd65018548944e495b0265b45e1bffecce1ef7f39", size = 852896, upload_time = "2024-11-06T20:10:34.054Z" }, + { url = "https://files.pythonhosted.org/packages/e3/39/ed4416bc90deedbfdada2568b2cb0bc1fdb98efe11f5378d9892b2a88f8f/regex-2024.11.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e5364a4502efca094731680e80009632ad6624084aff9a23ce8c8c6820de3e51", size = 860138, upload_time = "2024-11-06T20:10:36.142Z" }, + { url = "https://files.pythonhosted.org/packages/93/2d/dd56bb76bd8e95bbce684326302f287455b56242a4f9c61f1bc76e28360e/regex-2024.11.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0a86e7eeca091c09e021db8eb72d54751e527fa47b8d5787caf96d9831bd02ad", size = 787692, upload_time = "2024-11-06T20:10:38.394Z" }, + { url = "https://files.pythonhosted.org/packages/0b/55/31877a249ab7a5156758246b9c59539abbeba22461b7d8adc9e8475ff73e/regex-2024.11.6-cp312-cp312-win32.whl", hash = "sha256:32f9a4c643baad4efa81d549c2aadefaeba12249b2adc5af541759237eee1c54", size = 262135, upload_time = "2024-11-06T20:10:40.367Z" }, + { url = "https://files.pythonhosted.org/packages/38/ec/ad2d7de49a600cdb8dd78434a1aeffe28b9d6fc42eb36afab4a27ad23384/regex-2024.11.6-cp312-cp312-win_amd64.whl", hash = "sha256:a93c194e2df18f7d264092dc8539b8ffb86b45b899ab976aa15d48214138e81b", size = 273567, upload_time = "2024-11-06T20:10:43.467Z" }, + { url = "https://files.pythonhosted.org/packages/90/73/bcb0e36614601016552fa9344544a3a2ae1809dc1401b100eab02e772e1f/regex-2024.11.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a6ba92c0bcdf96cbf43a12c717eae4bc98325ca3730f6b130ffa2e3c3c723d84", size = 483525, upload_time = "2024-11-06T20:10:45.19Z" }, + { url = "https://files.pythonhosted.org/packages/0f/3f/f1a082a46b31e25291d830b369b6b0c5576a6f7fb89d3053a354c24b8a83/regex-2024.11.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:525eab0b789891ac3be914d36893bdf972d483fe66551f79d3e27146191a37d4", size = 288324, upload_time = "2024-11-06T20:10:47.177Z" }, + { url = "https://files.pythonhosted.org/packages/09/c9/4e68181a4a652fb3ef5099e077faf4fd2a694ea6e0f806a7737aff9e758a/regex-2024.11.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:086a27a0b4ca227941700e0b31425e7a28ef1ae8e5e05a33826e17e47fbfdba0", size = 284617, upload_time = "2024-11-06T20:10:49.312Z" }, + { url = "https://files.pythonhosted.org/packages/fc/fd/37868b75eaf63843165f1d2122ca6cb94bfc0271e4428cf58c0616786dce/regex-2024.11.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bde01f35767c4a7899b7eb6e823b125a64de314a8ee9791367c9a34d56af18d0", size = 795023, upload_time = "2024-11-06T20:10:51.102Z" }, + { url = "https://files.pythonhosted.org/packages/c4/7c/d4cd9c528502a3dedb5c13c146e7a7a539a3853dc20209c8e75d9ba9d1b2/regex-2024.11.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b583904576650166b3d920d2bcce13971f6f9e9a396c673187f49811b2769dc7", size = 833072, upload_time = "2024-11-06T20:10:52.926Z" }, + { url = "https://files.pythonhosted.org/packages/4f/db/46f563a08f969159c5a0f0e722260568425363bea43bb7ae370becb66a67/regex-2024.11.6-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c4de13f06a0d54fa0d5ab1b7138bfa0d883220965a29616e3ea61b35d5f5fc7", size = 823130, upload_time = "2024-11-06T20:10:54.828Z" }, + { url = "https://files.pythonhosted.org/packages/db/60/1eeca2074f5b87df394fccaa432ae3fc06c9c9bfa97c5051aed70e6e00c2/regex-2024.11.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3cde6e9f2580eb1665965ce9bf17ff4952f34f5b126beb509fee8f4e994f143c", size = 796857, upload_time = "2024-11-06T20:10:56.634Z" }, + { url = "https://files.pythonhosted.org/packages/10/db/ac718a08fcee981554d2f7bb8402f1faa7e868c1345c16ab1ebec54b0d7b/regex-2024.11.6-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d7f453dca13f40a02b79636a339c5b62b670141e63efd511d3f8f73fba162b3", size = 784006, upload_time = "2024-11-06T20:10:59.369Z" }, + { url = "https://files.pythonhosted.org/packages/c2/41/7da3fe70216cea93144bf12da2b87367590bcf07db97604edeea55dac9ad/regex-2024.11.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59dfe1ed21aea057a65c6b586afd2a945de04fc7db3de0a6e3ed5397ad491b07", size = 781650, upload_time = "2024-11-06T20:11:02.042Z" }, + { url = "https://files.pythonhosted.org/packages/a7/d5/880921ee4eec393a4752e6ab9f0fe28009435417c3102fc413f3fe81c4e5/regex-2024.11.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b97c1e0bd37c5cd7902e65f410779d39eeda155800b65fc4d04cc432efa9bc6e", size = 789545, upload_time = "2024-11-06T20:11:03.933Z" }, + { url = "https://files.pythonhosted.org/packages/dc/96/53770115e507081122beca8899ab7f5ae28ae790bfcc82b5e38976df6a77/regex-2024.11.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f9d1e379028e0fc2ae3654bac3cbbef81bf3fd571272a42d56c24007979bafb6", size = 853045, upload_time = "2024-11-06T20:11:06.497Z" }, + { url = "https://files.pythonhosted.org/packages/31/d3/1372add5251cc2d44b451bd94f43b2ec78e15a6e82bff6a290ef9fd8f00a/regex-2024.11.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:13291b39131e2d002a7940fb176e120bec5145f3aeb7621be6534e46251912c4", size = 860182, upload_time = "2024-11-06T20:11:09.06Z" }, + { url = "https://files.pythonhosted.org/packages/ed/e3/c446a64984ea9f69982ba1a69d4658d5014bc7a0ea468a07e1a1265db6e2/regex-2024.11.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f51f88c126370dcec4908576c5a627220da6c09d0bff31cfa89f2523843316d", size = 787733, upload_time = "2024-11-06T20:11:11.256Z" }, + { url = "https://files.pythonhosted.org/packages/2b/f1/e40c8373e3480e4f29f2692bd21b3e05f296d3afebc7e5dcf21b9756ca1c/regex-2024.11.6-cp313-cp313-win32.whl", hash = "sha256:63b13cfd72e9601125027202cad74995ab26921d8cd935c25f09c630436348ff", size = 262122, upload_time = "2024-11-06T20:11:13.161Z" }, + { url = "https://files.pythonhosted.org/packages/45/94/bc295babb3062a731f52621cdc992d123111282e291abaf23faa413443ea/regex-2024.11.6-cp313-cp313-win_amd64.whl", hash = "sha256:2b3361af3198667e99927da8b84c1b010752fa4b1115ee30beaa332cabc3ef1a", size = 273545, upload_time = "2024-11-06T20:11:15Z" }, + { url = "https://files.pythonhosted.org/packages/89/23/c4a86df398e57e26f93b13ae63acce58771e04bdde86092502496fa57f9c/regex-2024.11.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5704e174f8ccab2026bd2f1ab6c510345ae8eac818b613d7d73e785f1310f839", size = 482682, upload_time = "2024-11-06T20:11:52.65Z" }, + { url = "https://files.pythonhosted.org/packages/3c/8b/45c24ab7a51a1658441b961b86209c43e6bb9d39caf1e63f46ce6ea03bc7/regex-2024.11.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:220902c3c5cc6af55d4fe19ead504de80eb91f786dc102fbd74894b1551f095e", size = 287679, upload_time = "2024-11-06T20:11:55.011Z" }, + { url = "https://files.pythonhosted.org/packages/7a/d1/598de10b17fdafc452d11f7dada11c3be4e379a8671393e4e3da3c4070df/regex-2024.11.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5e7e351589da0850c125f1600a4c4ba3c722efefe16b297de54300f08d734fbf", size = 284578, upload_time = "2024-11-06T20:11:57.033Z" }, + { url = "https://files.pythonhosted.org/packages/49/70/c7eaa219efa67a215846766fde18d92d54cb590b6a04ffe43cef30057622/regex-2024.11.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5056b185ca113c88e18223183aa1a50e66507769c9640a6ff75859619d73957b", size = 782012, upload_time = "2024-11-06T20:11:59.218Z" }, + { url = "https://files.pythonhosted.org/packages/89/e5/ef52c7eb117dd20ff1697968219971d052138965a4d3d9b95e92e549f505/regex-2024.11.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e34b51b650b23ed3354b5a07aab37034d9f923db2a40519139af34f485f77d0", size = 820580, upload_time = "2024-11-06T20:12:01.969Z" }, + { url = "https://files.pythonhosted.org/packages/5f/3f/9f5da81aff1d4167ac52711acf789df13e789fe6ac9545552e49138e3282/regex-2024.11.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5670bce7b200273eee1840ef307bfa07cda90b38ae56e9a6ebcc9f50da9c469b", size = 809110, upload_time = "2024-11-06T20:12:04.786Z" }, + { url = "https://files.pythonhosted.org/packages/86/44/2101cc0890c3621b90365c9ee8d7291a597c0722ad66eccd6ffa7f1bcc09/regex-2024.11.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08986dce1339bc932923e7d1232ce9881499a0e02925f7402fb7c982515419ef", size = 780919, upload_time = "2024-11-06T20:12:06.944Z" }, + { url = "https://files.pythonhosted.org/packages/ce/2e/3e0668d8d1c7c3c0d397bf54d92fc182575b3a26939aed5000d3cc78760f/regex-2024.11.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93c0b12d3d3bc25af4ebbf38f9ee780a487e8bf6954c115b9f015822d3bb8e48", size = 771515, upload_time = "2024-11-06T20:12:09.9Z" }, + { url = "https://files.pythonhosted.org/packages/a6/49/1bc4584254355e3dba930a3a2fd7ad26ccba3ebbab7d9100db0aff2eedb0/regex-2024.11.6-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:764e71f22ab3b305e7f4c21f1a97e1526a25ebdd22513e251cf376760213da13", size = 696957, upload_time = "2024-11-06T20:12:12.319Z" }, + { url = "https://files.pythonhosted.org/packages/c8/dd/42879c1fc8a37a887cd08e358af3d3ba9e23038cd77c7fe044a86d9450ba/regex-2024.11.6-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f056bf21105c2515c32372bbc057f43eb02aae2fda61052e2f7622c801f0b4e2", size = 768088, upload_time = "2024-11-06T20:12:15.149Z" }, + { url = "https://files.pythonhosted.org/packages/89/96/c05a0fe173cd2acd29d5e13c1adad8b706bcaa71b169e1ee57dcf2e74584/regex-2024.11.6-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:69ab78f848845569401469da20df3e081e6b5a11cb086de3eed1d48f5ed57c95", size = 774752, upload_time = "2024-11-06T20:12:17.416Z" }, + { url = "https://files.pythonhosted.org/packages/b5/f3/a757748066255f97f14506483436c5f6aded7af9e37bca04ec30c90ca683/regex-2024.11.6-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:86fddba590aad9208e2fa8b43b4c098bb0ec74f15718bb6a704e3c63e2cef3e9", size = 838862, upload_time = "2024-11-06T20:12:19.639Z" }, + { url = "https://files.pythonhosted.org/packages/5c/93/c6d2092fd479dcaeea40fc8fa673822829181ded77d294a7f950f1dda6e2/regex-2024.11.6-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:684d7a212682996d21ca12ef3c17353c021fe9de6049e19ac8481ec35574a70f", size = 842622, upload_time = "2024-11-06T20:12:21.841Z" }, + { url = "https://files.pythonhosted.org/packages/ff/9c/daa99532c72f25051a90ef90e1413a8d54413a9e64614d9095b0c1c154d0/regex-2024.11.6-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a03e02f48cd1abbd9f3b7e3586d97c8f7a9721c436f51a5245b3b9483044480b", size = 772713, upload_time = "2024-11-06T20:12:24.785Z" }, + { url = "https://files.pythonhosted.org/packages/13/5d/61a533ccb8c231b474ac8e3a7d70155b00dfc61af6cafdccd1947df6d735/regex-2024.11.6-cp39-cp39-win32.whl", hash = "sha256:41758407fc32d5c3c5de163888068cfee69cb4c2be844e7ac517a52770f9af57", size = 261756, upload_time = "2024-11-06T20:12:26.975Z" }, + { url = "https://files.pythonhosted.org/packages/dc/7b/e59b7f7c91ae110d154370c24133f947262525b5d6406df65f23422acc17/regex-2024.11.6-cp39-cp39-win_amd64.whl", hash = "sha256:b2837718570f95dd41675328e111345f9b7095d821bac435aac173ac80b19983", size = 274110, upload_time = "2024-11-06T20:12:29.368Z" }, ] [[package]] @@ -1394,9 +1481,9 @@ dependencies = [ { name = "idna" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 } +sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218, upload_time = "2024-05-29T15:37:49.536Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 }, + { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928, upload_time = "2024-05-29T15:37:47.027Z" }, ] [[package]] @@ -1408,9 +1495,9 @@ dependencies = [ { name = "pygments" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a1/53/830aa4c3066a8ab0ae9a9955976fb770fe9c6102117c8ec4ab3ea62d89e8/rich-14.0.0.tar.gz", hash = "sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725", size = 224078 } +sdist = { url = "https://files.pythonhosted.org/packages/a1/53/830aa4c3066a8ab0ae9a9955976fb770fe9c6102117c8ec4ab3ea62d89e8/rich-14.0.0.tar.gz", hash = "sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725", size = 224078, upload_time = "2025-03-30T14:15:14.23Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0d/9b/63f4c7ebc259242c89b3acafdb37b41d1185c07ff0011164674e9076b491/rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0", size = 243229 }, + { url = "https://files.pythonhosted.org/packages/0d/9b/63f4c7ebc259242c89b3acafdb37b41d1185c07ff0011164674e9076b491/rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0", size = 243229, upload_time = "2025-03-30T14:15:12.283Z" }, ] [[package]] @@ -1422,9 +1509,9 @@ dependencies = [ { name = "rich" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2e/ea/13945d58d556a28dfb0f774ad5c8af759527390e59505a40d164bf8ce1ce/rich_toolkit-0.14.1.tar.gz", hash = "sha256:9248e2d087bfc01f3e4c5c8987e05f7fa744d00dd22fa2be3aa6e50255790b3f", size = 104416 } +sdist = { url = "https://files.pythonhosted.org/packages/2e/ea/13945d58d556a28dfb0f774ad5c8af759527390e59505a40d164bf8ce1ce/rich_toolkit-0.14.1.tar.gz", hash = "sha256:9248e2d087bfc01f3e4c5c8987e05f7fa744d00dd22fa2be3aa6e50255790b3f", size = 104416, upload_time = "2025-03-30T12:19:08.623Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/66/e8/61c5b12d1567fdba41a6775db12a090d88b8305424ee7c47259c70d33cb4/rich_toolkit-0.14.1-py3-none-any.whl", hash = "sha256:dc92c0117d752446d04fdc828dbca5873bcded213a091a5d3742a2beec2e6559", size = 24177 }, + { url = "https://files.pythonhosted.org/packages/66/e8/61c5b12d1567fdba41a6775db12a090d88b8305424ee7c47259c70d33cb4/rich_toolkit-0.14.1-py3-none-any.whl", hash = "sha256:dc92c0117d752446d04fdc828dbca5873bcded213a091a5d3742a2beec2e6559", size = 24177, upload_time = "2025-03-30T12:19:07.307Z" }, ] [[package]] @@ -1434,34 +1521,34 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyasn1" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/aa/65/7d973b89c4d2351d7fb232c2e452547ddfa243e93131e7cfa766da627b52/rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21", size = 29711 } +sdist = { url = "https://files.pythonhosted.org/packages/aa/65/7d973b89c4d2351d7fb232c2e452547ddfa243e93131e7cfa766da627b52/rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21", size = 29711, upload_time = "2022-07-20T10:28:36.115Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/49/97/fa78e3d2f65c02c8e1268b9aba606569fe97f6c8f7c2d74394553347c145/rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7", size = 34315 }, + { url = "https://files.pythonhosted.org/packages/49/97/fa78e3d2f65c02c8e1268b9aba606569fe97f6c8f7c2d74394553347c145/rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7", size = 34315, upload_time = "2022-07-20T10:28:34.978Z" }, ] [[package]] name = "ruff" version = "0.11.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e8/5b/3ae20f89777115944e89c2d8c2e795dcc5b9e04052f76d5347e35e0da66e/ruff-0.11.4.tar.gz", hash = "sha256:f45bd2fb1a56a5a85fae3b95add03fb185a0b30cf47f5edc92aa0355ca1d7407", size = 3933063 } +sdist = { url = "https://files.pythonhosted.org/packages/e8/5b/3ae20f89777115944e89c2d8c2e795dcc5b9e04052f76d5347e35e0da66e/ruff-0.11.4.tar.gz", hash = "sha256:f45bd2fb1a56a5a85fae3b95add03fb185a0b30cf47f5edc92aa0355ca1d7407", size = 3933063, upload_time = "2025-04-04T18:24:52.197Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9c/db/baee59ac88f57527fcbaad3a7b309994e42329c6bc4d4d2b681a3d7b5426/ruff-0.11.4-py3-none-linux_armv6l.whl", hash = "sha256:d9f4a761ecbde448a2d3e12fb398647c7f0bf526dbc354a643ec505965824ed2", size = 10106493 }, - { url = "https://files.pythonhosted.org/packages/c1/d6/9a0962cbb347f4ff98b33d699bf1193ff04ca93bed4b4222fd881b502154/ruff-0.11.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:8c1747d903447d45ca3d40c794d1a56458c51e5cc1bc77b7b64bd2cf0b1626cc", size = 10876382 }, - { url = "https://files.pythonhosted.org/packages/3a/8f/62bab0c7d7e1ae3707b69b157701b41c1ccab8f83e8501734d12ea8a839f/ruff-0.11.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:51a6494209cacca79e121e9b244dc30d3414dac8cc5afb93f852173a2ecfc906", size = 10237050 }, - { url = "https://files.pythonhosted.org/packages/09/96/e296965ae9705af19c265d4d441958ed65c0c58fc4ec340c27cc9d2a1f5b/ruff-0.11.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f171605f65f4fc49c87f41b456e882cd0c89e4ac9d58e149a2b07930e1d466f", size = 10424984 }, - { url = "https://files.pythonhosted.org/packages/e5/56/644595eb57d855afed6e54b852e2df8cd5ca94c78043b2f29bdfb29882d5/ruff-0.11.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ebf99ea9af918878e6ce42098981fc8c1db3850fef2f1ada69fb1dcdb0f8e79e", size = 9957438 }, - { url = "https://files.pythonhosted.org/packages/86/83/9d3f3bed0118aef3e871ded9e5687fb8c5776bde233427fd9ce0a45db2d4/ruff-0.11.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edad2eac42279df12e176564a23fc6f4aaeeb09abba840627780b1bb11a9d223", size = 11547282 }, - { url = "https://files.pythonhosted.org/packages/40/e6/0c6e4f5ae72fac5ccb44d72c0111f294a5c2c8cc5024afcb38e6bda5f4b3/ruff-0.11.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:f103a848be9ff379fc19b5d656c1f911d0a0b4e3e0424f9532ececf319a4296e", size = 12182020 }, - { url = "https://files.pythonhosted.org/packages/b5/92/4aed0e460aeb1df5ea0c2fbe8d04f9725cccdb25d8da09a0d3f5b8764bf8/ruff-0.11.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:193e6fac6eb60cc97b9f728e953c21cc38a20077ed64f912e9d62b97487f3f2d", size = 11679154 }, - { url = "https://files.pythonhosted.org/packages/1b/d3/7316aa2609f2c592038e2543483eafbc62a0e1a6a6965178e284808c095c/ruff-0.11.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7af4e5f69b7c138be8dcffa5b4a061bf6ba6a3301f632a6bce25d45daff9bc99", size = 13905985 }, - { url = "https://files.pythonhosted.org/packages/63/80/734d3d17546e47ff99871f44ea7540ad2bbd7a480ed197fe8a1c8a261075/ruff-0.11.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:126b1bf13154aa18ae2d6c3c5efe144ec14b97c60844cfa6eb960c2a05188222", size = 11348343 }, - { url = "https://files.pythonhosted.org/packages/04/7b/70fc7f09a0161dce9613a4671d198f609e653d6f4ff9eee14d64c4c240fb/ruff-0.11.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e8806daaf9dfa881a0ed603f8a0e364e4f11b6ed461b56cae2b1c0cab0645304", size = 10308487 }, - { url = "https://files.pythonhosted.org/packages/1a/22/1cdd62dabd678d75842bf4944fd889cf794dc9e58c18cc547f9eb28f95ed/ruff-0.11.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:5d94bb1cc2fc94a769b0eb975344f1b1f3d294da1da9ddbb5a77665feb3a3019", size = 9929091 }, - { url = "https://files.pythonhosted.org/packages/9f/20/40e0563506332313148e783bbc1e4276d657962cc370657b2fff20e6e058/ruff-0.11.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:995071203d0fe2183fc7a268766fd7603afb9996785f086b0d76edee8755c896", size = 10924659 }, - { url = "https://files.pythonhosted.org/packages/b5/41/eef9b7aac8819d9e942f617f9db296f13d2c4576806d604aba8db5a753f1/ruff-0.11.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:7a37ca937e307ea18156e775a6ac6e02f34b99e8c23fe63c1996185a4efe0751", size = 11428160 }, - { url = "https://files.pythonhosted.org/packages/ff/61/c488943414fb2b8754c02f3879de003e26efdd20f38167ded3fb3fc1cda3/ruff-0.11.4-py3-none-win32.whl", hash = "sha256:0e9365a7dff9b93af933dab8aebce53b72d8f815e131796268709890b4a83270", size = 10311496 }, - { url = "https://files.pythonhosted.org/packages/b6/2b/2a1c8deb5f5dfa3871eb7daa41492c4d2b2824a74d2b38e788617612a66d/ruff-0.11.4-py3-none-win_amd64.whl", hash = "sha256:5a9fa1c69c7815e39fcfb3646bbfd7f528fa8e2d4bebdcf4c2bd0fa037a255fb", size = 11399146 }, - { url = "https://files.pythonhosted.org/packages/4f/03/3aec4846226d54a37822e4c7ea39489e4abd6f88388fba74e3d4abe77300/ruff-0.11.4-py3-none-win_arm64.whl", hash = "sha256:d435db6b9b93d02934cf61ef332e66af82da6d8c69aefdea5994c89997c7a0fc", size = 10450306 }, + { url = "https://files.pythonhosted.org/packages/9c/db/baee59ac88f57527fcbaad3a7b309994e42329c6bc4d4d2b681a3d7b5426/ruff-0.11.4-py3-none-linux_armv6l.whl", hash = "sha256:d9f4a761ecbde448a2d3e12fb398647c7f0bf526dbc354a643ec505965824ed2", size = 10106493, upload_time = "2025-04-04T18:23:56.751Z" }, + { url = "https://files.pythonhosted.org/packages/c1/d6/9a0962cbb347f4ff98b33d699bf1193ff04ca93bed4b4222fd881b502154/ruff-0.11.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:8c1747d903447d45ca3d40c794d1a56458c51e5cc1bc77b7b64bd2cf0b1626cc", size = 10876382, upload_time = "2025-04-04T18:24:02.391Z" }, + { url = "https://files.pythonhosted.org/packages/3a/8f/62bab0c7d7e1ae3707b69b157701b41c1ccab8f83e8501734d12ea8a839f/ruff-0.11.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:51a6494209cacca79e121e9b244dc30d3414dac8cc5afb93f852173a2ecfc906", size = 10237050, upload_time = "2025-04-04T18:24:05.387Z" }, + { url = "https://files.pythonhosted.org/packages/09/96/e296965ae9705af19c265d4d441958ed65c0c58fc4ec340c27cc9d2a1f5b/ruff-0.11.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f171605f65f4fc49c87f41b456e882cd0c89e4ac9d58e149a2b07930e1d466f", size = 10424984, upload_time = "2025-04-04T18:24:08.134Z" }, + { url = "https://files.pythonhosted.org/packages/e5/56/644595eb57d855afed6e54b852e2df8cd5ca94c78043b2f29bdfb29882d5/ruff-0.11.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ebf99ea9af918878e6ce42098981fc8c1db3850fef2f1ada69fb1dcdb0f8e79e", size = 9957438, upload_time = "2025-04-04T18:24:11.061Z" }, + { url = "https://files.pythonhosted.org/packages/86/83/9d3f3bed0118aef3e871ded9e5687fb8c5776bde233427fd9ce0a45db2d4/ruff-0.11.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edad2eac42279df12e176564a23fc6f4aaeeb09abba840627780b1bb11a9d223", size = 11547282, upload_time = "2025-04-04T18:24:13.739Z" }, + { url = "https://files.pythonhosted.org/packages/40/e6/0c6e4f5ae72fac5ccb44d72c0111f294a5c2c8cc5024afcb38e6bda5f4b3/ruff-0.11.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:f103a848be9ff379fc19b5d656c1f911d0a0b4e3e0424f9532ececf319a4296e", size = 12182020, upload_time = "2025-04-04T18:24:16.799Z" }, + { url = "https://files.pythonhosted.org/packages/b5/92/4aed0e460aeb1df5ea0c2fbe8d04f9725cccdb25d8da09a0d3f5b8764bf8/ruff-0.11.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:193e6fac6eb60cc97b9f728e953c21cc38a20077ed64f912e9d62b97487f3f2d", size = 11679154, upload_time = "2025-04-04T18:24:19.797Z" }, + { url = "https://files.pythonhosted.org/packages/1b/d3/7316aa2609f2c592038e2543483eafbc62a0e1a6a6965178e284808c095c/ruff-0.11.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7af4e5f69b7c138be8dcffa5b4a061bf6ba6a3301f632a6bce25d45daff9bc99", size = 13905985, upload_time = "2025-04-04T18:24:24.542Z" }, + { url = "https://files.pythonhosted.org/packages/63/80/734d3d17546e47ff99871f44ea7540ad2bbd7a480ed197fe8a1c8a261075/ruff-0.11.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:126b1bf13154aa18ae2d6c3c5efe144ec14b97c60844cfa6eb960c2a05188222", size = 11348343, upload_time = "2025-04-04T18:24:27.742Z" }, + { url = "https://files.pythonhosted.org/packages/04/7b/70fc7f09a0161dce9613a4671d198f609e653d6f4ff9eee14d64c4c240fb/ruff-0.11.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e8806daaf9dfa881a0ed603f8a0e364e4f11b6ed461b56cae2b1c0cab0645304", size = 10308487, upload_time = "2025-04-04T18:24:30.59Z" }, + { url = "https://files.pythonhosted.org/packages/1a/22/1cdd62dabd678d75842bf4944fd889cf794dc9e58c18cc547f9eb28f95ed/ruff-0.11.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:5d94bb1cc2fc94a769b0eb975344f1b1f3d294da1da9ddbb5a77665feb3a3019", size = 9929091, upload_time = "2025-04-04T18:24:33.24Z" }, + { url = "https://files.pythonhosted.org/packages/9f/20/40e0563506332313148e783bbc1e4276d657962cc370657b2fff20e6e058/ruff-0.11.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:995071203d0fe2183fc7a268766fd7603afb9996785f086b0d76edee8755c896", size = 10924659, upload_time = "2025-04-04T18:24:36.728Z" }, + { url = "https://files.pythonhosted.org/packages/b5/41/eef9b7aac8819d9e942f617f9db296f13d2c4576806d604aba8db5a753f1/ruff-0.11.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:7a37ca937e307ea18156e775a6ac6e02f34b99e8c23fe63c1996185a4efe0751", size = 11428160, upload_time = "2025-04-04T18:24:40.08Z" }, + { url = "https://files.pythonhosted.org/packages/ff/61/c488943414fb2b8754c02f3879de003e26efdd20f38167ded3fb3fc1cda3/ruff-0.11.4-py3-none-win32.whl", hash = "sha256:0e9365a7dff9b93af933dab8aebce53b72d8f815e131796268709890b4a83270", size = 10311496, upload_time = "2025-04-04T18:24:42.94Z" }, + { url = "https://files.pythonhosted.org/packages/b6/2b/2a1c8deb5f5dfa3871eb7daa41492c4d2b2824a74d2b38e788617612a66d/ruff-0.11.4-py3-none-win_amd64.whl", hash = "sha256:5a9fa1c69c7815e39fcfb3646bbfd7f528fa8e2d4bebdcf4c2bd0fa037a255fb", size = 11399146, upload_time = "2025-04-04T18:24:45.651Z" }, + { url = "https://files.pythonhosted.org/packages/4f/03/3aec4846226d54a37822e4c7ea39489e4abd6f88388fba74e3d4abe77300/ruff-0.11.4-py3-none-win_arm64.whl", hash = "sha256:d435db6b9b93d02934cf61ef332e66af82da6d8c69aefdea5994c89997c7a0fc", size = 10450306, upload_time = "2025-04-04T18:24:49.603Z" }, ] [[package]] @@ -1472,9 +1559,9 @@ dependencies = [ { name = "certifi" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/85/2f/a0f732270cc7c1834f5ec45539aec87c360d5483a8bd788217a9102ccfbd/sentry_sdk-2.25.1.tar.gz", hash = "sha256:f9041b7054a7cf12d41eadabe6458ce7c6d6eea7a97cfe1b760b6692e9562cf0", size = 322190 } +sdist = { url = "https://files.pythonhosted.org/packages/85/2f/a0f732270cc7c1834f5ec45539aec87c360d5483a8bd788217a9102ccfbd/sentry_sdk-2.25.1.tar.gz", hash = "sha256:f9041b7054a7cf12d41eadabe6458ce7c6d6eea7a97cfe1b760b6692e9562cf0", size = 322190, upload_time = "2025-04-02T15:13:10.225Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/96/b6/84049ab0967affbc7cc7590d86ae0170c1b494edb69df8786707100420e5/sentry_sdk-2.25.1-py2.py3-none-any.whl", hash = "sha256:60b016d0772789454dc55a284a6a44212044d4a16d9f8448725effee97aaf7f6", size = 339851 }, + { url = "https://files.pythonhosted.org/packages/96/b6/84049ab0967affbc7cc7590d86ae0170c1b494edb69df8786707100420e5/sentry_sdk-2.25.1-py2.py3-none-any.whl", hash = "sha256:60b016d0772789454dc55a284a6a44212044d4a16d9f8448725effee97aaf7f6", size = 339851, upload_time = "2025-04-02T15:13:08.616Z" }, ] [package.optional-dependencies] @@ -1492,18 +1579,18 @@ sqlalchemy = [ name = "shellingham" version = "1.5.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310 } +sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload_time = "2023-10-24T04:13:40.426Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755 }, + { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload_time = "2023-10-24T04:13:38.866Z" }, ] [[package]] name = "sniffio" version = "1.3.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload_time = "2024-02-25T23:20:04.057Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 }, + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload_time = "2024-02-25T23:20:01.196Z" }, ] [[package]] @@ -1514,49 +1601,49 @@ dependencies = [ { name = "greenlet", marker = "(python_full_version < '3.14' and platform_machine == 'AMD64') or (python_full_version < '3.14' and platform_machine == 'WIN32') or (python_full_version < '3.14' and platform_machine == 'aarch64') or (python_full_version < '3.14' and platform_machine == 'amd64') or (python_full_version < '3.14' and platform_machine == 'ppc64le') or (python_full_version < '3.14' and platform_machine == 'win32') or (python_full_version < '3.14' and platform_machine == 'x86_64')" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/68/c3/3f2bfa5e4dcd9938405fe2fab5b6ab94a9248a4f9536ea2fd497da20525f/sqlalchemy-2.0.40.tar.gz", hash = "sha256:d827099289c64589418ebbcaead0145cd19f4e3e8a93919a0100247af245fa00", size = 9664299 } +sdist = { url = "https://files.pythonhosted.org/packages/68/c3/3f2bfa5e4dcd9938405fe2fab5b6ab94a9248a4f9536ea2fd497da20525f/sqlalchemy-2.0.40.tar.gz", hash = "sha256:d827099289c64589418ebbcaead0145cd19f4e3e8a93919a0100247af245fa00", size = 9664299, upload_time = "2025-03-27T17:52:31.876Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/87/fa/8e8fd93684b04e65816be864bebf0000fe1602e5452d006f9acc5db14ce5/sqlalchemy-2.0.40-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f1ea21bef99c703f44444ad29c2c1b6bd55d202750b6de8e06a955380f4725d7", size = 2112843 }, - { url = "https://files.pythonhosted.org/packages/ba/87/06992f78a9ce545dfd1fea3dd99262bec5221f6f9d2d2066c3e94662529f/sqlalchemy-2.0.40-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:afe63b208153f3a7a2d1a5b9df452b0673082588933e54e7c8aac457cf35e758", size = 2104032 }, - { url = "https://files.pythonhosted.org/packages/92/ee/57dc77282e8be22d686bd4681825299aa1069bbe090564868ea270ed5214/sqlalchemy-2.0.40-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a8aae085ea549a1eddbc9298b113cffb75e514eadbb542133dd2b99b5fb3b6af", size = 3086406 }, - { url = "https://files.pythonhosted.org/packages/94/3f/ceb9ab214b2e42d2e74a9209b3a2f2f073504eee16cddd2df81feeb67c2f/sqlalchemy-2.0.40-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ea9181284754d37db15156eb7be09c86e16e50fbe77610e9e7bee09291771a1", size = 3094652 }, - { url = "https://files.pythonhosted.org/packages/00/0a/3401232a5b6d91a2df16c1dc39c6504c54575744c2faafa1e5a50de96621/sqlalchemy-2.0.40-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5434223b795be5c5ef8244e5ac98056e290d3a99bdcc539b916e282b160dda00", size = 3050503 }, - { url = "https://files.pythonhosted.org/packages/93/c2/ea7171415ab131397f71a2673645c2fe29ebe9a93063d458eb89e42bf051/sqlalchemy-2.0.40-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:15d08d5ef1b779af6a0909b97be6c1fd4298057504eb6461be88bd1696cb438e", size = 3076011 }, - { url = "https://files.pythonhosted.org/packages/3d/ee/d8e229280d621bed8c51eebf1dd413aa09ca89e309b1fff40d881dd149af/sqlalchemy-2.0.40-cp310-cp310-win32.whl", hash = "sha256:cd2f75598ae70bcfca9117d9e51a3b06fe29edd972fdd7fd57cc97b4dbf3b08a", size = 2085136 }, - { url = "https://files.pythonhosted.org/packages/60/7f/ea1086136bc648cd4713a1e01869f7fc31979d67b3a8f973f5d9ab8de7e1/sqlalchemy-2.0.40-cp310-cp310-win_amd64.whl", hash = "sha256:2cbafc8d39ff1abdfdda96435f38fab141892dc759a2165947d1a8fffa7ef596", size = 2109421 }, - { url = "https://files.pythonhosted.org/packages/77/7e/55044a9ec48c3249bb38d5faae93f09579c35e862bb318ebd1ed7a1994a5/sqlalchemy-2.0.40-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f6bacab7514de6146a1976bc56e1545bee247242fab030b89e5f70336fc0003e", size = 2114025 }, - { url = "https://files.pythonhosted.org/packages/77/0f/dcf7bba95f847aec72f638750747b12d37914f71c8cc7c133cf326ab945c/sqlalchemy-2.0.40-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5654d1ac34e922b6c5711631f2da497d3a7bffd6f9f87ac23b35feea56098011", size = 2104419 }, - { url = "https://files.pythonhosted.org/packages/75/70/c86a5c20715e4fe903dde4c2fd44fc7e7a0d5fb52c1b954d98526f65a3ea/sqlalchemy-2.0.40-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35904d63412db21088739510216e9349e335f142ce4a04b69e2528020ee19ed4", size = 3222720 }, - { url = "https://files.pythonhosted.org/packages/12/cf/b891a8c1d0c27ce9163361664c2128c7a57de3f35000ea5202eb3a2917b7/sqlalchemy-2.0.40-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c7a80ed86d6aaacb8160a1caef6680d4ddd03c944d985aecee940d168c411d1", size = 3222682 }, - { url = "https://files.pythonhosted.org/packages/15/3f/7709d8c8266953d945435a96b7f425ae4172a336963756b58e996fbef7f3/sqlalchemy-2.0.40-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:519624685a51525ddaa7d8ba8265a1540442a2ec71476f0e75241eb8263d6f51", size = 3159542 }, - { url = "https://files.pythonhosted.org/packages/85/7e/717eaabaf0f80a0132dc2032ea8f745b7a0914451c984821a7c8737fb75a/sqlalchemy-2.0.40-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2ee5f9999a5b0e9689bed96e60ee53c3384f1a05c2dd8068cc2e8361b0df5b7a", size = 3179864 }, - { url = "https://files.pythonhosted.org/packages/e4/cc/03eb5dfcdb575cbecd2bd82487b9848f250a4b6ecfb4707e834b4ce4ec07/sqlalchemy-2.0.40-cp311-cp311-win32.whl", hash = "sha256:c0cae71e20e3c02c52f6b9e9722bca70e4a90a466d59477822739dc31ac18b4b", size = 2084675 }, - { url = "https://files.pythonhosted.org/packages/9a/48/440946bf9dc4dc231f4f31ef0d316f7135bf41d4b86aaba0c0655150d370/sqlalchemy-2.0.40-cp311-cp311-win_amd64.whl", hash = "sha256:574aea2c54d8f1dd1699449f332c7d9b71c339e04ae50163a3eb5ce4c4325ee4", size = 2110099 }, - { url = "https://files.pythonhosted.org/packages/92/06/552c1f92e880b57d8b92ce6619bd569b25cead492389b1d84904b55989d8/sqlalchemy-2.0.40-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9d3b31d0a1c44b74d3ae27a3de422dfccd2b8f0b75e51ecb2faa2bf65ab1ba0d", size = 2112620 }, - { url = "https://files.pythonhosted.org/packages/01/72/a5bc6e76c34cebc071f758161dbe1453de8815ae6e662393910d3be6d70d/sqlalchemy-2.0.40-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:37f7a0f506cf78c80450ed1e816978643d3969f99c4ac6b01104a6fe95c5490a", size = 2103004 }, - { url = "https://files.pythonhosted.org/packages/bf/fd/0e96c8e6767618ed1a06e4d7a167fe13734c2f8113c4cb704443e6783038/sqlalchemy-2.0.40-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bb933a650323e476a2e4fbef8997a10d0003d4da996aad3fd7873e962fdde4d", size = 3252440 }, - { url = "https://files.pythonhosted.org/packages/cd/6a/eb82e45b15a64266a2917a6833b51a334ea3c1991728fd905bfccbf5cf63/sqlalchemy-2.0.40-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6959738971b4745eea16f818a2cd086fb35081383b078272c35ece2b07012716", size = 3263277 }, - { url = "https://files.pythonhosted.org/packages/45/97/ebe41ab4530f50af99e3995ebd4e0204bf1b0dc0930f32250dde19c389fe/sqlalchemy-2.0.40-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:110179728e442dae85dd39591beb74072ae4ad55a44eda2acc6ec98ead80d5f2", size = 3198591 }, - { url = "https://files.pythonhosted.org/packages/e6/1c/a569c1b2b2f5ac20ba6846a1321a2bf52e9a4061001f282bf1c5528dcd69/sqlalchemy-2.0.40-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e8040680eaacdce4d635f12c55c714f3d4c7f57da2bc47a01229d115bd319191", size = 3225199 }, - { url = "https://files.pythonhosted.org/packages/8f/91/87cc71a6b10065ca0209d19a4bb575378abda6085e72fa0b61ffb2201b84/sqlalchemy-2.0.40-cp312-cp312-win32.whl", hash = "sha256:650490653b110905c10adac69408380688cefc1f536a137d0d69aca1069dc1d1", size = 2082959 }, - { url = "https://files.pythonhosted.org/packages/2a/9f/14c511cda174aa1ad9b0e42b64ff5a71db35d08b0d80dc044dae958921e5/sqlalchemy-2.0.40-cp312-cp312-win_amd64.whl", hash = "sha256:2be94d75ee06548d2fc591a3513422b873490efb124048f50556369a834853b0", size = 2108526 }, - { url = "https://files.pythonhosted.org/packages/8c/18/4e3a86cc0232377bc48c373a9ba6a1b3fb79ba32dbb4eda0b357f5a2c59d/sqlalchemy-2.0.40-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:915866fd50dd868fdcc18d61d8258db1bf9ed7fbd6dfec960ba43365952f3b01", size = 2107887 }, - { url = "https://files.pythonhosted.org/packages/cb/60/9fa692b1d2ffc4cbd5f47753731fd332afed30137115d862d6e9a1e962c7/sqlalchemy-2.0.40-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4a4c5a2905a9ccdc67a8963e24abd2f7afcd4348829412483695c59e0af9a705", size = 2098367 }, - { url = "https://files.pythonhosted.org/packages/4c/9f/84b78357ca641714a439eb3fbbddb17297dacfa05d951dbf24f28d7b5c08/sqlalchemy-2.0.40-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55028d7a3ebdf7ace492fab9895cbc5270153f75442a0472d8516e03159ab364", size = 3184806 }, - { url = "https://files.pythonhosted.org/packages/4b/7d/e06164161b6bfce04c01bfa01518a20cccbd4100d5c951e5a7422189191a/sqlalchemy-2.0.40-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6cfedff6878b0e0d1d0a50666a817ecd85051d12d56b43d9d425455e608b5ba0", size = 3198131 }, - { url = "https://files.pythonhosted.org/packages/6d/51/354af20da42d7ec7b5c9de99edafbb7663a1d75686d1999ceb2c15811302/sqlalchemy-2.0.40-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bb19e30fdae77d357ce92192a3504579abe48a66877f476880238a962e5b96db", size = 3131364 }, - { url = "https://files.pythonhosted.org/packages/7a/2f/48a41ff4e6e10549d83fcc551ab85c268bde7c03cf77afb36303c6594d11/sqlalchemy-2.0.40-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:16d325ea898f74b26ffcd1cf8c593b0beed8714f0317df2bed0d8d1de05a8f26", size = 3159482 }, - { url = "https://files.pythonhosted.org/packages/33/ac/e5e0a807163652a35be878c0ad5cfd8b1d29605edcadfb5df3c512cdf9f3/sqlalchemy-2.0.40-cp313-cp313-win32.whl", hash = "sha256:a669cbe5be3c63f75bcbee0b266779706f1a54bcb1000f302685b87d1b8c1500", size = 2080704 }, - { url = "https://files.pythonhosted.org/packages/1c/cb/f38c61f7f2fd4d10494c1c135ff6a6ddb63508d0b47bccccd93670637309/sqlalchemy-2.0.40-cp313-cp313-win_amd64.whl", hash = "sha256:641ee2e0834812d657862f3a7de95e0048bdcb6c55496f39c6fa3d435f6ac6ad", size = 2104564 }, - { url = "https://files.pythonhosted.org/packages/d1/8d/fb1f43d001ed9f8e48e4fb231199fde7f182741efd315d9aef241c3c2292/sqlalchemy-2.0.40-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c884de19528e0fcd9dc34ee94c810581dd6e74aef75437ff17e696c2bfefae3e", size = 2115715 }, - { url = "https://files.pythonhosted.org/packages/16/a6/a25d35a13368424b7623a37a3943620e9c3c1670aab4fd039cdaf84deb79/sqlalchemy-2.0.40-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1abb387710283fc5983d8a1209d9696a4eae9db8d7ac94b402981fe2fe2e39ad", size = 2106945 }, - { url = "https://files.pythonhosted.org/packages/f2/91/171e9f94e66419bf9ec94cb1a52346b023c227ca9b6c4b4d767b252ac7b2/sqlalchemy-2.0.40-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cfa124eda500ba4b0d3afc3e91ea27ed4754e727c7f025f293a22f512bcd4c9", size = 3100866 }, - { url = "https://files.pythonhosted.org/packages/fa/56/a3fc75088c9f57a405bb890b8e00686a394bd0419e68758fbffd14649a3e/sqlalchemy-2.0.40-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b6b28d303b9d57c17a5164eb1fd2d5119bb6ff4413d5894e74873280483eeb5", size = 3108645 }, - { url = "https://files.pythonhosted.org/packages/40/18/fb198acaa8041dd5b61a521678bcef80c2d1fa90c8eaebe35004f12a3fba/sqlalchemy-2.0.40-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:b5a5bbe29c10c5bfd63893747a1bf6f8049df607638c786252cb9243b86b6706", size = 3067694 }, - { url = "https://files.pythonhosted.org/packages/aa/39/832b5fe338c98b8c0d6c987128e341ac74ce2e5298e9e019433b37cb6b19/sqlalchemy-2.0.40-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:f0fda83e113bb0fb27dc003685f32a5dcb99c9c4f41f4fa0838ac35265c23b5c", size = 3094193 }, - { url = "https://files.pythonhosted.org/packages/3e/57/b3684de3e179e6429d71f31efb55183b274f3ffc1bee8cfda138b2b34927/sqlalchemy-2.0.40-cp39-cp39-win32.whl", hash = "sha256:957f8d85d5e834397ef78a6109550aeb0d27a53b5032f7a57f2451e1adc37e98", size = 2087537 }, - { url = "https://files.pythonhosted.org/packages/05/dc/6af9d62239c1115c95a53477092bc4578f0f809962da1680ad75976a8672/sqlalchemy-2.0.40-cp39-cp39-win_amd64.whl", hash = "sha256:1ffdf9c91428e59744f8e6f98190516f8e1d05eec90e936eb08b257332c5e870", size = 2111906 }, - { url = "https://files.pythonhosted.org/packages/d1/7c/5fc8e802e7506fe8b55a03a2e1dab156eae205c91bee46305755e086d2e2/sqlalchemy-2.0.40-py3-none-any.whl", hash = "sha256:32587e2e1e359276957e6fe5dad089758bc042a971a8a09ae8ecf7a8fe23d07a", size = 1903894 }, + { url = "https://files.pythonhosted.org/packages/87/fa/8e8fd93684b04e65816be864bebf0000fe1602e5452d006f9acc5db14ce5/sqlalchemy-2.0.40-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f1ea21bef99c703f44444ad29c2c1b6bd55d202750b6de8e06a955380f4725d7", size = 2112843, upload_time = "2025-03-27T18:49:25.515Z" }, + { url = "https://files.pythonhosted.org/packages/ba/87/06992f78a9ce545dfd1fea3dd99262bec5221f6f9d2d2066c3e94662529f/sqlalchemy-2.0.40-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:afe63b208153f3a7a2d1a5b9df452b0673082588933e54e7c8aac457cf35e758", size = 2104032, upload_time = "2025-03-27T18:49:28.098Z" }, + { url = "https://files.pythonhosted.org/packages/92/ee/57dc77282e8be22d686bd4681825299aa1069bbe090564868ea270ed5214/sqlalchemy-2.0.40-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a8aae085ea549a1eddbc9298b113cffb75e514eadbb542133dd2b99b5fb3b6af", size = 3086406, upload_time = "2025-03-27T18:44:25.302Z" }, + { url = "https://files.pythonhosted.org/packages/94/3f/ceb9ab214b2e42d2e74a9209b3a2f2f073504eee16cddd2df81feeb67c2f/sqlalchemy-2.0.40-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ea9181284754d37db15156eb7be09c86e16e50fbe77610e9e7bee09291771a1", size = 3094652, upload_time = "2025-03-27T18:55:16.174Z" }, + { url = "https://files.pythonhosted.org/packages/00/0a/3401232a5b6d91a2df16c1dc39c6504c54575744c2faafa1e5a50de96621/sqlalchemy-2.0.40-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5434223b795be5c5ef8244e5ac98056e290d3a99bdcc539b916e282b160dda00", size = 3050503, upload_time = "2025-03-27T18:44:28.266Z" }, + { url = "https://files.pythonhosted.org/packages/93/c2/ea7171415ab131397f71a2673645c2fe29ebe9a93063d458eb89e42bf051/sqlalchemy-2.0.40-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:15d08d5ef1b779af6a0909b97be6c1fd4298057504eb6461be88bd1696cb438e", size = 3076011, upload_time = "2025-03-27T18:55:17.967Z" }, + { url = "https://files.pythonhosted.org/packages/3d/ee/d8e229280d621bed8c51eebf1dd413aa09ca89e309b1fff40d881dd149af/sqlalchemy-2.0.40-cp310-cp310-win32.whl", hash = "sha256:cd2f75598ae70bcfca9117d9e51a3b06fe29edd972fdd7fd57cc97b4dbf3b08a", size = 2085136, upload_time = "2025-03-27T18:48:53.032Z" }, + { url = "https://files.pythonhosted.org/packages/60/7f/ea1086136bc648cd4713a1e01869f7fc31979d67b3a8f973f5d9ab8de7e1/sqlalchemy-2.0.40-cp310-cp310-win_amd64.whl", hash = "sha256:2cbafc8d39ff1abdfdda96435f38fab141892dc759a2165947d1a8fffa7ef596", size = 2109421, upload_time = "2025-03-27T18:48:54.258Z" }, + { url = "https://files.pythonhosted.org/packages/77/7e/55044a9ec48c3249bb38d5faae93f09579c35e862bb318ebd1ed7a1994a5/sqlalchemy-2.0.40-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f6bacab7514de6146a1976bc56e1545bee247242fab030b89e5f70336fc0003e", size = 2114025, upload_time = "2025-03-27T18:49:29.456Z" }, + { url = "https://files.pythonhosted.org/packages/77/0f/dcf7bba95f847aec72f638750747b12d37914f71c8cc7c133cf326ab945c/sqlalchemy-2.0.40-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5654d1ac34e922b6c5711631f2da497d3a7bffd6f9f87ac23b35feea56098011", size = 2104419, upload_time = "2025-03-27T18:49:30.75Z" }, + { url = "https://files.pythonhosted.org/packages/75/70/c86a5c20715e4fe903dde4c2fd44fc7e7a0d5fb52c1b954d98526f65a3ea/sqlalchemy-2.0.40-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35904d63412db21088739510216e9349e335f142ce4a04b69e2528020ee19ed4", size = 3222720, upload_time = "2025-03-27T18:44:29.871Z" }, + { url = "https://files.pythonhosted.org/packages/12/cf/b891a8c1d0c27ce9163361664c2128c7a57de3f35000ea5202eb3a2917b7/sqlalchemy-2.0.40-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c7a80ed86d6aaacb8160a1caef6680d4ddd03c944d985aecee940d168c411d1", size = 3222682, upload_time = "2025-03-27T18:55:20.097Z" }, + { url = "https://files.pythonhosted.org/packages/15/3f/7709d8c8266953d945435a96b7f425ae4172a336963756b58e996fbef7f3/sqlalchemy-2.0.40-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:519624685a51525ddaa7d8ba8265a1540442a2ec71476f0e75241eb8263d6f51", size = 3159542, upload_time = "2025-03-27T18:44:31.333Z" }, + { url = "https://files.pythonhosted.org/packages/85/7e/717eaabaf0f80a0132dc2032ea8f745b7a0914451c984821a7c8737fb75a/sqlalchemy-2.0.40-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2ee5f9999a5b0e9689bed96e60ee53c3384f1a05c2dd8068cc2e8361b0df5b7a", size = 3179864, upload_time = "2025-03-27T18:55:21.784Z" }, + { url = "https://files.pythonhosted.org/packages/e4/cc/03eb5dfcdb575cbecd2bd82487b9848f250a4b6ecfb4707e834b4ce4ec07/sqlalchemy-2.0.40-cp311-cp311-win32.whl", hash = "sha256:c0cae71e20e3c02c52f6b9e9722bca70e4a90a466d59477822739dc31ac18b4b", size = 2084675, upload_time = "2025-03-27T18:48:55.915Z" }, + { url = "https://files.pythonhosted.org/packages/9a/48/440946bf9dc4dc231f4f31ef0d316f7135bf41d4b86aaba0c0655150d370/sqlalchemy-2.0.40-cp311-cp311-win_amd64.whl", hash = "sha256:574aea2c54d8f1dd1699449f332c7d9b71c339e04ae50163a3eb5ce4c4325ee4", size = 2110099, upload_time = "2025-03-27T18:48:57.45Z" }, + { url = "https://files.pythonhosted.org/packages/92/06/552c1f92e880b57d8b92ce6619bd569b25cead492389b1d84904b55989d8/sqlalchemy-2.0.40-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9d3b31d0a1c44b74d3ae27a3de422dfccd2b8f0b75e51ecb2faa2bf65ab1ba0d", size = 2112620, upload_time = "2025-03-27T18:40:00.071Z" }, + { url = "https://files.pythonhosted.org/packages/01/72/a5bc6e76c34cebc071f758161dbe1453de8815ae6e662393910d3be6d70d/sqlalchemy-2.0.40-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:37f7a0f506cf78c80450ed1e816978643d3969f99c4ac6b01104a6fe95c5490a", size = 2103004, upload_time = "2025-03-27T18:40:04.204Z" }, + { url = "https://files.pythonhosted.org/packages/bf/fd/0e96c8e6767618ed1a06e4d7a167fe13734c2f8113c4cb704443e6783038/sqlalchemy-2.0.40-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bb933a650323e476a2e4fbef8997a10d0003d4da996aad3fd7873e962fdde4d", size = 3252440, upload_time = "2025-03-27T18:51:25.624Z" }, + { url = "https://files.pythonhosted.org/packages/cd/6a/eb82e45b15a64266a2917a6833b51a334ea3c1991728fd905bfccbf5cf63/sqlalchemy-2.0.40-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6959738971b4745eea16f818a2cd086fb35081383b078272c35ece2b07012716", size = 3263277, upload_time = "2025-03-27T18:50:28.142Z" }, + { url = "https://files.pythonhosted.org/packages/45/97/ebe41ab4530f50af99e3995ebd4e0204bf1b0dc0930f32250dde19c389fe/sqlalchemy-2.0.40-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:110179728e442dae85dd39591beb74072ae4ad55a44eda2acc6ec98ead80d5f2", size = 3198591, upload_time = "2025-03-27T18:51:27.543Z" }, + { url = "https://files.pythonhosted.org/packages/e6/1c/a569c1b2b2f5ac20ba6846a1321a2bf52e9a4061001f282bf1c5528dcd69/sqlalchemy-2.0.40-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e8040680eaacdce4d635f12c55c714f3d4c7f57da2bc47a01229d115bd319191", size = 3225199, upload_time = "2025-03-27T18:50:30.069Z" }, + { url = "https://files.pythonhosted.org/packages/8f/91/87cc71a6b10065ca0209d19a4bb575378abda6085e72fa0b61ffb2201b84/sqlalchemy-2.0.40-cp312-cp312-win32.whl", hash = "sha256:650490653b110905c10adac69408380688cefc1f536a137d0d69aca1069dc1d1", size = 2082959, upload_time = "2025-03-27T18:45:57.574Z" }, + { url = "https://files.pythonhosted.org/packages/2a/9f/14c511cda174aa1ad9b0e42b64ff5a71db35d08b0d80dc044dae958921e5/sqlalchemy-2.0.40-cp312-cp312-win_amd64.whl", hash = "sha256:2be94d75ee06548d2fc591a3513422b873490efb124048f50556369a834853b0", size = 2108526, upload_time = "2025-03-27T18:45:58.965Z" }, + { url = "https://files.pythonhosted.org/packages/8c/18/4e3a86cc0232377bc48c373a9ba6a1b3fb79ba32dbb4eda0b357f5a2c59d/sqlalchemy-2.0.40-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:915866fd50dd868fdcc18d61d8258db1bf9ed7fbd6dfec960ba43365952f3b01", size = 2107887, upload_time = "2025-03-27T18:40:05.461Z" }, + { url = "https://files.pythonhosted.org/packages/cb/60/9fa692b1d2ffc4cbd5f47753731fd332afed30137115d862d6e9a1e962c7/sqlalchemy-2.0.40-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4a4c5a2905a9ccdc67a8963e24abd2f7afcd4348829412483695c59e0af9a705", size = 2098367, upload_time = "2025-03-27T18:40:07.182Z" }, + { url = "https://files.pythonhosted.org/packages/4c/9f/84b78357ca641714a439eb3fbbddb17297dacfa05d951dbf24f28d7b5c08/sqlalchemy-2.0.40-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55028d7a3ebdf7ace492fab9895cbc5270153f75442a0472d8516e03159ab364", size = 3184806, upload_time = "2025-03-27T18:51:29.356Z" }, + { url = "https://files.pythonhosted.org/packages/4b/7d/e06164161b6bfce04c01bfa01518a20cccbd4100d5c951e5a7422189191a/sqlalchemy-2.0.40-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6cfedff6878b0e0d1d0a50666a817ecd85051d12d56b43d9d425455e608b5ba0", size = 3198131, upload_time = "2025-03-27T18:50:31.616Z" }, + { url = "https://files.pythonhosted.org/packages/6d/51/354af20da42d7ec7b5c9de99edafbb7663a1d75686d1999ceb2c15811302/sqlalchemy-2.0.40-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bb19e30fdae77d357ce92192a3504579abe48a66877f476880238a962e5b96db", size = 3131364, upload_time = "2025-03-27T18:51:31.336Z" }, + { url = "https://files.pythonhosted.org/packages/7a/2f/48a41ff4e6e10549d83fcc551ab85c268bde7c03cf77afb36303c6594d11/sqlalchemy-2.0.40-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:16d325ea898f74b26ffcd1cf8c593b0beed8714f0317df2bed0d8d1de05a8f26", size = 3159482, upload_time = "2025-03-27T18:50:33.201Z" }, + { url = "https://files.pythonhosted.org/packages/33/ac/e5e0a807163652a35be878c0ad5cfd8b1d29605edcadfb5df3c512cdf9f3/sqlalchemy-2.0.40-cp313-cp313-win32.whl", hash = "sha256:a669cbe5be3c63f75bcbee0b266779706f1a54bcb1000f302685b87d1b8c1500", size = 2080704, upload_time = "2025-03-27T18:46:00.193Z" }, + { url = "https://files.pythonhosted.org/packages/1c/cb/f38c61f7f2fd4d10494c1c135ff6a6ddb63508d0b47bccccd93670637309/sqlalchemy-2.0.40-cp313-cp313-win_amd64.whl", hash = "sha256:641ee2e0834812d657862f3a7de95e0048bdcb6c55496f39c6fa3d435f6ac6ad", size = 2104564, upload_time = "2025-03-27T18:46:01.442Z" }, + { url = "https://files.pythonhosted.org/packages/d1/8d/fb1f43d001ed9f8e48e4fb231199fde7f182741efd315d9aef241c3c2292/sqlalchemy-2.0.40-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c884de19528e0fcd9dc34ee94c810581dd6e74aef75437ff17e696c2bfefae3e", size = 2115715, upload_time = "2025-03-27T18:49:23.956Z" }, + { url = "https://files.pythonhosted.org/packages/16/a6/a25d35a13368424b7623a37a3943620e9c3c1670aab4fd039cdaf84deb79/sqlalchemy-2.0.40-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1abb387710283fc5983d8a1209d9696a4eae9db8d7ac94b402981fe2fe2e39ad", size = 2106945, upload_time = "2025-03-27T18:49:25.376Z" }, + { url = "https://files.pythonhosted.org/packages/f2/91/171e9f94e66419bf9ec94cb1a52346b023c227ca9b6c4b4d767b252ac7b2/sqlalchemy-2.0.40-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cfa124eda500ba4b0d3afc3e91ea27ed4754e727c7f025f293a22f512bcd4c9", size = 3100866, upload_time = "2025-03-27T18:10:48.796Z" }, + { url = "https://files.pythonhosted.org/packages/fa/56/a3fc75088c9f57a405bb890b8e00686a394bd0419e68758fbffd14649a3e/sqlalchemy-2.0.40-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b6b28d303b9d57c17a5164eb1fd2d5119bb6ff4413d5894e74873280483eeb5", size = 3108645, upload_time = "2025-03-27T18:55:40.936Z" }, + { url = "https://files.pythonhosted.org/packages/40/18/fb198acaa8041dd5b61a521678bcef80c2d1fa90c8eaebe35004f12a3fba/sqlalchemy-2.0.40-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:b5a5bbe29c10c5bfd63893747a1bf6f8049df607638c786252cb9243b86b6706", size = 3067694, upload_time = "2025-03-27T18:10:50.135Z" }, + { url = "https://files.pythonhosted.org/packages/aa/39/832b5fe338c98b8c0d6c987128e341ac74ce2e5298e9e019433b37cb6b19/sqlalchemy-2.0.40-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:f0fda83e113bb0fb27dc003685f32a5dcb99c9c4f41f4fa0838ac35265c23b5c", size = 3094193, upload_time = "2025-03-27T18:55:42.682Z" }, + { url = "https://files.pythonhosted.org/packages/3e/57/b3684de3e179e6429d71f31efb55183b274f3ffc1bee8cfda138b2b34927/sqlalchemy-2.0.40-cp39-cp39-win32.whl", hash = "sha256:957f8d85d5e834397ef78a6109550aeb0d27a53b5032f7a57f2451e1adc37e98", size = 2087537, upload_time = "2025-03-27T18:53:32.186Z" }, + { url = "https://files.pythonhosted.org/packages/05/dc/6af9d62239c1115c95a53477092bc4578f0f809962da1680ad75976a8672/sqlalchemy-2.0.40-cp39-cp39-win_amd64.whl", hash = "sha256:1ffdf9c91428e59744f8e6f98190516f8e1d05eec90e936eb08b257332c5e870", size = 2111906, upload_time = "2025-03-27T18:53:33.647Z" }, + { url = "https://files.pythonhosted.org/packages/d1/7c/5fc8e802e7506fe8b55a03a2e1dab156eae205c91bee46305755e086d2e2/sqlalchemy-2.0.40-py3-none-any.whl", hash = "sha256:32587e2e1e359276957e6fe5dad089758bc042a971a8a09ae8ecf7a8fe23d07a", size = 1903894, upload_time = "2025-03-27T18:40:43.796Z" }, ] [[package]] @@ -1566,9 +1653,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "sqlalchemy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4d/bf/abfd5474cdd89ddd36dbbde9c6efba16bfa7f5448913eba946fed14729da/SQLAlchemy-Utils-0.41.2.tar.gz", hash = "sha256:bc599c8c3b3319e53ce6c5c3c471120bd325d0071fb6f38a10e924e3d07b9990", size = 138017 } +sdist = { url = "https://files.pythonhosted.org/packages/4d/bf/abfd5474cdd89ddd36dbbde9c6efba16bfa7f5448913eba946fed14729da/SQLAlchemy-Utils-0.41.2.tar.gz", hash = "sha256:bc599c8c3b3319e53ce6c5c3c471120bd325d0071fb6f38a10e924e3d07b9990", size = 138017, upload_time = "2024-03-24T15:17:28.196Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d5/f0/dc4757b83ac1ab853cf222df8535ed73973e0c203d983982ba7b8bc60508/SQLAlchemy_Utils-0.41.2-py3-none-any.whl", hash = "sha256:85cf3842da2bf060760f955f8467b87983fb2e30f1764fd0e24a48307dc8ec6e", size = 93083 }, + { url = "https://files.pythonhosted.org/packages/d5/f0/dc4757b83ac1ab853cf222df8535ed73973e0c203d983982ba7b8bc60508/SQLAlchemy_Utils-0.41.2-py3-none-any.whl", hash = "sha256:85cf3842da2bf060760f955f8467b87983fb2e30f1764fd0e24a48307dc8ec6e", size = 93083, upload_time = "2024-03-24T15:17:24.533Z" }, ] [[package]] @@ -1579,57 +1666,99 @@ dependencies = [ { name = "anyio" }, { name = "typing-extensions", marker = "python_full_version < '3.10'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/04/1b/52b27f2e13ceedc79a908e29eac426a63465a1a01248e5f24aa36a62aeb3/starlette-0.46.1.tar.gz", hash = "sha256:3c88d58ee4bd1bb807c0d1acb381838afc7752f9ddaec81bbe4383611d833230", size = 2580102 } +sdist = { url = "https://files.pythonhosted.org/packages/04/1b/52b27f2e13ceedc79a908e29eac426a63465a1a01248e5f24aa36a62aeb3/starlette-0.46.1.tar.gz", hash = "sha256:3c88d58ee4bd1bb807c0d1acb381838afc7752f9ddaec81bbe4383611d833230", size = 2580102, upload_time = "2025-03-08T10:55:34.504Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/4b/528ccf7a982216885a1ff4908e886b8fb5f19862d1962f56a3fce2435a70/starlette-0.46.1-py3-none-any.whl", hash = "sha256:77c74ed9d2720138b25875133f3a2dae6d854af2ec37dceb56aef370c1d8a227", size = 71995 }, + { url = "https://files.pythonhosted.org/packages/a0/4b/528ccf7a982216885a1ff4908e886b8fb5f19862d1962f56a3fce2435a70/starlette-0.46.1-py3-none-any.whl", hash = "sha256:77c74ed9d2720138b25875133f3a2dae6d854af2ec37dceb56aef370c1d8a227", size = 71995, upload_time = "2025-03-08T10:55:32.662Z" }, ] [[package]] name = "tabulate" version = "0.9.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ec/fe/802052aecb21e3797b8f7902564ab6ea0d60ff8ca23952079064155d1ae1/tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c", size = 81090 } +sdist = { url = "https://files.pythonhosted.org/packages/ec/fe/802052aecb21e3797b8f7902564ab6ea0d60ff8ca23952079064155d1ae1/tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c", size = 81090, upload_time = "2022-10-06T17:21:48.54Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f", size = 35252 }, + { url = "https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f", size = 35252, upload_time = "2022-10-06T17:21:44.262Z" }, +] + +[[package]] +name = "tiktoken" +version = "0.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "regex" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ea/cf/756fedf6981e82897f2d570dd25fa597eb3f4459068ae0572d7e888cfd6f/tiktoken-0.9.0.tar.gz", hash = "sha256:d02a5ca6a938e0490e1ff957bc48c8b078c88cb83977be1625b1fd8aac792c5d", size = 35991, upload_time = "2025-02-14T06:03:01.003Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/f3/50ec5709fad61641e4411eb1b9ac55b99801d71f1993c29853f256c726c9/tiktoken-0.9.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:586c16358138b96ea804c034b8acf3f5d3f0258bd2bc3b0227af4af5d622e382", size = 1065770, upload_time = "2025-02-14T06:02:01.251Z" }, + { url = "https://files.pythonhosted.org/packages/d6/f8/5a9560a422cf1755b6e0a9a436e14090eeb878d8ec0f80e0cd3d45b78bf4/tiktoken-0.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d9c59ccc528c6c5dd51820b3474402f69d9a9e1d656226848ad68a8d5b2e5108", size = 1009314, upload_time = "2025-02-14T06:02:02.869Z" }, + { url = "https://files.pythonhosted.org/packages/bc/20/3ed4cfff8f809cb902900ae686069e029db74567ee10d017cb254df1d598/tiktoken-0.9.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f0968d5beeafbca2a72c595e8385a1a1f8af58feaebb02b227229b69ca5357fd", size = 1143140, upload_time = "2025-02-14T06:02:04.165Z" }, + { url = "https://files.pythonhosted.org/packages/f1/95/cc2c6d79df8f113bdc6c99cdec985a878768120d87d839a34da4bd3ff90a/tiktoken-0.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:92a5fb085a6a3b7350b8fc838baf493317ca0e17bd95e8642f95fc69ecfed1de", size = 1197860, upload_time = "2025-02-14T06:02:06.268Z" }, + { url = "https://files.pythonhosted.org/packages/c7/6c/9c1a4cc51573e8867c9381db1814223c09ebb4716779c7f845d48688b9c8/tiktoken-0.9.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:15a2752dea63d93b0332fb0ddb05dd909371ededa145fe6a3242f46724fa7990", size = 1259661, upload_time = "2025-02-14T06:02:08.889Z" }, + { url = "https://files.pythonhosted.org/packages/cd/4c/22eb8e9856a2b1808d0a002d171e534eac03f96dbe1161978d7389a59498/tiktoken-0.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:26113fec3bd7a352e4b33dbaf1bd8948de2507e30bd95a44e2b1156647bc01b4", size = 894026, upload_time = "2025-02-14T06:02:12.841Z" }, + { url = "https://files.pythonhosted.org/packages/4d/ae/4613a59a2a48e761c5161237fc850eb470b4bb93696db89da51b79a871f1/tiktoken-0.9.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:f32cc56168eac4851109e9b5d327637f15fd662aa30dd79f964b7c39fbadd26e", size = 1065987, upload_time = "2025-02-14T06:02:14.174Z" }, + { url = "https://files.pythonhosted.org/packages/3f/86/55d9d1f5b5a7e1164d0f1538a85529b5fcba2b105f92db3622e5d7de6522/tiktoken-0.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:45556bc41241e5294063508caf901bf92ba52d8ef9222023f83d2483a3055348", size = 1009155, upload_time = "2025-02-14T06:02:15.384Z" }, + { url = "https://files.pythonhosted.org/packages/03/58/01fb6240df083b7c1916d1dcb024e2b761213c95d576e9f780dfb5625a76/tiktoken-0.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03935988a91d6d3216e2ec7c645afbb3d870b37bcb67ada1943ec48678e7ee33", size = 1142898, upload_time = "2025-02-14T06:02:16.666Z" }, + { url = "https://files.pythonhosted.org/packages/b1/73/41591c525680cd460a6becf56c9b17468d3711b1df242c53d2c7b2183d16/tiktoken-0.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b3d80aad8d2c6b9238fc1a5524542087c52b860b10cbf952429ffb714bc1136", size = 1197535, upload_time = "2025-02-14T06:02:18.595Z" }, + { url = "https://files.pythonhosted.org/packages/7d/7c/1069f25521c8f01a1a182f362e5c8e0337907fae91b368b7da9c3e39b810/tiktoken-0.9.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b2a21133be05dc116b1d0372af051cd2c6aa1d2188250c9b553f9fa49301b336", size = 1259548, upload_time = "2025-02-14T06:02:20.729Z" }, + { url = "https://files.pythonhosted.org/packages/6f/07/c67ad1724b8e14e2b4c8cca04b15da158733ac60136879131db05dda7c30/tiktoken-0.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:11a20e67fdf58b0e2dea7b8654a288e481bb4fc0289d3ad21291f8d0849915fb", size = 893895, upload_time = "2025-02-14T06:02:22.67Z" }, + { url = "https://files.pythonhosted.org/packages/cf/e5/21ff33ecfa2101c1bb0f9b6df750553bd873b7fb532ce2cb276ff40b197f/tiktoken-0.9.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e88f121c1c22b726649ce67c089b90ddda8b9662545a8aeb03cfef15967ddd03", size = 1065073, upload_time = "2025-02-14T06:02:24.768Z" }, + { url = "https://files.pythonhosted.org/packages/8e/03/a95e7b4863ee9ceec1c55983e4cc9558bcfd8f4f80e19c4f8a99642f697d/tiktoken-0.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a6600660f2f72369acb13a57fb3e212434ed38b045fd8cc6cdd74947b4b5d210", size = 1008075, upload_time = "2025-02-14T06:02:26.92Z" }, + { url = "https://files.pythonhosted.org/packages/40/10/1305bb02a561595088235a513ec73e50b32e74364fef4de519da69bc8010/tiktoken-0.9.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95e811743b5dfa74f4b227927ed86cbc57cad4df859cb3b643be797914e41794", size = 1140754, upload_time = "2025-02-14T06:02:28.124Z" }, + { url = "https://files.pythonhosted.org/packages/1b/40/da42522018ca496432ffd02793c3a72a739ac04c3794a4914570c9bb2925/tiktoken-0.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99376e1370d59bcf6935c933cb9ba64adc29033b7e73f5f7569f3aad86552b22", size = 1196678, upload_time = "2025-02-14T06:02:29.845Z" }, + { url = "https://files.pythonhosted.org/packages/5c/41/1e59dddaae270ba20187ceb8aa52c75b24ffc09f547233991d5fd822838b/tiktoken-0.9.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:badb947c32739fb6ddde173e14885fb3de4d32ab9d8c591cbd013c22b4c31dd2", size = 1259283, upload_time = "2025-02-14T06:02:33.838Z" }, + { url = "https://files.pythonhosted.org/packages/5b/64/b16003419a1d7728d0d8c0d56a4c24325e7b10a21a9dd1fc0f7115c02f0a/tiktoken-0.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:5a62d7a25225bafed786a524c1b9f0910a1128f4232615bf3f8257a73aaa3b16", size = 894897, upload_time = "2025-02-14T06:02:36.265Z" }, + { url = "https://files.pythonhosted.org/packages/7a/11/09d936d37f49f4f494ffe660af44acd2d99eb2429d60a57c71318af214e0/tiktoken-0.9.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2b0e8e05a26eda1249e824156d537015480af7ae222ccb798e5234ae0285dbdb", size = 1064919, upload_time = "2025-02-14T06:02:37.494Z" }, + { url = "https://files.pythonhosted.org/packages/80/0e/f38ba35713edb8d4197ae602e80837d574244ced7fb1b6070b31c29816e0/tiktoken-0.9.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:27d457f096f87685195eea0165a1807fae87b97b2161fe8c9b1df5bd74ca6f63", size = 1007877, upload_time = "2025-02-14T06:02:39.516Z" }, + { url = "https://files.pythonhosted.org/packages/fe/82/9197f77421e2a01373e27a79dd36efdd99e6b4115746ecc553318ecafbf0/tiktoken-0.9.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cf8ded49cddf825390e36dd1ad35cd49589e8161fdcb52aa25f0583e90a3e01", size = 1140095, upload_time = "2025-02-14T06:02:41.791Z" }, + { url = "https://files.pythonhosted.org/packages/f2/bb/4513da71cac187383541facd0291c4572b03ec23c561de5811781bbd988f/tiktoken-0.9.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc156cb314119a8bb9748257a2eaebd5cc0753b6cb491d26694ed42fc7cb3139", size = 1195649, upload_time = "2025-02-14T06:02:43Z" }, + { url = "https://files.pythonhosted.org/packages/fa/5c/74e4c137530dd8504e97e3a41729b1103a4ac29036cbfd3250b11fd29451/tiktoken-0.9.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:cd69372e8c9dd761f0ab873112aba55a0e3e506332dd9f7522ca466e817b1b7a", size = 1258465, upload_time = "2025-02-14T06:02:45.046Z" }, + { url = "https://files.pythonhosted.org/packages/de/a8/8f499c179ec900783ffe133e9aab10044481679bb9aad78436d239eee716/tiktoken-0.9.0-cp313-cp313-win_amd64.whl", hash = "sha256:5ea0edb6f83dc56d794723286215918c1cde03712cbbafa0348b33448faf5b95", size = 894669, upload_time = "2025-02-14T06:02:47.341Z" }, + { url = "https://files.pythonhosted.org/packages/c4/92/4d681b5c066d417b98f22a0176358d9e606e183c6b61c337d61fb54accb4/tiktoken-0.9.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c6386ca815e7d96ef5b4ac61e0048cd32ca5a92d5781255e13b31381d28667dc", size = 1066217, upload_time = "2025-02-14T06:02:49.259Z" }, + { url = "https://files.pythonhosted.org/packages/12/dd/af27bbe186df481666de48cf0f2f4e0643ba9c78b472e7bf70144c663b22/tiktoken-0.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:75f6d5db5bc2c6274b674ceab1615c1778e6416b14705827d19b40e6355f03e0", size = 1009441, upload_time = "2025-02-14T06:02:51.347Z" }, + { url = "https://files.pythonhosted.org/packages/33/35/2792b7dcb8b150d2767322637513c73a3e80833c19212efea80b31087894/tiktoken-0.9.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e15b16f61e6f4625a57a36496d28dd182a8a60ec20a534c5343ba3cafa156ac7", size = 1144423, upload_time = "2025-02-14T06:02:52.547Z" }, + { url = "https://files.pythonhosted.org/packages/65/ae/4d1682510172ce3500bbed3b206ebc4efefe280f0bf1179cfb043f88cc16/tiktoken-0.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ebcec91babf21297022882344c3f7d9eed855931466c3311b1ad6b64befb3df", size = 1199002, upload_time = "2025-02-14T06:02:55.72Z" }, + { url = "https://files.pythonhosted.org/packages/1c/2e/df2dc31dd161190f315829775a9652ea01d60f307af8f98e35bdd14a6a93/tiktoken-0.9.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:e5fd49e7799579240f03913447c0cdfa1129625ebd5ac440787afc4345990427", size = 1260610, upload_time = "2025-02-14T06:02:56.924Z" }, + { url = "https://files.pythonhosted.org/packages/70/22/e8fc1bf9cdecc439b7ddc28a45b976a8c699a38874c070749d855696368a/tiktoken-0.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:26242ca9dc8b58e875ff4ca078b9a94d2f0813e6a535dcd2205df5d49d927cc7", size = 894215, upload_time = "2025-02-14T06:02:59.031Z" }, ] [[package]] name = "tomli" version = "2.2.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175 } +sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175, upload_time = "2024-11-27T22:38:36.873Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077 }, - { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429 }, - { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067 }, - { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030 }, - { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898 }, - { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894 }, - { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319 }, - { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273 }, - { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310 }, - { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309 }, - { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762 }, - { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453 }, - { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486 }, - { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349 }, - { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159 }, - { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243 }, - { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645 }, - { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584 }, - { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875 }, - { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418 }, - { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708 }, - { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582 }, - { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543 }, - { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691 }, - { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170 }, - { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530 }, - { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666 }, - { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954 }, - { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724 }, - { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383 }, - { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257 }, + { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077, upload_time = "2024-11-27T22:37:54.956Z" }, + { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429, upload_time = "2024-11-27T22:37:56.698Z" }, + { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067, upload_time = "2024-11-27T22:37:57.63Z" }, + { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030, upload_time = "2024-11-27T22:37:59.344Z" }, + { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898, upload_time = "2024-11-27T22:38:00.429Z" }, + { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894, upload_time = "2024-11-27T22:38:02.094Z" }, + { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319, upload_time = "2024-11-27T22:38:03.206Z" }, + { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273, upload_time = "2024-11-27T22:38:04.217Z" }, + { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310, upload_time = "2024-11-27T22:38:05.908Z" }, + { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309, upload_time = "2024-11-27T22:38:06.812Z" }, + { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762, upload_time = "2024-11-27T22:38:07.731Z" }, + { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453, upload_time = "2024-11-27T22:38:09.384Z" }, + { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486, upload_time = "2024-11-27T22:38:10.329Z" }, + { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349, upload_time = "2024-11-27T22:38:11.443Z" }, + { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159, upload_time = "2024-11-27T22:38:13.099Z" }, + { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243, upload_time = "2024-11-27T22:38:14.766Z" }, + { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645, upload_time = "2024-11-27T22:38:15.843Z" }, + { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584, upload_time = "2024-11-27T22:38:17.645Z" }, + { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875, upload_time = "2024-11-27T22:38:19.159Z" }, + { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418, upload_time = "2024-11-27T22:38:20.064Z" }, + { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708, upload_time = "2024-11-27T22:38:21.659Z" }, + { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582, upload_time = "2024-11-27T22:38:22.693Z" }, + { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543, upload_time = "2024-11-27T22:38:24.367Z" }, + { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691, upload_time = "2024-11-27T22:38:26.081Z" }, + { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170, upload_time = "2024-11-27T22:38:27.921Z" }, + { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530, upload_time = "2024-11-27T22:38:29.591Z" }, + { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666, upload_time = "2024-11-27T22:38:30.639Z" }, + { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954, upload_time = "2024-11-27T22:38:31.702Z" }, + { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724, upload_time = "2024-11-27T22:38:32.837Z" }, + { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383, upload_time = "2024-11-27T22:38:34.455Z" }, + { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload_time = "2024-11-27T22:38:35.385Z" }, ] [[package]] @@ -1639,9 +1768,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737 } +sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737, upload_time = "2024-11-24T20:12:22.481Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540 }, + { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload_time = "2024-11-24T20:12:19.698Z" }, ] [[package]] @@ -1654,18 +1783,18 @@ dependencies = [ { name = "shellingham" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8b/6f/3991f0f1c7fcb2df31aef28e0594d8d54b05393a0e4e34c65e475c2a5d41/typer-0.15.2.tar.gz", hash = "sha256:ab2fab47533a813c49fe1f16b1a370fd5819099c00b119e0633df65f22144ba5", size = 100711 } +sdist = { url = "https://files.pythonhosted.org/packages/8b/6f/3991f0f1c7fcb2df31aef28e0594d8d54b05393a0e4e34c65e475c2a5d41/typer-0.15.2.tar.gz", hash = "sha256:ab2fab47533a813c49fe1f16b1a370fd5819099c00b119e0633df65f22144ba5", size = 100711, upload_time = "2025-02-27T19:17:34.807Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7f/fc/5b29fea8cee020515ca82cc68e3b8e1e34bb19a3535ad854cac9257b414c/typer-0.15.2-py3-none-any.whl", hash = "sha256:46a499c6107d645a9c13f7ee46c5d5096cae6f5fc57dd11eccbbb9ae3e44ddfc", size = 45061 }, + { url = "https://files.pythonhosted.org/packages/7f/fc/5b29fea8cee020515ca82cc68e3b8e1e34bb19a3535ad854cac9257b414c/typer-0.15.2-py3-none-any.whl", hash = "sha256:46a499c6107d645a9c13f7ee46c5d5096cae6f5fc57dd11eccbbb9ae3e44ddfc", size = 45061, upload_time = "2025-02-27T19:17:32.111Z" }, ] [[package]] name = "typing-extensions" version = "4.13.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/76/ad/cd3e3465232ec2416ae9b983f27b9e94dc8171d56ac99b345319a9475967/typing_extensions-4.13.1.tar.gz", hash = "sha256:98795af00fb9640edec5b8e31fc647597b4691f099ad75f469a2616be1a76dff", size = 106633 } +sdist = { url = "https://files.pythonhosted.org/packages/76/ad/cd3e3465232ec2416ae9b983f27b9e94dc8171d56ac99b345319a9475967/typing_extensions-4.13.1.tar.gz", hash = "sha256:98795af00fb9640edec5b8e31fc647597b4691f099ad75f469a2616be1a76dff", size = 106633, upload_time = "2025-04-03T16:11:20.955Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/df/c5/e7a0b0f5ed69f94c8ab7379c599e6036886bffcde609969a5325f47f1332/typing_extensions-4.13.1-py3-none-any.whl", hash = "sha256:4b6cf02909eb5495cfbc3f6e8fd49217e6cc7944e145cdda8caa3734777f9e69", size = 45739 }, + { url = "https://files.pythonhosted.org/packages/df/c5/e7a0b0f5ed69f94c8ab7379c599e6036886bffcde609969a5325f47f1332/typing_extensions-4.13.1-py3-none-any.whl", hash = "sha256:4b6cf02909eb5495cfbc3f6e8fd49217e6cc7944e145cdda8caa3734777f9e69", size = 45739, upload_time = "2025-04-03T16:11:19.281Z" }, ] [[package]] @@ -1675,27 +1804,27 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/82/5c/e6082df02e215b846b4b8c0b887a64d7d08ffaba30605502639d44c06b82/typing_inspection-0.4.0.tar.gz", hash = "sha256:9765c87de36671694a67904bf2c96e395be9c6439bb6c87b5142569dcdd65122", size = 76222 } +sdist = { url = "https://files.pythonhosted.org/packages/82/5c/e6082df02e215b846b4b8c0b887a64d7d08ffaba30605502639d44c06b82/typing_inspection-0.4.0.tar.gz", hash = "sha256:9765c87de36671694a67904bf2c96e395be9c6439bb6c87b5142569dcdd65122", size = 76222, upload_time = "2025-02-25T17:27:59.638Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/31/08/aa4fdfb71f7de5176385bd9e90852eaf6b5d622735020ad600f2bab54385/typing_inspection-0.4.0-py3-none-any.whl", hash = "sha256:50e72559fcd2a6367a19f7a7e610e6afcb9fac940c650290eed893d61386832f", size = 14125 }, + { url = "https://files.pythonhosted.org/packages/31/08/aa4fdfb71f7de5176385bd9e90852eaf6b5d622735020ad600f2bab54385/typing_inspection-0.4.0-py3-none-any.whl", hash = "sha256:50e72559fcd2a6367a19f7a7e610e6afcb9fac940c650290eed893d61386832f", size = 14125, upload_time = "2025-02-25T17:27:57.754Z" }, ] [[package]] name = "tzdata" version = "2025.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380 } +sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload_time = "2025-03-23T13:54:43.652Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839 }, + { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload_time = "2025-03-23T13:54:41.845Z" }, ] [[package]] name = "urllib3" version = "2.3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/aa/63/e53da845320b757bf29ef6a9062f5c669fe997973f966045cb019c3f4b66/urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d", size = 307268 } +sdist = { url = "https://files.pythonhosted.org/packages/aa/63/e53da845320b757bf29ef6a9062f5c669fe997973f966045cb019c3f4b66/urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d", size = 307268, upload_time = "2024-12-22T07:47:30.032Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c8/19/4ec628951a74043532ca2cf5d97b7b14863931476d117c471e8e2b1eb39f/urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df", size = 128369 }, + { url = "https://files.pythonhosted.org/packages/c8/19/4ec628951a74043532ca2cf5d97b7b14863931476d117c471e8e2b1eb39f/urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df", size = 128369, upload_time = "2024-12-22T07:47:28.074Z" }, ] [[package]] @@ -1707,9 +1836,9 @@ dependencies = [ { name = "h11" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4b/4d/938bd85e5bf2edeec766267a5015ad969730bb91e31b44021dfe8b22df6c/uvicorn-0.34.0.tar.gz", hash = "sha256:404051050cd7e905de2c9a7e61790943440b3416f49cb409f965d9dcd0fa73e9", size = 76568 } +sdist = { url = "https://files.pythonhosted.org/packages/4b/4d/938bd85e5bf2edeec766267a5015ad969730bb91e31b44021dfe8b22df6c/uvicorn-0.34.0.tar.gz", hash = "sha256:404051050cd7e905de2c9a7e61790943440b3416f49cb409f965d9dcd0fa73e9", size = 76568, upload_time = "2024-12-15T13:33:30.42Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/61/14/33a3a1352cfa71812a3a21e8c9bfb83f60b0011f5e36f2b1399d51928209/uvicorn-0.34.0-py3-none-any.whl", hash = "sha256:023dc038422502fa28a09c7a30bf2b6991512da7dcdb8fd35fe57cfc154126f4", size = 62315 }, + { url = "https://files.pythonhosted.org/packages/61/14/33a3a1352cfa71812a3a21e8c9bfb83f60b0011f5e36f2b1399d51928209/uvicorn-0.34.0-py3-none-any.whl", hash = "sha256:023dc038422502fa28a09c7a30bf2b6991512da7dcdb8fd35fe57cfc154126f4", size = 62315, upload_time = "2024-12-15T13:33:27.467Z" }, ] [package.optional-dependencies] @@ -1727,38 +1856,38 @@ standard = [ name = "uvloop" version = "0.21.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/af/c0/854216d09d33c543f12a44b393c402e89a920b1a0a7dc634c42de91b9cf6/uvloop-0.21.0.tar.gz", hash = "sha256:3bf12b0fda68447806a7ad847bfa591613177275d35b6724b1ee573faa3704e3", size = 2492741 } +sdist = { url = "https://files.pythonhosted.org/packages/af/c0/854216d09d33c543f12a44b393c402e89a920b1a0a7dc634c42de91b9cf6/uvloop-0.21.0.tar.gz", hash = "sha256:3bf12b0fda68447806a7ad847bfa591613177275d35b6724b1ee573faa3704e3", size = 2492741, upload_time = "2024-10-14T23:38:35.489Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3d/76/44a55515e8c9505aa1420aebacf4dd82552e5e15691654894e90d0bd051a/uvloop-0.21.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ec7e6b09a6fdded42403182ab6b832b71f4edaf7f37a9a0e371a01db5f0cb45f", size = 1442019 }, - { url = "https://files.pythonhosted.org/packages/35/5a/62d5800358a78cc25c8a6c72ef8b10851bdb8cca22e14d9c74167b7f86da/uvloop-0.21.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:196274f2adb9689a289ad7d65700d37df0c0930fd8e4e743fa4834e850d7719d", size = 801898 }, - { url = "https://files.pythonhosted.org/packages/f3/96/63695e0ebd7da6c741ccd4489b5947394435e198a1382349c17b1146bb97/uvloop-0.21.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f38b2e090258d051d68a5b14d1da7203a3c3677321cf32a95a6f4db4dd8b6f26", size = 3827735 }, - { url = "https://files.pythonhosted.org/packages/61/e0/f0f8ec84979068ffae132c58c79af1de9cceeb664076beea86d941af1a30/uvloop-0.21.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87c43e0f13022b998eb9b973b5e97200c8b90823454d4bc06ab33829e09fb9bb", size = 3825126 }, - { url = "https://files.pythonhosted.org/packages/bf/fe/5e94a977d058a54a19df95f12f7161ab6e323ad49f4dabc28822eb2df7ea/uvloop-0.21.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:10d66943def5fcb6e7b37310eb6b5639fd2ccbc38df1177262b0640c3ca68c1f", size = 3705789 }, - { url = "https://files.pythonhosted.org/packages/26/dd/c7179618e46092a77e036650c1f056041a028a35c4d76945089fcfc38af8/uvloop-0.21.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:67dd654b8ca23aed0a8e99010b4c34aca62f4b7fce88f39d452ed7622c94845c", size = 3800523 }, - { url = "https://files.pythonhosted.org/packages/57/a7/4cf0334105c1160dd6819f3297f8700fda7fc30ab4f61fbf3e725acbc7cc/uvloop-0.21.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c0f3fa6200b3108919f8bdabb9a7f87f20e7097ea3c543754cabc7d717d95cf8", size = 1447410 }, - { url = "https://files.pythonhosted.org/packages/8c/7c/1517b0bbc2dbe784b563d6ab54f2ef88c890fdad77232c98ed490aa07132/uvloop-0.21.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0878c2640cf341b269b7e128b1a5fed890adc4455513ca710d77d5e93aa6d6a0", size = 805476 }, - { url = "https://files.pythonhosted.org/packages/ee/ea/0bfae1aceb82a503f358d8d2fa126ca9dbdb2ba9c7866974faec1cb5875c/uvloop-0.21.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9fb766bb57b7388745d8bcc53a359b116b8a04c83a2288069809d2b3466c37e", size = 3960855 }, - { url = "https://files.pythonhosted.org/packages/8a/ca/0864176a649838b838f36d44bf31c451597ab363b60dc9e09c9630619d41/uvloop-0.21.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a375441696e2eda1c43c44ccb66e04d61ceeffcd76e4929e527b7fa401b90fb", size = 3973185 }, - { url = "https://files.pythonhosted.org/packages/30/bf/08ad29979a936d63787ba47a540de2132169f140d54aa25bc8c3df3e67f4/uvloop-0.21.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:baa0e6291d91649c6ba4ed4b2f982f9fa165b5bbd50a9e203c416a2797bab3c6", size = 3820256 }, - { url = "https://files.pythonhosted.org/packages/da/e2/5cf6ef37e3daf2f06e651aae5ea108ad30df3cb269102678b61ebf1fdf42/uvloop-0.21.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4509360fcc4c3bd2c70d87573ad472de40c13387f5fda8cb58350a1d7475e58d", size = 3937323 }, - { url = "https://files.pythonhosted.org/packages/8c/4c/03f93178830dc7ce8b4cdee1d36770d2f5ebb6f3d37d354e061eefc73545/uvloop-0.21.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:359ec2c888397b9e592a889c4d72ba3d6befba8b2bb01743f72fffbde663b59c", size = 1471284 }, - { url = "https://files.pythonhosted.org/packages/43/3e/92c03f4d05e50f09251bd8b2b2b584a2a7f8fe600008bcc4523337abe676/uvloop-0.21.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f7089d2dc73179ce5ac255bdf37c236a9f914b264825fdaacaded6990a7fb4c2", size = 821349 }, - { url = "https://files.pythonhosted.org/packages/a6/ef/a02ec5da49909dbbfb1fd205a9a1ac4e88ea92dcae885e7c961847cd51e2/uvloop-0.21.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:baa4dcdbd9ae0a372f2167a207cd98c9f9a1ea1188a8a526431eef2f8116cc8d", size = 4580089 }, - { url = "https://files.pythonhosted.org/packages/06/a7/b4e6a19925c900be9f98bec0a75e6e8f79bb53bdeb891916609ab3958967/uvloop-0.21.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86975dca1c773a2c9864f4c52c5a55631038e387b47eaf56210f873887b6c8dc", size = 4693770 }, - { url = "https://files.pythonhosted.org/packages/ce/0c/f07435a18a4b94ce6bd0677d8319cd3de61f3a9eeb1e5f8ab4e8b5edfcb3/uvloop-0.21.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:461d9ae6660fbbafedd07559c6a2e57cd553b34b0065b6550685f6653a98c1cb", size = 4451321 }, - { url = "https://files.pythonhosted.org/packages/8f/eb/f7032be105877bcf924709c97b1bf3b90255b4ec251f9340cef912559f28/uvloop-0.21.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:183aef7c8730e54c9a3ee3227464daed66e37ba13040bb3f350bc2ddc040f22f", size = 4659022 }, - { url = "https://files.pythonhosted.org/packages/3f/8d/2cbef610ca21539f0f36e2b34da49302029e7c9f09acef0b1c3b5839412b/uvloop-0.21.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:bfd55dfcc2a512316e65f16e503e9e450cab148ef11df4e4e679b5e8253a5281", size = 1468123 }, - { url = "https://files.pythonhosted.org/packages/93/0d/b0038d5a469f94ed8f2b2fce2434a18396d8fbfb5da85a0a9781ebbdec14/uvloop-0.21.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:787ae31ad8a2856fc4e7c095341cccc7209bd657d0e71ad0dc2ea83c4a6fa8af", size = 819325 }, - { url = "https://files.pythonhosted.org/packages/50/94/0a687f39e78c4c1e02e3272c6b2ccdb4e0085fda3b8352fecd0410ccf915/uvloop-0.21.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ee4d4ef48036ff6e5cfffb09dd192c7a5027153948d85b8da7ff705065bacc6", size = 4582806 }, - { url = "https://files.pythonhosted.org/packages/d2/19/f5b78616566ea68edd42aacaf645adbf71fbd83fc52281fba555dc27e3f1/uvloop-0.21.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3df876acd7ec037a3d005b3ab85a7e4110422e4d9c1571d4fc89b0fc41b6816", size = 4701068 }, - { url = "https://files.pythonhosted.org/packages/47/57/66f061ee118f413cd22a656de622925097170b9380b30091b78ea0c6ea75/uvloop-0.21.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd53ecc9a0f3d87ab847503c2e1552b690362e005ab54e8a48ba97da3924c0dc", size = 4454428 }, - { url = "https://files.pythonhosted.org/packages/63/9a/0962b05b308494e3202d3f794a6e85abe471fe3cafdbcf95c2e8c713aabd/uvloop-0.21.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a5c39f217ab3c663dc699c04cbd50c13813e31d917642d459fdcec07555cc553", size = 4660018 }, - { url = "https://files.pythonhosted.org/packages/3c/a4/646a9d0edff7cde25fc1734695d3dfcee0501140dd0e723e4df3f0a50acb/uvloop-0.21.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c097078b8031190c934ed0ebfee8cc5f9ba9642e6eb88322b9958b649750f72b", size = 1439646 }, - { url = "https://files.pythonhosted.org/packages/01/2e/e128c66106af9728f86ebfeeb52af27ecd3cb09336f3e2f3e06053707a15/uvloop-0.21.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:46923b0b5ee7fc0020bef24afe7836cb068f5050ca04caf6b487c513dc1a20b2", size = 800931 }, - { url = "https://files.pythonhosted.org/packages/2d/1a/9fbc2b1543d0df11f7aed1632f64bdf5ecc4053cf98cdc9edb91a65494f9/uvloop-0.21.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:53e420a3afe22cdcf2a0f4846e377d16e718bc70103d7088a4f7623567ba5fb0", size = 3829660 }, - { url = "https://files.pythonhosted.org/packages/b8/c0/392e235e4100ae3b95b5c6dac77f82b529d2760942b1e7e0981e5d8e895d/uvloop-0.21.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88cb67cdbc0e483da00af0b2c3cdad4b7c61ceb1ee0f33fe00e09c81e3a6cb75", size = 3827185 }, - { url = "https://files.pythonhosted.org/packages/e1/24/a5da6aba58f99aed5255eca87d58d1760853e8302d390820cc29058408e3/uvloop-0.21.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:221f4f2a1f46032b403bf3be628011caf75428ee3cc204a22addf96f586b19fd", size = 3705833 }, - { url = "https://files.pythonhosted.org/packages/1a/5c/6ba221bb60f1e6474474102e17e38612ec7a06dc320e22b687ab563d877f/uvloop-0.21.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2d1f581393673ce119355d56da84fe1dd9d2bb8b3d13ce792524e1607139feff", size = 3804696 }, + { url = "https://files.pythonhosted.org/packages/3d/76/44a55515e8c9505aa1420aebacf4dd82552e5e15691654894e90d0bd051a/uvloop-0.21.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ec7e6b09a6fdded42403182ab6b832b71f4edaf7f37a9a0e371a01db5f0cb45f", size = 1442019, upload_time = "2024-10-14T23:37:20.068Z" }, + { url = "https://files.pythonhosted.org/packages/35/5a/62d5800358a78cc25c8a6c72ef8b10851bdb8cca22e14d9c74167b7f86da/uvloop-0.21.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:196274f2adb9689a289ad7d65700d37df0c0930fd8e4e743fa4834e850d7719d", size = 801898, upload_time = "2024-10-14T23:37:22.663Z" }, + { url = "https://files.pythonhosted.org/packages/f3/96/63695e0ebd7da6c741ccd4489b5947394435e198a1382349c17b1146bb97/uvloop-0.21.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f38b2e090258d051d68a5b14d1da7203a3c3677321cf32a95a6f4db4dd8b6f26", size = 3827735, upload_time = "2024-10-14T23:37:25.129Z" }, + { url = "https://files.pythonhosted.org/packages/61/e0/f0f8ec84979068ffae132c58c79af1de9cceeb664076beea86d941af1a30/uvloop-0.21.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87c43e0f13022b998eb9b973b5e97200c8b90823454d4bc06ab33829e09fb9bb", size = 3825126, upload_time = "2024-10-14T23:37:27.59Z" }, + { url = "https://files.pythonhosted.org/packages/bf/fe/5e94a977d058a54a19df95f12f7161ab6e323ad49f4dabc28822eb2df7ea/uvloop-0.21.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:10d66943def5fcb6e7b37310eb6b5639fd2ccbc38df1177262b0640c3ca68c1f", size = 3705789, upload_time = "2024-10-14T23:37:29.385Z" }, + { url = "https://files.pythonhosted.org/packages/26/dd/c7179618e46092a77e036650c1f056041a028a35c4d76945089fcfc38af8/uvloop-0.21.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:67dd654b8ca23aed0a8e99010b4c34aca62f4b7fce88f39d452ed7622c94845c", size = 3800523, upload_time = "2024-10-14T23:37:32.048Z" }, + { url = "https://files.pythonhosted.org/packages/57/a7/4cf0334105c1160dd6819f3297f8700fda7fc30ab4f61fbf3e725acbc7cc/uvloop-0.21.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c0f3fa6200b3108919f8bdabb9a7f87f20e7097ea3c543754cabc7d717d95cf8", size = 1447410, upload_time = "2024-10-14T23:37:33.612Z" }, + { url = "https://files.pythonhosted.org/packages/8c/7c/1517b0bbc2dbe784b563d6ab54f2ef88c890fdad77232c98ed490aa07132/uvloop-0.21.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0878c2640cf341b269b7e128b1a5fed890adc4455513ca710d77d5e93aa6d6a0", size = 805476, upload_time = "2024-10-14T23:37:36.11Z" }, + { url = "https://files.pythonhosted.org/packages/ee/ea/0bfae1aceb82a503f358d8d2fa126ca9dbdb2ba9c7866974faec1cb5875c/uvloop-0.21.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9fb766bb57b7388745d8bcc53a359b116b8a04c83a2288069809d2b3466c37e", size = 3960855, upload_time = "2024-10-14T23:37:37.683Z" }, + { url = "https://files.pythonhosted.org/packages/8a/ca/0864176a649838b838f36d44bf31c451597ab363b60dc9e09c9630619d41/uvloop-0.21.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a375441696e2eda1c43c44ccb66e04d61ceeffcd76e4929e527b7fa401b90fb", size = 3973185, upload_time = "2024-10-14T23:37:40.226Z" }, + { url = "https://files.pythonhosted.org/packages/30/bf/08ad29979a936d63787ba47a540de2132169f140d54aa25bc8c3df3e67f4/uvloop-0.21.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:baa0e6291d91649c6ba4ed4b2f982f9fa165b5bbd50a9e203c416a2797bab3c6", size = 3820256, upload_time = "2024-10-14T23:37:42.839Z" }, + { url = "https://files.pythonhosted.org/packages/da/e2/5cf6ef37e3daf2f06e651aae5ea108ad30df3cb269102678b61ebf1fdf42/uvloop-0.21.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4509360fcc4c3bd2c70d87573ad472de40c13387f5fda8cb58350a1d7475e58d", size = 3937323, upload_time = "2024-10-14T23:37:45.337Z" }, + { url = "https://files.pythonhosted.org/packages/8c/4c/03f93178830dc7ce8b4cdee1d36770d2f5ebb6f3d37d354e061eefc73545/uvloop-0.21.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:359ec2c888397b9e592a889c4d72ba3d6befba8b2bb01743f72fffbde663b59c", size = 1471284, upload_time = "2024-10-14T23:37:47.833Z" }, + { url = "https://files.pythonhosted.org/packages/43/3e/92c03f4d05e50f09251bd8b2b2b584a2a7f8fe600008bcc4523337abe676/uvloop-0.21.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f7089d2dc73179ce5ac255bdf37c236a9f914b264825fdaacaded6990a7fb4c2", size = 821349, upload_time = "2024-10-14T23:37:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/a6/ef/a02ec5da49909dbbfb1fd205a9a1ac4e88ea92dcae885e7c961847cd51e2/uvloop-0.21.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:baa4dcdbd9ae0a372f2167a207cd98c9f9a1ea1188a8a526431eef2f8116cc8d", size = 4580089, upload_time = "2024-10-14T23:37:51.703Z" }, + { url = "https://files.pythonhosted.org/packages/06/a7/b4e6a19925c900be9f98bec0a75e6e8f79bb53bdeb891916609ab3958967/uvloop-0.21.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86975dca1c773a2c9864f4c52c5a55631038e387b47eaf56210f873887b6c8dc", size = 4693770, upload_time = "2024-10-14T23:37:54.122Z" }, + { url = "https://files.pythonhosted.org/packages/ce/0c/f07435a18a4b94ce6bd0677d8319cd3de61f3a9eeb1e5f8ab4e8b5edfcb3/uvloop-0.21.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:461d9ae6660fbbafedd07559c6a2e57cd553b34b0065b6550685f6653a98c1cb", size = 4451321, upload_time = "2024-10-14T23:37:55.766Z" }, + { url = "https://files.pythonhosted.org/packages/8f/eb/f7032be105877bcf924709c97b1bf3b90255b4ec251f9340cef912559f28/uvloop-0.21.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:183aef7c8730e54c9a3ee3227464daed66e37ba13040bb3f350bc2ddc040f22f", size = 4659022, upload_time = "2024-10-14T23:37:58.195Z" }, + { url = "https://files.pythonhosted.org/packages/3f/8d/2cbef610ca21539f0f36e2b34da49302029e7c9f09acef0b1c3b5839412b/uvloop-0.21.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:bfd55dfcc2a512316e65f16e503e9e450cab148ef11df4e4e679b5e8253a5281", size = 1468123, upload_time = "2024-10-14T23:38:00.688Z" }, + { url = "https://files.pythonhosted.org/packages/93/0d/b0038d5a469f94ed8f2b2fce2434a18396d8fbfb5da85a0a9781ebbdec14/uvloop-0.21.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:787ae31ad8a2856fc4e7c095341cccc7209bd657d0e71ad0dc2ea83c4a6fa8af", size = 819325, upload_time = "2024-10-14T23:38:02.309Z" }, + { url = "https://files.pythonhosted.org/packages/50/94/0a687f39e78c4c1e02e3272c6b2ccdb4e0085fda3b8352fecd0410ccf915/uvloop-0.21.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ee4d4ef48036ff6e5cfffb09dd192c7a5027153948d85b8da7ff705065bacc6", size = 4582806, upload_time = "2024-10-14T23:38:04.711Z" }, + { url = "https://files.pythonhosted.org/packages/d2/19/f5b78616566ea68edd42aacaf645adbf71fbd83fc52281fba555dc27e3f1/uvloop-0.21.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3df876acd7ec037a3d005b3ab85a7e4110422e4d9c1571d4fc89b0fc41b6816", size = 4701068, upload_time = "2024-10-14T23:38:06.385Z" }, + { url = "https://files.pythonhosted.org/packages/47/57/66f061ee118f413cd22a656de622925097170b9380b30091b78ea0c6ea75/uvloop-0.21.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd53ecc9a0f3d87ab847503c2e1552b690362e005ab54e8a48ba97da3924c0dc", size = 4454428, upload_time = "2024-10-14T23:38:08.416Z" }, + { url = "https://files.pythonhosted.org/packages/63/9a/0962b05b308494e3202d3f794a6e85abe471fe3cafdbcf95c2e8c713aabd/uvloop-0.21.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a5c39f217ab3c663dc699c04cbd50c13813e31d917642d459fdcec07555cc553", size = 4660018, upload_time = "2024-10-14T23:38:10.888Z" }, + { url = "https://files.pythonhosted.org/packages/3c/a4/646a9d0edff7cde25fc1734695d3dfcee0501140dd0e723e4df3f0a50acb/uvloop-0.21.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c097078b8031190c934ed0ebfee8cc5f9ba9642e6eb88322b9958b649750f72b", size = 1439646, upload_time = "2024-10-14T23:38:24.656Z" }, + { url = "https://files.pythonhosted.org/packages/01/2e/e128c66106af9728f86ebfeeb52af27ecd3cb09336f3e2f3e06053707a15/uvloop-0.21.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:46923b0b5ee7fc0020bef24afe7836cb068f5050ca04caf6b487c513dc1a20b2", size = 800931, upload_time = "2024-10-14T23:38:26.087Z" }, + { url = "https://files.pythonhosted.org/packages/2d/1a/9fbc2b1543d0df11f7aed1632f64bdf5ecc4053cf98cdc9edb91a65494f9/uvloop-0.21.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:53e420a3afe22cdcf2a0f4846e377d16e718bc70103d7088a4f7623567ba5fb0", size = 3829660, upload_time = "2024-10-14T23:38:27.905Z" }, + { url = "https://files.pythonhosted.org/packages/b8/c0/392e235e4100ae3b95b5c6dac77f82b529d2760942b1e7e0981e5d8e895d/uvloop-0.21.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88cb67cdbc0e483da00af0b2c3cdad4b7c61ceb1ee0f33fe00e09c81e3a6cb75", size = 3827185, upload_time = "2024-10-14T23:38:29.458Z" }, + { url = "https://files.pythonhosted.org/packages/e1/24/a5da6aba58f99aed5255eca87d58d1760853e8302d390820cc29058408e3/uvloop-0.21.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:221f4f2a1f46032b403bf3be628011caf75428ee3cc204a22addf96f586b19fd", size = 3705833, upload_time = "2024-10-14T23:38:31.155Z" }, + { url = "https://files.pythonhosted.org/packages/1a/5c/6ba221bb60f1e6474474102e17e38612ec7a06dc320e22b687ab563d877f/uvloop-0.21.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2d1f581393673ce119355d56da84fe1dd9d2bb8b3d13ce792524e1607139feff", size = 3804696, upload_time = "2024-10-14T23:38:33.633Z" }, ] [[package]] @@ -1768,227 +1897,227 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f5/26/c705fc77d0a9ecdb9b66f1e2976d95b81df3cae518967431e7dbf9b5e219/watchfiles-1.0.4.tar.gz", hash = "sha256:6ba473efd11062d73e4f00c2b730255f9c1bdd73cd5f9fe5b5da8dbd4a717205", size = 94625 } +sdist = { url = "https://files.pythonhosted.org/packages/f5/26/c705fc77d0a9ecdb9b66f1e2976d95b81df3cae518967431e7dbf9b5e219/watchfiles-1.0.4.tar.gz", hash = "sha256:6ba473efd11062d73e4f00c2b730255f9c1bdd73cd5f9fe5b5da8dbd4a717205", size = 94625, upload_time = "2025-01-10T13:05:56.196Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/14/02/22fcaed0396730b0d362bc8d1ffb3be2658fd473eecbb2ba84243e157f11/watchfiles-1.0.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:ba5bb3073d9db37c64520681dd2650f8bd40902d991e7b4cfaeece3e32561d08", size = 395212 }, - { url = "https://files.pythonhosted.org/packages/e9/3d/ec5a2369a46edf3ebe092c39d9ae48e8cb6dacbde51c4b4f98936c524269/watchfiles-1.0.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9f25d0ba0fe2b6d2c921cf587b2bf4c451860086534f40c384329fb96e2044d1", size = 384815 }, - { url = "https://files.pythonhosted.org/packages/df/b4/898991cececbe171e67142c31905510203649569d9817848f47c4177ee42/watchfiles-1.0.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:47eb32ef8c729dbc4f4273baece89398a4d4b5d21a1493efea77a17059f4df8a", size = 450680 }, - { url = "https://files.pythonhosted.org/packages/58/f7/d4aa3000e812cfb5e5c2c6c0a3ec9d0a46a42489a8727edd160631c4e210/watchfiles-1.0.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:076f293100db3b0b634514aa0d294b941daa85fc777f9c698adb1009e5aca0b1", size = 455923 }, - { url = "https://files.pythonhosted.org/packages/dd/95/7e2e4c6aba1b02fb5c76d2f6a450b85215921ec5f8f7ad5efd075369563f/watchfiles-1.0.4-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1eacd91daeb5158c598fe22d7ce66d60878b6294a86477a4715154990394c9b3", size = 482339 }, - { url = "https://files.pythonhosted.org/packages/bb/67/4265b0fabcc2ef2c9e3e8802ba7908cf718a357ebfb49c72e53787156a48/watchfiles-1.0.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:13c2ce7b72026cfbca120d652f02c7750f33b4c9395d79c9790b27f014c8a5a2", size = 519908 }, - { url = "https://files.pythonhosted.org/packages/0d/96/b57802d5f8164bdf070befb4fd3dec4edba5a364ec0670965a97eb8098ce/watchfiles-1.0.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:90192cdc15ab7254caa7765a98132a5a41471cf739513cc9bcf7d2ffcc0ec7b2", size = 501410 }, - { url = "https://files.pythonhosted.org/packages/8b/18/6db0de4e8911ba14e31853201b40c0fa9fea5ecf3feb86b0ad58f006dfc3/watchfiles-1.0.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:278aaa395f405972e9f523bd786ed59dfb61e4b827856be46a42130605fd0899", size = 452876 }, - { url = "https://files.pythonhosted.org/packages/df/df/092a961815edf723a38ba2638c49491365943919c3526cc9cf82c42786a6/watchfiles-1.0.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a462490e75e466edbb9fc4cd679b62187153b3ba804868452ef0577ec958f5ff", size = 615353 }, - { url = "https://files.pythonhosted.org/packages/f3/cf/b85fe645de4ff82f3f436c5e9032379fce37c303f6396a18f9726cc34519/watchfiles-1.0.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8d0d0630930f5cd5af929040e0778cf676a46775753e442a3f60511f2409f48f", size = 613187 }, - { url = "https://files.pythonhosted.org/packages/f6/d4/a9fea27aef4dd69689bc3556718c1157a7accb72aa035ece87c1fa8483b5/watchfiles-1.0.4-cp310-cp310-win32.whl", hash = "sha256:cc27a65069bcabac4552f34fd2dce923ce3fcde0721a16e4fb1b466d63ec831f", size = 270799 }, - { url = "https://files.pythonhosted.org/packages/df/02/dbe9d4439f15dd4ad0720b6e039bde9d66d1f830331f34c18eb70fa6608e/watchfiles-1.0.4-cp310-cp310-win_amd64.whl", hash = "sha256:8b1f135238e75d075359cf506b27bf3f4ca12029c47d3e769d8593a2024ce161", size = 284145 }, - { url = "https://files.pythonhosted.org/packages/0f/bb/8461adc4b1fed009546fb797fc0d5698dcfe5e289cb37e1b8f16a93cdc30/watchfiles-1.0.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:2a9f93f8439639dc244c4d2902abe35b0279102bca7bbcf119af964f51d53c19", size = 394869 }, - { url = "https://files.pythonhosted.org/packages/55/88/9ebf36b3547176d1709c320de78c1fa3263a46be31b5b1267571d9102686/watchfiles-1.0.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9eea33ad8c418847dd296e61eb683cae1c63329b6d854aefcd412e12d94ee235", size = 384905 }, - { url = "https://files.pythonhosted.org/packages/03/8a/04335ce23ef78d8c69f0913e8b20cf7d9233e3986543aeef95ef2d6e43d2/watchfiles-1.0.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:31f1a379c9dcbb3f09cf6be1b7e83b67c0e9faabed0471556d9438a4a4e14202", size = 449944 }, - { url = "https://files.pythonhosted.org/packages/17/4e/c8d5dcd14fe637f4633616dabea8a4af0a10142dccf3b43e0f081ba81ab4/watchfiles-1.0.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ab594e75644421ae0a2484554832ca5895f8cab5ab62de30a1a57db460ce06c6", size = 456020 }, - { url = "https://files.pythonhosted.org/packages/5e/74/3e91e09e1861dd7fbb1190ce7bd786700dc0fbc2ccd33bb9fff5de039229/watchfiles-1.0.4-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fc2eb5d14a8e0d5df7b36288979176fbb39672d45184fc4b1c004d7c3ce29317", size = 482983 }, - { url = "https://files.pythonhosted.org/packages/a1/3d/e64de2d1ce4eb6a574fd78ce3a28c279da263be9ef3cfcab6f708df192f2/watchfiles-1.0.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f68d8e9d5a321163ddacebe97091000955a1b74cd43724e346056030b0bacee", size = 520320 }, - { url = "https://files.pythonhosted.org/packages/2c/bd/52235f7063b57240c66a991696ed27e2a18bd6fcec8a1ea5a040b70d0611/watchfiles-1.0.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f9ce064e81fe79faa925ff03b9f4c1a98b0bbb4a1b8c1b015afa93030cb21a49", size = 500988 }, - { url = "https://files.pythonhosted.org/packages/3a/b0/ff04194141a5fe650c150400dd9e42667916bc0f52426e2e174d779b8a74/watchfiles-1.0.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b77d5622ac5cc91d21ae9c2b284b5d5c51085a0bdb7b518dba263d0af006132c", size = 452573 }, - { url = "https://files.pythonhosted.org/packages/3d/9d/966164332c5a178444ae6d165082d4f351bd56afd9c3ec828eecbf190e6a/watchfiles-1.0.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1941b4e39de9b38b868a69b911df5e89dc43767feeda667b40ae032522b9b5f1", size = 615114 }, - { url = "https://files.pythonhosted.org/packages/94/df/f569ae4c1877f96ad4086c153a8eee5a19a3b519487bf5c9454a3438c341/watchfiles-1.0.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4f8c4998506241dedf59613082d1c18b836e26ef2a4caecad0ec41e2a15e4226", size = 613076 }, - { url = "https://files.pythonhosted.org/packages/15/ae/8ce5f29e65d5fa5790e3c80c289819c55e12be2e1b9f5b6a0e55e169b97d/watchfiles-1.0.4-cp311-cp311-win32.whl", hash = "sha256:4ebbeca9360c830766b9f0df3640b791be569d988f4be6c06d6fae41f187f105", size = 271013 }, - { url = "https://files.pythonhosted.org/packages/a4/c6/79dc4a7c598a978e5fafa135090aaf7bbb03b8dec7bada437dfbe578e7ed/watchfiles-1.0.4-cp311-cp311-win_amd64.whl", hash = "sha256:05d341c71f3d7098920f8551d4df47f7b57ac5b8dad56558064c3431bdfc0b74", size = 284229 }, - { url = "https://files.pythonhosted.org/packages/37/3d/928633723211753f3500bfb138434f080363b87a1b08ca188b1ce54d1e05/watchfiles-1.0.4-cp311-cp311-win_arm64.whl", hash = "sha256:32b026a6ab64245b584acf4931fe21842374da82372d5c039cba6bf99ef722f3", size = 276824 }, - { url = "https://files.pythonhosted.org/packages/5b/1a/8f4d9a1461709756ace48c98f07772bc6d4519b1e48b5fa24a4061216256/watchfiles-1.0.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:229e6ec880eca20e0ba2f7e2249c85bae1999d330161f45c78d160832e026ee2", size = 391345 }, - { url = "https://files.pythonhosted.org/packages/bc/d2/6750b7b3527b1cdaa33731438432e7238a6c6c40a9924049e4cebfa40805/watchfiles-1.0.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5717021b199e8353782dce03bd8a8f64438832b84e2885c4a645f9723bf656d9", size = 381515 }, - { url = "https://files.pythonhosted.org/packages/4e/17/80500e42363deef1e4b4818729ed939aaddc56f82f4e72b2508729dd3c6b/watchfiles-1.0.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0799ae68dfa95136dde7c472525700bd48777875a4abb2ee454e3ab18e9fc712", size = 449767 }, - { url = "https://files.pythonhosted.org/packages/10/37/1427fa4cfa09adbe04b1e97bced19a29a3462cc64c78630787b613a23f18/watchfiles-1.0.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:43b168bba889886b62edb0397cab5b6490ffb656ee2fcb22dec8bfeb371a9e12", size = 455677 }, - { url = "https://files.pythonhosted.org/packages/c5/7a/39e9397f3a19cb549a7d380412fd9e507d4854eddc0700bfad10ef6d4dba/watchfiles-1.0.4-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fb2c46e275fbb9f0c92e7654b231543c7bbfa1df07cdc4b99fa73bedfde5c844", size = 482219 }, - { url = "https://files.pythonhosted.org/packages/45/2d/7113931a77e2ea4436cad0c1690c09a40a7f31d366f79c6f0a5bc7a4f6d5/watchfiles-1.0.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:857f5fc3aa027ff5e57047da93f96e908a35fe602d24f5e5d8ce64bf1f2fc733", size = 518830 }, - { url = "https://files.pythonhosted.org/packages/f9/1b/50733b1980fa81ef3c70388a546481ae5fa4c2080040100cd7bf3bf7b321/watchfiles-1.0.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55ccfd27c497b228581e2838d4386301227fc0cb47f5a12923ec2fe4f97b95af", size = 497997 }, - { url = "https://files.pythonhosted.org/packages/2b/b4/9396cc61b948ef18943e7c85ecfa64cf940c88977d882da57147f62b34b1/watchfiles-1.0.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c11ea22304d17d4385067588123658e9f23159225a27b983f343fcffc3e796a", size = 452249 }, - { url = "https://files.pythonhosted.org/packages/fb/69/0c65a5a29e057ad0dc691c2fa6c23b2983c7dabaa190ba553b29ac84c3cc/watchfiles-1.0.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:74cb3ca19a740be4caa18f238298b9d472c850f7b2ed89f396c00a4c97e2d9ff", size = 614412 }, - { url = "https://files.pythonhosted.org/packages/7f/b9/319fcba6eba5fad34327d7ce16a6b163b39741016b1996f4a3c96b8dd0e1/watchfiles-1.0.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c7cce76c138a91e720d1df54014a047e680b652336e1b73b8e3ff3158e05061e", size = 611982 }, - { url = "https://files.pythonhosted.org/packages/f1/47/143c92418e30cb9348a4387bfa149c8e0e404a7c5b0585d46d2f7031b4b9/watchfiles-1.0.4-cp312-cp312-win32.whl", hash = "sha256:b045c800d55bc7e2cadd47f45a97c7b29f70f08a7c2fa13241905010a5493f94", size = 271822 }, - { url = "https://files.pythonhosted.org/packages/ea/94/b0165481bff99a64b29e46e07ac2e0df9f7a957ef13bec4ceab8515f44e3/watchfiles-1.0.4-cp312-cp312-win_amd64.whl", hash = "sha256:c2acfa49dd0ad0bf2a9c0bb9a985af02e89345a7189be1efc6baa085e0f72d7c", size = 285441 }, - { url = "https://files.pythonhosted.org/packages/11/de/09fe56317d582742d7ca8c2ca7b52a85927ebb50678d9b0fa8194658f536/watchfiles-1.0.4-cp312-cp312-win_arm64.whl", hash = "sha256:22bb55a7c9e564e763ea06c7acea24fc5d2ee5dfc5dafc5cfbedfe58505e9f90", size = 277141 }, - { url = "https://files.pythonhosted.org/packages/08/98/f03efabec64b5b1fa58c0daab25c68ef815b0f320e54adcacd0d6847c339/watchfiles-1.0.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:8012bd820c380c3d3db8435e8cf7592260257b378b649154a7948a663b5f84e9", size = 390954 }, - { url = "https://files.pythonhosted.org/packages/16/09/4dd49ba0a32a45813debe5fb3897955541351ee8142f586303b271a02b40/watchfiles-1.0.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:aa216f87594f951c17511efe5912808dfcc4befa464ab17c98d387830ce07b60", size = 381133 }, - { url = "https://files.pythonhosted.org/packages/76/59/5aa6fc93553cd8d8ee75c6247763d77c02631aed21551a97d94998bf1dae/watchfiles-1.0.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62c9953cf85529c05b24705639ffa390f78c26449e15ec34d5339e8108c7c407", size = 449516 }, - { url = "https://files.pythonhosted.org/packages/4c/aa/df4b6fe14b6317290b91335b23c96b488d365d65549587434817e06895ea/watchfiles-1.0.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7cf684aa9bba4cd95ecb62c822a56de54e3ae0598c1a7f2065d51e24637a3c5d", size = 454820 }, - { url = "https://files.pythonhosted.org/packages/5e/71/185f8672f1094ce48af33252c73e39b48be93b761273872d9312087245f6/watchfiles-1.0.4-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f44a39aee3cbb9b825285ff979ab887a25c5d336e5ec3574f1506a4671556a8d", size = 481550 }, - { url = "https://files.pythonhosted.org/packages/85/d7/50ebba2c426ef1a5cb17f02158222911a2e005d401caf5d911bfca58f4c4/watchfiles-1.0.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a38320582736922be8c865d46520c043bff350956dfc9fbaee3b2df4e1740a4b", size = 518647 }, - { url = "https://files.pythonhosted.org/packages/f0/7a/4c009342e393c545d68987e8010b937f72f47937731225b2b29b7231428f/watchfiles-1.0.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39f4914548b818540ef21fd22447a63e7be6e24b43a70f7642d21f1e73371590", size = 497547 }, - { url = "https://files.pythonhosted.org/packages/0f/7c/1cf50b35412d5c72d63b2bf9a4fffee2e1549a245924960dd087eb6a6de4/watchfiles-1.0.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f12969a3765909cf5dc1e50b2436eb2c0e676a3c75773ab8cc3aa6175c16e902", size = 452179 }, - { url = "https://files.pythonhosted.org/packages/d6/a9/3db1410e1c1413735a9a472380e4f431ad9a9e81711cda2aaf02b7f62693/watchfiles-1.0.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:0986902677a1a5e6212d0c49b319aad9cc48da4bd967f86a11bde96ad9676ca1", size = 614125 }, - { url = "https://files.pythonhosted.org/packages/f2/e1/0025d365cf6248c4d1ee4c3d2e3d373bdd3f6aff78ba4298f97b4fad2740/watchfiles-1.0.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:308ac265c56f936636e3b0e3f59e059a40003c655228c131e1ad439957592303", size = 611911 }, - { url = "https://files.pythonhosted.org/packages/55/55/035838277d8c98fc8c917ac9beeb0cd6c59d675dc2421df5f9fcf44a0070/watchfiles-1.0.4-cp313-cp313-win32.whl", hash = "sha256:aee397456a29b492c20fda2d8961e1ffb266223625346ace14e4b6d861ba9c80", size = 271152 }, - { url = "https://files.pythonhosted.org/packages/f0/e5/96b8e55271685ddbadc50ce8bc53aa2dff278fb7ac4c2e473df890def2dc/watchfiles-1.0.4-cp313-cp313-win_amd64.whl", hash = "sha256:d6097538b0ae5c1b88c3b55afa245a66793a8fec7ada6755322e465fb1a0e8cc", size = 285216 }, - { url = "https://files.pythonhosted.org/packages/15/81/54484fc2fa715abe79694b975692af963f0878fb9d72b8251aa542bf3f10/watchfiles-1.0.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:d3452c1ec703aa1c61e15dfe9d482543e4145e7c45a6b8566978fbb044265a21", size = 394967 }, - { url = "https://files.pythonhosted.org/packages/14/b3/557f0cd90add86586fe3deeebd11e8299db6bc3452b44a534f844c6ab831/watchfiles-1.0.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7b75fee5a16826cf5c46fe1c63116e4a156924d668c38b013e6276f2582230f0", size = 384707 }, - { url = "https://files.pythonhosted.org/packages/03/a3/34638e1bffcb85a405e7b005e30bb211fd9be2ab2cb1847f2ceb81bef27b/watchfiles-1.0.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e997802d78cdb02623b5941830ab06f8860038faf344f0d288d325cc9c5d2ff", size = 450442 }, - { url = "https://files.pythonhosted.org/packages/8f/9f/6a97460dd11a606003d634c7158d9fea8517e98daffc6f56d0f5fde2e86a/watchfiles-1.0.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e0611d244ce94d83f5b9aff441ad196c6e21b55f77f3c47608dcf651efe54c4a", size = 455959 }, - { url = "https://files.pythonhosted.org/packages/9d/bb/e0648c6364e4d37ec692bc3f0c77507d17d8bb8f75689148819142010bbf/watchfiles-1.0.4-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9745a4210b59e218ce64c91deb599ae8775c8a9da4e95fb2ee6fe745fc87d01a", size = 483187 }, - { url = "https://files.pythonhosted.org/packages/dd/ad/d9290586a25288a81dfa8ad6329cf1de32aa1a9798ace45259eb95dcfb37/watchfiles-1.0.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4810ea2ae622add560f4aa50c92fef975e475f7ac4900ce5ff5547b2434642d8", size = 519733 }, - { url = "https://files.pythonhosted.org/packages/4e/a9/150c1666825cc9637093f8cae7fc6f53b3296311ab8bd65f1389acb717cb/watchfiles-1.0.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:740d103cd01458f22462dedeb5a3382b7f2c57d07ff033fbc9465919e5e1d0f3", size = 502275 }, - { url = "https://files.pythonhosted.org/packages/44/dc/5bfd21e20a330aca1706ac44713bc322838061938edf4b53130f97a7b211/watchfiles-1.0.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdbd912a61543a36aef85e34f212e5d2486e7c53ebfdb70d1e0b060cc50dd0bf", size = 452907 }, - { url = "https://files.pythonhosted.org/packages/50/fe/8f4fc488f1699f564687b697456eb5c0cb8e2b0b8538150511c234c62094/watchfiles-1.0.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0bc80d91ddaf95f70258cf78c471246846c1986bcc5fd33ccc4a1a67fcb40f9a", size = 615927 }, - { url = "https://files.pythonhosted.org/packages/ad/19/2e45f6f6eec89dd97a4d281635e3d73c17e5f692e7432063bdfdf9562c89/watchfiles-1.0.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ab0311bb2ffcd9f74b6c9de2dda1612c13c84b996d032cd74799adb656af4e8b", size = 613435 }, - { url = "https://files.pythonhosted.org/packages/91/17/dc5ac62ca377827c24321d68050efc2eaee2ebaf3f21d055bbce2206d309/watchfiles-1.0.4-cp39-cp39-win32.whl", hash = "sha256:02a526ee5b5a09e8168314c905fc545c9bc46509896ed282aeb5a8ba9bd6ca27", size = 270810 }, - { url = "https://files.pythonhosted.org/packages/82/2b/dad851342492d538e7ffe72a8c756f747dd147988abb039ac9d6577d2235/watchfiles-1.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:a5ae5706058b27c74bac987d615105da17724172d5aaacc6c362a40599b6de43", size = 284866 }, - { url = "https://files.pythonhosted.org/packages/6f/06/175d5ac6b838fb319008c0cd981d7bf289317c510154d411d3584ca2b67b/watchfiles-1.0.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:cdcc92daeae268de1acf5b7befcd6cfffd9a047098199056c72e4623f531de18", size = 396269 }, - { url = "https://files.pythonhosted.org/packages/86/ee/5db93b0b57dc0587abdbac4149296ee73275f615d790a82cb5598af0557f/watchfiles-1.0.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d8d3d9203705b5797f0af7e7e5baa17c8588030aaadb7f6a86107b7247303817", size = 386010 }, - { url = "https://files.pythonhosted.org/packages/75/61/fe0dc5fedf152bfc085a53711f740701f6bdb8ab6b5c950402b681d4858b/watchfiles-1.0.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bdef5a1be32d0b07dcea3318a0be95d42c98ece24177820226b56276e06b63b0", size = 450913 }, - { url = "https://files.pythonhosted.org/packages/9f/dd/3c7731af3baf1a9957afc643d176f94480921a690ec3237c9f9d11301c08/watchfiles-1.0.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:342622287b5604ddf0ed2d085f3a589099c9ae8b7331df3ae9845571586c4f3d", size = 453474 }, - { url = "https://files.pythonhosted.org/packages/6b/b4/c3998f54c91a35cee60ee6d3a855a069c5dff2bae6865147a46e9090dccd/watchfiles-1.0.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:9fe37a2de80aa785d340f2980276b17ef697ab8db6019b07ee4fd28a8359d2f3", size = 395565 }, - { url = "https://files.pythonhosted.org/packages/3f/05/ac1a4d235beb9ddfb8ac26ce93a00ba6bd1b1b43051ef12d7da957b4a9d1/watchfiles-1.0.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:9d1ef56b56ed7e8f312c934436dea93bfa3e7368adfcf3df4c0da6d4de959a1e", size = 385406 }, - { url = "https://files.pythonhosted.org/packages/4c/ea/36532e7d86525f4e52a10efed182abf33efb106a93d49f5fbc994b256bcd/watchfiles-1.0.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95b42cac65beae3a362629950c444077d1b44f1790ea2772beaea95451c086bb", size = 450424 }, - { url = "https://files.pythonhosted.org/packages/7a/e9/3cbcf4d70cd0b6d3f30631deae1bf37cc0be39887ca327a44462fe546bf5/watchfiles-1.0.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e0227b8ed9074c6172cf55d85b5670199c99ab11fd27d2c473aa30aec67ee42", size = 452488 }, + { url = "https://files.pythonhosted.org/packages/14/02/22fcaed0396730b0d362bc8d1ffb3be2658fd473eecbb2ba84243e157f11/watchfiles-1.0.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:ba5bb3073d9db37c64520681dd2650f8bd40902d991e7b4cfaeece3e32561d08", size = 395212, upload_time = "2025-01-10T13:03:06.589Z" }, + { url = "https://files.pythonhosted.org/packages/e9/3d/ec5a2369a46edf3ebe092c39d9ae48e8cb6dacbde51c4b4f98936c524269/watchfiles-1.0.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9f25d0ba0fe2b6d2c921cf587b2bf4c451860086534f40c384329fb96e2044d1", size = 384815, upload_time = "2025-01-10T13:03:09.698Z" }, + { url = "https://files.pythonhosted.org/packages/df/b4/898991cececbe171e67142c31905510203649569d9817848f47c4177ee42/watchfiles-1.0.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:47eb32ef8c729dbc4f4273baece89398a4d4b5d21a1493efea77a17059f4df8a", size = 450680, upload_time = "2025-01-10T13:03:11.653Z" }, + { url = "https://files.pythonhosted.org/packages/58/f7/d4aa3000e812cfb5e5c2c6c0a3ec9d0a46a42489a8727edd160631c4e210/watchfiles-1.0.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:076f293100db3b0b634514aa0d294b941daa85fc777f9c698adb1009e5aca0b1", size = 455923, upload_time = "2025-01-10T13:03:14.603Z" }, + { url = "https://files.pythonhosted.org/packages/dd/95/7e2e4c6aba1b02fb5c76d2f6a450b85215921ec5f8f7ad5efd075369563f/watchfiles-1.0.4-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1eacd91daeb5158c598fe22d7ce66d60878b6294a86477a4715154990394c9b3", size = 482339, upload_time = "2025-01-10T13:03:17.737Z" }, + { url = "https://files.pythonhosted.org/packages/bb/67/4265b0fabcc2ef2c9e3e8802ba7908cf718a357ebfb49c72e53787156a48/watchfiles-1.0.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:13c2ce7b72026cfbca120d652f02c7750f33b4c9395d79c9790b27f014c8a5a2", size = 519908, upload_time = "2025-01-10T13:03:20.644Z" }, + { url = "https://files.pythonhosted.org/packages/0d/96/b57802d5f8164bdf070befb4fd3dec4edba5a364ec0670965a97eb8098ce/watchfiles-1.0.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:90192cdc15ab7254caa7765a98132a5a41471cf739513cc9bcf7d2ffcc0ec7b2", size = 501410, upload_time = "2025-01-10T13:03:23.719Z" }, + { url = "https://files.pythonhosted.org/packages/8b/18/6db0de4e8911ba14e31853201b40c0fa9fea5ecf3feb86b0ad58f006dfc3/watchfiles-1.0.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:278aaa395f405972e9f523bd786ed59dfb61e4b827856be46a42130605fd0899", size = 452876, upload_time = "2025-01-10T13:03:26.795Z" }, + { url = "https://files.pythonhosted.org/packages/df/df/092a961815edf723a38ba2638c49491365943919c3526cc9cf82c42786a6/watchfiles-1.0.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a462490e75e466edbb9fc4cd679b62187153b3ba804868452ef0577ec958f5ff", size = 615353, upload_time = "2025-01-10T13:03:29.819Z" }, + { url = "https://files.pythonhosted.org/packages/f3/cf/b85fe645de4ff82f3f436c5e9032379fce37c303f6396a18f9726cc34519/watchfiles-1.0.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8d0d0630930f5cd5af929040e0778cf676a46775753e442a3f60511f2409f48f", size = 613187, upload_time = "2025-01-10T13:03:32.926Z" }, + { url = "https://files.pythonhosted.org/packages/f6/d4/a9fea27aef4dd69689bc3556718c1157a7accb72aa035ece87c1fa8483b5/watchfiles-1.0.4-cp310-cp310-win32.whl", hash = "sha256:cc27a65069bcabac4552f34fd2dce923ce3fcde0721a16e4fb1b466d63ec831f", size = 270799, upload_time = "2025-01-10T13:03:34.79Z" }, + { url = "https://files.pythonhosted.org/packages/df/02/dbe9d4439f15dd4ad0720b6e039bde9d66d1f830331f34c18eb70fa6608e/watchfiles-1.0.4-cp310-cp310-win_amd64.whl", hash = "sha256:8b1f135238e75d075359cf506b27bf3f4ca12029c47d3e769d8593a2024ce161", size = 284145, upload_time = "2025-01-10T13:03:36.432Z" }, + { url = "https://files.pythonhosted.org/packages/0f/bb/8461adc4b1fed009546fb797fc0d5698dcfe5e289cb37e1b8f16a93cdc30/watchfiles-1.0.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:2a9f93f8439639dc244c4d2902abe35b0279102bca7bbcf119af964f51d53c19", size = 394869, upload_time = "2025-01-10T13:03:37.906Z" }, + { url = "https://files.pythonhosted.org/packages/55/88/9ebf36b3547176d1709c320de78c1fa3263a46be31b5b1267571d9102686/watchfiles-1.0.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9eea33ad8c418847dd296e61eb683cae1c63329b6d854aefcd412e12d94ee235", size = 384905, upload_time = "2025-01-10T13:03:39.562Z" }, + { url = "https://files.pythonhosted.org/packages/03/8a/04335ce23ef78d8c69f0913e8b20cf7d9233e3986543aeef95ef2d6e43d2/watchfiles-1.0.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:31f1a379c9dcbb3f09cf6be1b7e83b67c0e9faabed0471556d9438a4a4e14202", size = 449944, upload_time = "2025-01-10T13:03:42.483Z" }, + { url = "https://files.pythonhosted.org/packages/17/4e/c8d5dcd14fe637f4633616dabea8a4af0a10142dccf3b43e0f081ba81ab4/watchfiles-1.0.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ab594e75644421ae0a2484554832ca5895f8cab5ab62de30a1a57db460ce06c6", size = 456020, upload_time = "2025-01-10T13:03:45.449Z" }, + { url = "https://files.pythonhosted.org/packages/5e/74/3e91e09e1861dd7fbb1190ce7bd786700dc0fbc2ccd33bb9fff5de039229/watchfiles-1.0.4-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fc2eb5d14a8e0d5df7b36288979176fbb39672d45184fc4b1c004d7c3ce29317", size = 482983, upload_time = "2025-01-10T13:03:47.082Z" }, + { url = "https://files.pythonhosted.org/packages/a1/3d/e64de2d1ce4eb6a574fd78ce3a28c279da263be9ef3cfcab6f708df192f2/watchfiles-1.0.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f68d8e9d5a321163ddacebe97091000955a1b74cd43724e346056030b0bacee", size = 520320, upload_time = "2025-01-10T13:03:48.976Z" }, + { url = "https://files.pythonhosted.org/packages/2c/bd/52235f7063b57240c66a991696ed27e2a18bd6fcec8a1ea5a040b70d0611/watchfiles-1.0.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f9ce064e81fe79faa925ff03b9f4c1a98b0bbb4a1b8c1b015afa93030cb21a49", size = 500988, upload_time = "2025-01-10T13:03:50.543Z" }, + { url = "https://files.pythonhosted.org/packages/3a/b0/ff04194141a5fe650c150400dd9e42667916bc0f52426e2e174d779b8a74/watchfiles-1.0.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b77d5622ac5cc91d21ae9c2b284b5d5c51085a0bdb7b518dba263d0af006132c", size = 452573, upload_time = "2025-01-10T13:03:53.918Z" }, + { url = "https://files.pythonhosted.org/packages/3d/9d/966164332c5a178444ae6d165082d4f351bd56afd9c3ec828eecbf190e6a/watchfiles-1.0.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1941b4e39de9b38b868a69b911df5e89dc43767feeda667b40ae032522b9b5f1", size = 615114, upload_time = "2025-01-10T13:03:56.881Z" }, + { url = "https://files.pythonhosted.org/packages/94/df/f569ae4c1877f96ad4086c153a8eee5a19a3b519487bf5c9454a3438c341/watchfiles-1.0.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4f8c4998506241dedf59613082d1c18b836e26ef2a4caecad0ec41e2a15e4226", size = 613076, upload_time = "2025-01-10T13:04:00.751Z" }, + { url = "https://files.pythonhosted.org/packages/15/ae/8ce5f29e65d5fa5790e3c80c289819c55e12be2e1b9f5b6a0e55e169b97d/watchfiles-1.0.4-cp311-cp311-win32.whl", hash = "sha256:4ebbeca9360c830766b9f0df3640b791be569d988f4be6c06d6fae41f187f105", size = 271013, upload_time = "2025-01-10T13:04:08.455Z" }, + { url = "https://files.pythonhosted.org/packages/a4/c6/79dc4a7c598a978e5fafa135090aaf7bbb03b8dec7bada437dfbe578e7ed/watchfiles-1.0.4-cp311-cp311-win_amd64.whl", hash = "sha256:05d341c71f3d7098920f8551d4df47f7b57ac5b8dad56558064c3431bdfc0b74", size = 284229, upload_time = "2025-01-10T13:04:11.283Z" }, + { url = "https://files.pythonhosted.org/packages/37/3d/928633723211753f3500bfb138434f080363b87a1b08ca188b1ce54d1e05/watchfiles-1.0.4-cp311-cp311-win_arm64.whl", hash = "sha256:32b026a6ab64245b584acf4931fe21842374da82372d5c039cba6bf99ef722f3", size = 276824, upload_time = "2025-01-10T13:04:14.202Z" }, + { url = "https://files.pythonhosted.org/packages/5b/1a/8f4d9a1461709756ace48c98f07772bc6d4519b1e48b5fa24a4061216256/watchfiles-1.0.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:229e6ec880eca20e0ba2f7e2249c85bae1999d330161f45c78d160832e026ee2", size = 391345, upload_time = "2025-01-10T13:04:17.001Z" }, + { url = "https://files.pythonhosted.org/packages/bc/d2/6750b7b3527b1cdaa33731438432e7238a6c6c40a9924049e4cebfa40805/watchfiles-1.0.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5717021b199e8353782dce03bd8a8f64438832b84e2885c4a645f9723bf656d9", size = 381515, upload_time = "2025-01-10T13:04:21.27Z" }, + { url = "https://files.pythonhosted.org/packages/4e/17/80500e42363deef1e4b4818729ed939aaddc56f82f4e72b2508729dd3c6b/watchfiles-1.0.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0799ae68dfa95136dde7c472525700bd48777875a4abb2ee454e3ab18e9fc712", size = 449767, upload_time = "2025-01-10T13:04:23.745Z" }, + { url = "https://files.pythonhosted.org/packages/10/37/1427fa4cfa09adbe04b1e97bced19a29a3462cc64c78630787b613a23f18/watchfiles-1.0.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:43b168bba889886b62edb0397cab5b6490ffb656ee2fcb22dec8bfeb371a9e12", size = 455677, upload_time = "2025-01-10T13:04:27.618Z" }, + { url = "https://files.pythonhosted.org/packages/c5/7a/39e9397f3a19cb549a7d380412fd9e507d4854eddc0700bfad10ef6d4dba/watchfiles-1.0.4-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fb2c46e275fbb9f0c92e7654b231543c7bbfa1df07cdc4b99fa73bedfde5c844", size = 482219, upload_time = "2025-01-10T13:04:29.265Z" }, + { url = "https://files.pythonhosted.org/packages/45/2d/7113931a77e2ea4436cad0c1690c09a40a7f31d366f79c6f0a5bc7a4f6d5/watchfiles-1.0.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:857f5fc3aa027ff5e57047da93f96e908a35fe602d24f5e5d8ce64bf1f2fc733", size = 518830, upload_time = "2025-01-10T13:04:31.957Z" }, + { url = "https://files.pythonhosted.org/packages/f9/1b/50733b1980fa81ef3c70388a546481ae5fa4c2080040100cd7bf3bf7b321/watchfiles-1.0.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55ccfd27c497b228581e2838d4386301227fc0cb47f5a12923ec2fe4f97b95af", size = 497997, upload_time = "2025-01-10T13:04:33.938Z" }, + { url = "https://files.pythonhosted.org/packages/2b/b4/9396cc61b948ef18943e7c85ecfa64cf940c88977d882da57147f62b34b1/watchfiles-1.0.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c11ea22304d17d4385067588123658e9f23159225a27b983f343fcffc3e796a", size = 452249, upload_time = "2025-01-10T13:04:35.559Z" }, + { url = "https://files.pythonhosted.org/packages/fb/69/0c65a5a29e057ad0dc691c2fa6c23b2983c7dabaa190ba553b29ac84c3cc/watchfiles-1.0.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:74cb3ca19a740be4caa18f238298b9d472c850f7b2ed89f396c00a4c97e2d9ff", size = 614412, upload_time = "2025-01-10T13:04:37.061Z" }, + { url = "https://files.pythonhosted.org/packages/7f/b9/319fcba6eba5fad34327d7ce16a6b163b39741016b1996f4a3c96b8dd0e1/watchfiles-1.0.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c7cce76c138a91e720d1df54014a047e680b652336e1b73b8e3ff3158e05061e", size = 611982, upload_time = "2025-01-10T13:04:38.995Z" }, + { url = "https://files.pythonhosted.org/packages/f1/47/143c92418e30cb9348a4387bfa149c8e0e404a7c5b0585d46d2f7031b4b9/watchfiles-1.0.4-cp312-cp312-win32.whl", hash = "sha256:b045c800d55bc7e2cadd47f45a97c7b29f70f08a7c2fa13241905010a5493f94", size = 271822, upload_time = "2025-01-10T13:04:40.516Z" }, + { url = "https://files.pythonhosted.org/packages/ea/94/b0165481bff99a64b29e46e07ac2e0df9f7a957ef13bec4ceab8515f44e3/watchfiles-1.0.4-cp312-cp312-win_amd64.whl", hash = "sha256:c2acfa49dd0ad0bf2a9c0bb9a985af02e89345a7189be1efc6baa085e0f72d7c", size = 285441, upload_time = "2025-01-10T13:04:42.853Z" }, + { url = "https://files.pythonhosted.org/packages/11/de/09fe56317d582742d7ca8c2ca7b52a85927ebb50678d9b0fa8194658f536/watchfiles-1.0.4-cp312-cp312-win_arm64.whl", hash = "sha256:22bb55a7c9e564e763ea06c7acea24fc5d2ee5dfc5dafc5cfbedfe58505e9f90", size = 277141, upload_time = "2025-01-10T13:04:45.914Z" }, + { url = "https://files.pythonhosted.org/packages/08/98/f03efabec64b5b1fa58c0daab25c68ef815b0f320e54adcacd0d6847c339/watchfiles-1.0.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:8012bd820c380c3d3db8435e8cf7592260257b378b649154a7948a663b5f84e9", size = 390954, upload_time = "2025-01-10T13:04:47.458Z" }, + { url = "https://files.pythonhosted.org/packages/16/09/4dd49ba0a32a45813debe5fb3897955541351ee8142f586303b271a02b40/watchfiles-1.0.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:aa216f87594f951c17511efe5912808dfcc4befa464ab17c98d387830ce07b60", size = 381133, upload_time = "2025-01-10T13:04:48.977Z" }, + { url = "https://files.pythonhosted.org/packages/76/59/5aa6fc93553cd8d8ee75c6247763d77c02631aed21551a97d94998bf1dae/watchfiles-1.0.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62c9953cf85529c05b24705639ffa390f78c26449e15ec34d5339e8108c7c407", size = 449516, upload_time = "2025-01-10T13:04:50.653Z" }, + { url = "https://files.pythonhosted.org/packages/4c/aa/df4b6fe14b6317290b91335b23c96b488d365d65549587434817e06895ea/watchfiles-1.0.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7cf684aa9bba4cd95ecb62c822a56de54e3ae0598c1a7f2065d51e24637a3c5d", size = 454820, upload_time = "2025-01-10T13:04:52.312Z" }, + { url = "https://files.pythonhosted.org/packages/5e/71/185f8672f1094ce48af33252c73e39b48be93b761273872d9312087245f6/watchfiles-1.0.4-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f44a39aee3cbb9b825285ff979ab887a25c5d336e5ec3574f1506a4671556a8d", size = 481550, upload_time = "2025-01-10T13:04:54.007Z" }, + { url = "https://files.pythonhosted.org/packages/85/d7/50ebba2c426ef1a5cb17f02158222911a2e005d401caf5d911bfca58f4c4/watchfiles-1.0.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a38320582736922be8c865d46520c043bff350956dfc9fbaee3b2df4e1740a4b", size = 518647, upload_time = "2025-01-10T13:04:56.008Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/4c009342e393c545d68987e8010b937f72f47937731225b2b29b7231428f/watchfiles-1.0.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39f4914548b818540ef21fd22447a63e7be6e24b43a70f7642d21f1e73371590", size = 497547, upload_time = "2025-01-10T13:04:58.087Z" }, + { url = "https://files.pythonhosted.org/packages/0f/7c/1cf50b35412d5c72d63b2bf9a4fffee2e1549a245924960dd087eb6a6de4/watchfiles-1.0.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f12969a3765909cf5dc1e50b2436eb2c0e676a3c75773ab8cc3aa6175c16e902", size = 452179, upload_time = "2025-01-10T13:05:01.175Z" }, + { url = "https://files.pythonhosted.org/packages/d6/a9/3db1410e1c1413735a9a472380e4f431ad9a9e81711cda2aaf02b7f62693/watchfiles-1.0.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:0986902677a1a5e6212d0c49b319aad9cc48da4bd967f86a11bde96ad9676ca1", size = 614125, upload_time = "2025-01-10T13:05:03.086Z" }, + { url = "https://files.pythonhosted.org/packages/f2/e1/0025d365cf6248c4d1ee4c3d2e3d373bdd3f6aff78ba4298f97b4fad2740/watchfiles-1.0.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:308ac265c56f936636e3b0e3f59e059a40003c655228c131e1ad439957592303", size = 611911, upload_time = "2025-01-10T13:05:04.947Z" }, + { url = "https://files.pythonhosted.org/packages/55/55/035838277d8c98fc8c917ac9beeb0cd6c59d675dc2421df5f9fcf44a0070/watchfiles-1.0.4-cp313-cp313-win32.whl", hash = "sha256:aee397456a29b492c20fda2d8961e1ffb266223625346ace14e4b6d861ba9c80", size = 271152, upload_time = "2025-01-10T13:05:09.507Z" }, + { url = "https://files.pythonhosted.org/packages/f0/e5/96b8e55271685ddbadc50ce8bc53aa2dff278fb7ac4c2e473df890def2dc/watchfiles-1.0.4-cp313-cp313-win_amd64.whl", hash = "sha256:d6097538b0ae5c1b88c3b55afa245a66793a8fec7ada6755322e465fb1a0e8cc", size = 285216, upload_time = "2025-01-10T13:05:11.107Z" }, + { url = "https://files.pythonhosted.org/packages/15/81/54484fc2fa715abe79694b975692af963f0878fb9d72b8251aa542bf3f10/watchfiles-1.0.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:d3452c1ec703aa1c61e15dfe9d482543e4145e7c45a6b8566978fbb044265a21", size = 394967, upload_time = "2025-01-10T13:05:12.692Z" }, + { url = "https://files.pythonhosted.org/packages/14/b3/557f0cd90add86586fe3deeebd11e8299db6bc3452b44a534f844c6ab831/watchfiles-1.0.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7b75fee5a16826cf5c46fe1c63116e4a156924d668c38b013e6276f2582230f0", size = 384707, upload_time = "2025-01-10T13:05:14.395Z" }, + { url = "https://files.pythonhosted.org/packages/03/a3/34638e1bffcb85a405e7b005e30bb211fd9be2ab2cb1847f2ceb81bef27b/watchfiles-1.0.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e997802d78cdb02623b5941830ab06f8860038faf344f0d288d325cc9c5d2ff", size = 450442, upload_time = "2025-01-10T13:05:16.059Z" }, + { url = "https://files.pythonhosted.org/packages/8f/9f/6a97460dd11a606003d634c7158d9fea8517e98daffc6f56d0f5fde2e86a/watchfiles-1.0.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e0611d244ce94d83f5b9aff441ad196c6e21b55f77f3c47608dcf651efe54c4a", size = 455959, upload_time = "2025-01-10T13:05:17.816Z" }, + { url = "https://files.pythonhosted.org/packages/9d/bb/e0648c6364e4d37ec692bc3f0c77507d17d8bb8f75689148819142010bbf/watchfiles-1.0.4-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9745a4210b59e218ce64c91deb599ae8775c8a9da4e95fb2ee6fe745fc87d01a", size = 483187, upload_time = "2025-01-10T13:05:19.554Z" }, + { url = "https://files.pythonhosted.org/packages/dd/ad/d9290586a25288a81dfa8ad6329cf1de32aa1a9798ace45259eb95dcfb37/watchfiles-1.0.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4810ea2ae622add560f4aa50c92fef975e475f7ac4900ce5ff5547b2434642d8", size = 519733, upload_time = "2025-01-10T13:05:21.295Z" }, + { url = "https://files.pythonhosted.org/packages/4e/a9/150c1666825cc9637093f8cae7fc6f53b3296311ab8bd65f1389acb717cb/watchfiles-1.0.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:740d103cd01458f22462dedeb5a3382b7f2c57d07ff033fbc9465919e5e1d0f3", size = 502275, upload_time = "2025-01-10T13:05:24.325Z" }, + { url = "https://files.pythonhosted.org/packages/44/dc/5bfd21e20a330aca1706ac44713bc322838061938edf4b53130f97a7b211/watchfiles-1.0.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdbd912a61543a36aef85e34f212e5d2486e7c53ebfdb70d1e0b060cc50dd0bf", size = 452907, upload_time = "2025-01-10T13:05:26.75Z" }, + { url = "https://files.pythonhosted.org/packages/50/fe/8f4fc488f1699f564687b697456eb5c0cb8e2b0b8538150511c234c62094/watchfiles-1.0.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0bc80d91ddaf95f70258cf78c471246846c1986bcc5fd33ccc4a1a67fcb40f9a", size = 615927, upload_time = "2025-01-10T13:05:29.865Z" }, + { url = "https://files.pythonhosted.org/packages/ad/19/2e45f6f6eec89dd97a4d281635e3d73c17e5f692e7432063bdfdf9562c89/watchfiles-1.0.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ab0311bb2ffcd9f74b6c9de2dda1612c13c84b996d032cd74799adb656af4e8b", size = 613435, upload_time = "2025-01-10T13:05:32.64Z" }, + { url = "https://files.pythonhosted.org/packages/91/17/dc5ac62ca377827c24321d68050efc2eaee2ebaf3f21d055bbce2206d309/watchfiles-1.0.4-cp39-cp39-win32.whl", hash = "sha256:02a526ee5b5a09e8168314c905fc545c9bc46509896ed282aeb5a8ba9bd6ca27", size = 270810, upload_time = "2025-01-10T13:05:34.354Z" }, + { url = "https://files.pythonhosted.org/packages/82/2b/dad851342492d538e7ffe72a8c756f747dd147988abb039ac9d6577d2235/watchfiles-1.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:a5ae5706058b27c74bac987d615105da17724172d5aaacc6c362a40599b6de43", size = 284866, upload_time = "2025-01-10T13:05:36.005Z" }, + { url = "https://files.pythonhosted.org/packages/6f/06/175d5ac6b838fb319008c0cd981d7bf289317c510154d411d3584ca2b67b/watchfiles-1.0.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:cdcc92daeae268de1acf5b7befcd6cfffd9a047098199056c72e4623f531de18", size = 396269, upload_time = "2025-01-10T13:05:37.958Z" }, + { url = "https://files.pythonhosted.org/packages/86/ee/5db93b0b57dc0587abdbac4149296ee73275f615d790a82cb5598af0557f/watchfiles-1.0.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d8d3d9203705b5797f0af7e7e5baa17c8588030aaadb7f6a86107b7247303817", size = 386010, upload_time = "2025-01-10T13:05:39.45Z" }, + { url = "https://files.pythonhosted.org/packages/75/61/fe0dc5fedf152bfc085a53711f740701f6bdb8ab6b5c950402b681d4858b/watchfiles-1.0.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bdef5a1be32d0b07dcea3318a0be95d42c98ece24177820226b56276e06b63b0", size = 450913, upload_time = "2025-01-10T13:05:41.981Z" }, + { url = "https://files.pythonhosted.org/packages/9f/dd/3c7731af3baf1a9957afc643d176f94480921a690ec3237c9f9d11301c08/watchfiles-1.0.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:342622287b5604ddf0ed2d085f3a589099c9ae8b7331df3ae9845571586c4f3d", size = 453474, upload_time = "2025-01-10T13:05:45.968Z" }, + { url = "https://files.pythonhosted.org/packages/6b/b4/c3998f54c91a35cee60ee6d3a855a069c5dff2bae6865147a46e9090dccd/watchfiles-1.0.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:9fe37a2de80aa785d340f2980276b17ef697ab8db6019b07ee4fd28a8359d2f3", size = 395565, upload_time = "2025-01-10T13:05:47.641Z" }, + { url = "https://files.pythonhosted.org/packages/3f/05/ac1a4d235beb9ddfb8ac26ce93a00ba6bd1b1b43051ef12d7da957b4a9d1/watchfiles-1.0.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:9d1ef56b56ed7e8f312c934436dea93bfa3e7368adfcf3df4c0da6d4de959a1e", size = 385406, upload_time = "2025-01-10T13:05:49.254Z" }, + { url = "https://files.pythonhosted.org/packages/4c/ea/36532e7d86525f4e52a10efed182abf33efb106a93d49f5fbc994b256bcd/watchfiles-1.0.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95b42cac65beae3a362629950c444077d1b44f1790ea2772beaea95451c086bb", size = 450424, upload_time = "2025-01-10T13:05:51.147Z" }, + { url = "https://files.pythonhosted.org/packages/7a/e9/3cbcf4d70cd0b6d3f30631deae1bf37cc0be39887ca327a44462fe546bf5/watchfiles-1.0.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e0227b8ed9074c6172cf55d85b5670199c99ab11fd27d2c473aa30aec67ee42", size = 452488, upload_time = "2025-01-10T13:05:52.844Z" }, ] [[package]] name = "websockets" version = "15.0.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016 } +sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016, upload_time = "2025-03-05T20:03:41.606Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/da/6462a9f510c0c49837bbc9345aca92d767a56c1fb2939e1579df1e1cdcf7/websockets-15.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d63efaa0cd96cf0c5fe4d581521d9fa87744540d4bc999ae6e08595a1014b45b", size = 175423 }, - { url = "https://files.pythonhosted.org/packages/1c/9f/9d11c1a4eb046a9e106483b9ff69bce7ac880443f00e5ce64261b47b07e7/websockets-15.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac60e3b188ec7574cb761b08d50fcedf9d77f1530352db4eef1707fe9dee7205", size = 173080 }, - { url = "https://files.pythonhosted.org/packages/d5/4f/b462242432d93ea45f297b6179c7333dd0402b855a912a04e7fc61c0d71f/websockets-15.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5756779642579d902eed757b21b0164cd6fe338506a8083eb58af5c372e39d9a", size = 173329 }, - { url = "https://files.pythonhosted.org/packages/6e/0c/6afa1f4644d7ed50284ac59cc70ef8abd44ccf7d45850d989ea7310538d0/websockets-15.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fdfe3e2a29e4db3659dbd5bbf04560cea53dd9610273917799f1cde46aa725e", size = 182312 }, - { url = "https://files.pythonhosted.org/packages/dd/d4/ffc8bd1350b229ca7a4db2a3e1c482cf87cea1baccd0ef3e72bc720caeec/websockets-15.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c2529b320eb9e35af0fa3016c187dffb84a3ecc572bcee7c3ce302bfeba52bf", size = 181319 }, - { url = "https://files.pythonhosted.org/packages/97/3a/5323a6bb94917af13bbb34009fac01e55c51dfde354f63692bf2533ffbc2/websockets-15.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac1e5c9054fe23226fb11e05a6e630837f074174c4c2f0fe442996112a6de4fb", size = 181631 }, - { url = "https://files.pythonhosted.org/packages/a6/cc/1aeb0f7cee59ef065724041bb7ed667b6ab1eeffe5141696cccec2687b66/websockets-15.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5df592cd503496351d6dc14f7cdad49f268d8e618f80dce0cd5a36b93c3fc08d", size = 182016 }, - { url = "https://files.pythonhosted.org/packages/79/f9/c86f8f7af208e4161a7f7e02774e9d0a81c632ae76db2ff22549e1718a51/websockets-15.0.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0a34631031a8f05657e8e90903e656959234f3a04552259458aac0b0f9ae6fd9", size = 181426 }, - { url = "https://files.pythonhosted.org/packages/c7/b9/828b0bc6753db905b91df6ae477c0b14a141090df64fb17f8a9d7e3516cf/websockets-15.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3d00075aa65772e7ce9e990cab3ff1de702aa09be3940d1dc88d5abf1ab8a09c", size = 181360 }, - { url = "https://files.pythonhosted.org/packages/89/fb/250f5533ec468ba6327055b7d98b9df056fb1ce623b8b6aaafb30b55d02e/websockets-15.0.1-cp310-cp310-win32.whl", hash = "sha256:1234d4ef35db82f5446dca8e35a7da7964d02c127b095e172e54397fb6a6c256", size = 176388 }, - { url = "https://files.pythonhosted.org/packages/1c/46/aca7082012768bb98e5608f01658ff3ac8437e563eca41cf068bd5849a5e/websockets-15.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:39c1fec2c11dc8d89bba6b2bf1556af381611a173ac2b511cf7231622058af41", size = 176830 }, - { url = "https://files.pythonhosted.org/packages/9f/32/18fcd5919c293a398db67443acd33fde142f283853076049824fc58e6f75/websockets-15.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:823c248b690b2fd9303ba00c4f66cd5e2d8c3ba4aa968b2779be9532a4dad431", size = 175423 }, - { url = "https://files.pythonhosted.org/packages/76/70/ba1ad96b07869275ef42e2ce21f07a5b0148936688c2baf7e4a1f60d5058/websockets-15.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678999709e68425ae2593acf2e3ebcbcf2e69885a5ee78f9eb80e6e371f1bf57", size = 173082 }, - { url = "https://files.pythonhosted.org/packages/86/f2/10b55821dd40eb696ce4704a87d57774696f9451108cff0d2824c97e0f97/websockets-15.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d50fd1ee42388dcfb2b3676132c78116490976f1300da28eb629272d5d93e905", size = 173330 }, - { url = "https://files.pythonhosted.org/packages/a5/90/1c37ae8b8a113d3daf1065222b6af61cc44102da95388ac0018fcb7d93d9/websockets-15.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d99e5546bf73dbad5bf3547174cd6cb8ba7273062a23808ffea025ecb1cf8562", size = 182878 }, - { url = "https://files.pythonhosted.org/packages/8e/8d/96e8e288b2a41dffafb78e8904ea7367ee4f891dafc2ab8d87e2124cb3d3/websockets-15.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66dd88c918e3287efc22409d426c8f729688d89a0c587c88971a0faa2c2f3792", size = 181883 }, - { url = "https://files.pythonhosted.org/packages/93/1f/5d6dbf551766308f6f50f8baf8e9860be6182911e8106da7a7f73785f4c4/websockets-15.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8dd8327c795b3e3f219760fa603dcae1dcc148172290a8ab15158cf85a953413", size = 182252 }, - { url = "https://files.pythonhosted.org/packages/d4/78/2d4fed9123e6620cbf1706c0de8a1632e1a28e7774d94346d7de1bba2ca3/websockets-15.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8fdc51055e6ff4adeb88d58a11042ec9a5eae317a0a53d12c062c8a8865909e8", size = 182521 }, - { url = "https://files.pythonhosted.org/packages/e7/3b/66d4c1b444dd1a9823c4a81f50231b921bab54eee2f69e70319b4e21f1ca/websockets-15.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:693f0192126df6c2327cce3baa7c06f2a117575e32ab2308f7f8216c29d9e2e3", size = 181958 }, - { url = "https://files.pythonhosted.org/packages/08/ff/e9eed2ee5fed6f76fdd6032ca5cd38c57ca9661430bb3d5fb2872dc8703c/websockets-15.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:54479983bd5fb469c38f2f5c7e3a24f9a4e70594cd68cd1fa6b9340dadaff7cf", size = 181918 }, - { url = "https://files.pythonhosted.org/packages/d8/75/994634a49b7e12532be6a42103597b71098fd25900f7437d6055ed39930a/websockets-15.0.1-cp311-cp311-win32.whl", hash = "sha256:16b6c1b3e57799b9d38427dda63edcbe4926352c47cf88588c0be4ace18dac85", size = 176388 }, - { url = "https://files.pythonhosted.org/packages/98/93/e36c73f78400a65f5e236cd376713c34182e6663f6889cd45a4a04d8f203/websockets-15.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:27ccee0071a0e75d22cb35849b1db43f2ecd3e161041ac1ee9d2352ddf72f065", size = 176828 }, - { url = "https://files.pythonhosted.org/packages/51/6b/4545a0d843594f5d0771e86463606a3988b5a09ca5123136f8a76580dd63/websockets-15.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3e90baa811a5d73f3ca0bcbf32064d663ed81318ab225ee4f427ad4e26e5aff3", size = 175437 }, - { url = "https://files.pythonhosted.org/packages/f4/71/809a0f5f6a06522af902e0f2ea2757f71ead94610010cf570ab5c98e99ed/websockets-15.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:592f1a9fe869c778694f0aa806ba0374e97648ab57936f092fd9d87f8bc03665", size = 173096 }, - { url = "https://files.pythonhosted.org/packages/3d/69/1a681dd6f02180916f116894181eab8b2e25b31e484c5d0eae637ec01f7c/websockets-15.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0701bc3cfcb9164d04a14b149fd74be7347a530ad3bbf15ab2c678a2cd3dd9a2", size = 173332 }, - { url = "https://files.pythonhosted.org/packages/a6/02/0073b3952f5bce97eafbb35757f8d0d54812b6174ed8dd952aa08429bcc3/websockets-15.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8b56bdcdb4505c8078cb6c7157d9811a85790f2f2b3632c7d1462ab5783d215", size = 183152 }, - { url = "https://files.pythonhosted.org/packages/74/45/c205c8480eafd114b428284840da0b1be9ffd0e4f87338dc95dc6ff961a1/websockets-15.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0af68c55afbd5f07986df82831c7bff04846928ea8d1fd7f30052638788bc9b5", size = 182096 }, - { url = "https://files.pythonhosted.org/packages/14/8f/aa61f528fba38578ec553c145857a181384c72b98156f858ca5c8e82d9d3/websockets-15.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64dee438fed052b52e4f98f76c5790513235efaa1ef7f3f2192c392cd7c91b65", size = 182523 }, - { url = "https://files.pythonhosted.org/packages/ec/6d/0267396610add5bc0d0d3e77f546d4cd287200804fe02323797de77dbce9/websockets-15.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d5f6b181bb38171a8ad1d6aa58a67a6aa9d4b38d0f8c5f496b9e42561dfc62fe", size = 182790 }, - { url = "https://files.pythonhosted.org/packages/02/05/c68c5adbf679cf610ae2f74a9b871ae84564462955d991178f95a1ddb7dd/websockets-15.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5d54b09eba2bada6011aea5375542a157637b91029687eb4fdb2dab11059c1b4", size = 182165 }, - { url = "https://files.pythonhosted.org/packages/29/93/bb672df7b2f5faac89761cb5fa34f5cec45a4026c383a4b5761c6cea5c16/websockets-15.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3be571a8b5afed347da347bfcf27ba12b069d9d7f42cb8c7028b5e98bbb12597", size = 182160 }, - { url = "https://files.pythonhosted.org/packages/ff/83/de1f7709376dc3ca9b7eeb4b9a07b4526b14876b6d372a4dc62312bebee0/websockets-15.0.1-cp312-cp312-win32.whl", hash = "sha256:c338ffa0520bdb12fbc527265235639fb76e7bc7faafbb93f6ba80d9c06578a9", size = 176395 }, - { url = "https://files.pythonhosted.org/packages/7d/71/abf2ebc3bbfa40f391ce1428c7168fb20582d0ff57019b69ea20fa698043/websockets-15.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcd5cf9e305d7b8338754470cf69cf81f420459dbae8a3b40cee57417f4614a7", size = 176841 }, - { url = "https://files.pythonhosted.org/packages/cb/9f/51f0cf64471a9d2b4d0fc6c534f323b664e7095640c34562f5182e5a7195/websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931", size = 175440 }, - { url = "https://files.pythonhosted.org/packages/8a/05/aa116ec9943c718905997412c5989f7ed671bc0188ee2ba89520e8765d7b/websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675", size = 173098 }, - { url = "https://files.pythonhosted.org/packages/ff/0b/33cef55ff24f2d92924923c99926dcce78e7bd922d649467f0eda8368923/websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151", size = 173329 }, - { url = "https://files.pythonhosted.org/packages/31/1d/063b25dcc01faa8fada1469bdf769de3768b7044eac9d41f734fd7b6ad6d/websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22", size = 183111 }, - { url = "https://files.pythonhosted.org/packages/93/53/9a87ee494a51bf63e4ec9241c1ccc4f7c2f45fff85d5bde2ff74fcb68b9e/websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f", size = 182054 }, - { url = "https://files.pythonhosted.org/packages/ff/b2/83a6ddf56cdcbad4e3d841fcc55d6ba7d19aeb89c50f24dd7e859ec0805f/websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8", size = 182496 }, - { url = "https://files.pythonhosted.org/packages/98/41/e7038944ed0abf34c45aa4635ba28136f06052e08fc2168520bb8b25149f/websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375", size = 182829 }, - { url = "https://files.pythonhosted.org/packages/e0/17/de15b6158680c7623c6ef0db361da965ab25d813ae54fcfeae2e5b9ef910/websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d", size = 182217 }, - { url = "https://files.pythonhosted.org/packages/33/2b/1f168cb6041853eef0362fb9554c3824367c5560cbdaad89ac40f8c2edfc/websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4", size = 182195 }, - { url = "https://files.pythonhosted.org/packages/86/eb/20b6cdf273913d0ad05a6a14aed4b9a85591c18a987a3d47f20fa13dcc47/websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa", size = 176393 }, - { url = "https://files.pythonhosted.org/packages/1b/6c/c65773d6cab416a64d191d6ee8a8b1c68a09970ea6909d16965d26bfed1e/websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561", size = 176837 }, - { url = "https://files.pythonhosted.org/packages/36/db/3fff0bcbe339a6fa6a3b9e3fbc2bfb321ec2f4cd233692272c5a8d6cf801/websockets-15.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5f4c04ead5aed67c8a1a20491d54cdfba5884507a48dd798ecaf13c74c4489f5", size = 175424 }, - { url = "https://files.pythonhosted.org/packages/46/e6/519054c2f477def4165b0ec060ad664ed174e140b0d1cbb9fafa4a54f6db/websockets-15.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:abdc0c6c8c648b4805c5eacd131910d2a7f6455dfd3becab248ef108e89ab16a", size = 173077 }, - { url = "https://files.pythonhosted.org/packages/1a/21/c0712e382df64c93a0d16449ecbf87b647163485ca1cc3f6cbadb36d2b03/websockets-15.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a625e06551975f4b7ea7102bc43895b90742746797e2e14b70ed61c43a90f09b", size = 173324 }, - { url = "https://files.pythonhosted.org/packages/1c/cb/51ba82e59b3a664df54beed8ad95517c1b4dc1a913730e7a7db778f21291/websockets-15.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d591f8de75824cbb7acad4e05d2d710484f15f29d4a915092675ad3456f11770", size = 182094 }, - { url = "https://files.pythonhosted.org/packages/fb/0f/bf3788c03fec679bcdaef787518dbe60d12fe5615a544a6d4cf82f045193/websockets-15.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:47819cea040f31d670cc8d324bb6435c6f133b8c7a19ec3d61634e62f8d8f9eb", size = 181094 }, - { url = "https://files.pythonhosted.org/packages/5e/da/9fb8c21edbc719b66763a571afbaf206cb6d3736d28255a46fc2fe20f902/websockets-15.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac017dd64572e5c3bd01939121e4d16cf30e5d7e110a119399cf3133b63ad054", size = 181397 }, - { url = "https://files.pythonhosted.org/packages/2e/65/65f379525a2719e91d9d90c38fe8b8bc62bd3c702ac651b7278609b696c4/websockets-15.0.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4a9fac8e469d04ce6c25bb2610dc535235bd4aa14996b4e6dbebf5e007eba5ee", size = 181794 }, - { url = "https://files.pythonhosted.org/packages/d9/26/31ac2d08f8e9304d81a1a7ed2851c0300f636019a57cbaa91342015c72cc/websockets-15.0.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:363c6f671b761efcb30608d24925a382497c12c506b51661883c3e22337265ed", size = 181194 }, - { url = "https://files.pythonhosted.org/packages/98/72/1090de20d6c91994cd4b357c3f75a4f25ee231b63e03adea89671cc12a3f/websockets-15.0.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2034693ad3097d5355bfdacfffcbd3ef5694f9718ab7f29c29689a9eae841880", size = 181164 }, - { url = "https://files.pythonhosted.org/packages/2d/37/098f2e1c103ae8ed79b0e77f08d83b0ec0b241cf4b7f2f10edd0126472e1/websockets-15.0.1-cp39-cp39-win32.whl", hash = "sha256:3b1ac0d3e594bf121308112697cf4b32be538fb1444468fb0a6ae4feebc83411", size = 176381 }, - { url = "https://files.pythonhosted.org/packages/75/8b/a32978a3ab42cebb2ebdd5b05df0696a09f4d436ce69def11893afa301f0/websockets-15.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:b7643a03db5c95c799b89b31c036d5f27eeb4d259c798e878d6937d71832b1e4", size = 176841 }, - { url = "https://files.pythonhosted.org/packages/02/9e/d40f779fa16f74d3468357197af8d6ad07e7c5a27ea1ca74ceb38986f77a/websockets-15.0.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0c9e74d766f2818bb95f84c25be4dea09841ac0f734d1966f415e4edfc4ef1c3", size = 173109 }, - { url = "https://files.pythonhosted.org/packages/bc/cd/5b887b8585a593073fd92f7c23ecd3985cd2c3175025a91b0d69b0551372/websockets-15.0.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1009ee0c7739c08a0cd59de430d6de452a55e42d6b522de7aa15e6f67db0b8e1", size = 173343 }, - { url = "https://files.pythonhosted.org/packages/fe/ae/d34f7556890341e900a95acf4886833646306269f899d58ad62f588bf410/websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76d1f20b1c7a2fa82367e04982e708723ba0e7b8d43aa643d3dcd404d74f1475", size = 174599 }, - { url = "https://files.pythonhosted.org/packages/71/e6/5fd43993a87db364ec60fc1d608273a1a465c0caba69176dd160e197ce42/websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f29d80eb9a9263b8d109135351caf568cc3f80b9928bccde535c235de55c22d9", size = 174207 }, - { url = "https://files.pythonhosted.org/packages/2b/fb/c492d6daa5ec067c2988ac80c61359ace5c4c674c532985ac5a123436cec/websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b359ed09954d7c18bbc1680f380c7301f92c60bf924171629c5db97febb12f04", size = 174155 }, - { url = "https://files.pythonhosted.org/packages/68/a1/dcb68430b1d00b698ae7a7e0194433bce4f07ded185f0ee5fb21e2a2e91e/websockets-15.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:cad21560da69f4ce7658ca2cb83138fb4cf695a2ba3e475e0559e05991aa8122", size = 176884 }, - { url = "https://files.pythonhosted.org/packages/b7/48/4b67623bac4d79beb3a6bb27b803ba75c1bdedc06bd827e465803690a4b2/websockets-15.0.1-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7f493881579c90fc262d9cdbaa05a6b54b3811c2f300766748db79f098db9940", size = 173106 }, - { url = "https://files.pythonhosted.org/packages/ed/f0/adb07514a49fe5728192764e04295be78859e4a537ab8fcc518a3dbb3281/websockets-15.0.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:47b099e1f4fbc95b701b6e85768e1fcdaf1630f3cbe4765fa216596f12310e2e", size = 173339 }, - { url = "https://files.pythonhosted.org/packages/87/28/bd23c6344b18fb43df40d0700f6d3fffcd7cef14a6995b4f976978b52e62/websockets-15.0.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67f2b6de947f8c757db2db9c71527933ad0019737ec374a8a6be9a956786aaf9", size = 174597 }, - { url = "https://files.pythonhosted.org/packages/6d/79/ca288495863d0f23a60f546f0905ae8f3ed467ad87f8b6aceb65f4c013e4/websockets-15.0.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d08eb4c2b7d6c41da6ca0600c077e93f5adcfd979cd777d747e9ee624556da4b", size = 174205 }, - { url = "https://files.pythonhosted.org/packages/04/e4/120ff3180b0872b1fe6637f6f995bcb009fb5c87d597c1fc21456f50c848/websockets-15.0.1-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b826973a4a2ae47ba357e4e82fa44a463b8f168e1ca775ac64521442b19e87f", size = 174150 }, - { url = "https://files.pythonhosted.org/packages/cb/c3/30e2f9c539b8da8b1d76f64012f3b19253271a63413b2d3adb94b143407f/websockets-15.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:21c1fa28a6a7e3cbdc171c694398b6df4744613ce9b36b1a498e816787e28123", size = 176877 }, - { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743 }, + { url = "https://files.pythonhosted.org/packages/1e/da/6462a9f510c0c49837bbc9345aca92d767a56c1fb2939e1579df1e1cdcf7/websockets-15.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d63efaa0cd96cf0c5fe4d581521d9fa87744540d4bc999ae6e08595a1014b45b", size = 175423, upload_time = "2025-03-05T20:01:35.363Z" }, + { url = "https://files.pythonhosted.org/packages/1c/9f/9d11c1a4eb046a9e106483b9ff69bce7ac880443f00e5ce64261b47b07e7/websockets-15.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac60e3b188ec7574cb761b08d50fcedf9d77f1530352db4eef1707fe9dee7205", size = 173080, upload_time = "2025-03-05T20:01:37.304Z" }, + { url = "https://files.pythonhosted.org/packages/d5/4f/b462242432d93ea45f297b6179c7333dd0402b855a912a04e7fc61c0d71f/websockets-15.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5756779642579d902eed757b21b0164cd6fe338506a8083eb58af5c372e39d9a", size = 173329, upload_time = "2025-03-05T20:01:39.668Z" }, + { url = "https://files.pythonhosted.org/packages/6e/0c/6afa1f4644d7ed50284ac59cc70ef8abd44ccf7d45850d989ea7310538d0/websockets-15.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fdfe3e2a29e4db3659dbd5bbf04560cea53dd9610273917799f1cde46aa725e", size = 182312, upload_time = "2025-03-05T20:01:41.815Z" }, + { url = "https://files.pythonhosted.org/packages/dd/d4/ffc8bd1350b229ca7a4db2a3e1c482cf87cea1baccd0ef3e72bc720caeec/websockets-15.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c2529b320eb9e35af0fa3016c187dffb84a3ecc572bcee7c3ce302bfeba52bf", size = 181319, upload_time = "2025-03-05T20:01:43.967Z" }, + { url = "https://files.pythonhosted.org/packages/97/3a/5323a6bb94917af13bbb34009fac01e55c51dfde354f63692bf2533ffbc2/websockets-15.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac1e5c9054fe23226fb11e05a6e630837f074174c4c2f0fe442996112a6de4fb", size = 181631, upload_time = "2025-03-05T20:01:46.104Z" }, + { url = "https://files.pythonhosted.org/packages/a6/cc/1aeb0f7cee59ef065724041bb7ed667b6ab1eeffe5141696cccec2687b66/websockets-15.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5df592cd503496351d6dc14f7cdad49f268d8e618f80dce0cd5a36b93c3fc08d", size = 182016, upload_time = "2025-03-05T20:01:47.603Z" }, + { url = "https://files.pythonhosted.org/packages/79/f9/c86f8f7af208e4161a7f7e02774e9d0a81c632ae76db2ff22549e1718a51/websockets-15.0.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0a34631031a8f05657e8e90903e656959234f3a04552259458aac0b0f9ae6fd9", size = 181426, upload_time = "2025-03-05T20:01:48.949Z" }, + { url = "https://files.pythonhosted.org/packages/c7/b9/828b0bc6753db905b91df6ae477c0b14a141090df64fb17f8a9d7e3516cf/websockets-15.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3d00075aa65772e7ce9e990cab3ff1de702aa09be3940d1dc88d5abf1ab8a09c", size = 181360, upload_time = "2025-03-05T20:01:50.938Z" }, + { url = "https://files.pythonhosted.org/packages/89/fb/250f5533ec468ba6327055b7d98b9df056fb1ce623b8b6aaafb30b55d02e/websockets-15.0.1-cp310-cp310-win32.whl", hash = "sha256:1234d4ef35db82f5446dca8e35a7da7964d02c127b095e172e54397fb6a6c256", size = 176388, upload_time = "2025-03-05T20:01:52.213Z" }, + { url = "https://files.pythonhosted.org/packages/1c/46/aca7082012768bb98e5608f01658ff3ac8437e563eca41cf068bd5849a5e/websockets-15.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:39c1fec2c11dc8d89bba6b2bf1556af381611a173ac2b511cf7231622058af41", size = 176830, upload_time = "2025-03-05T20:01:53.922Z" }, + { url = "https://files.pythonhosted.org/packages/9f/32/18fcd5919c293a398db67443acd33fde142f283853076049824fc58e6f75/websockets-15.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:823c248b690b2fd9303ba00c4f66cd5e2d8c3ba4aa968b2779be9532a4dad431", size = 175423, upload_time = "2025-03-05T20:01:56.276Z" }, + { url = "https://files.pythonhosted.org/packages/76/70/ba1ad96b07869275ef42e2ce21f07a5b0148936688c2baf7e4a1f60d5058/websockets-15.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678999709e68425ae2593acf2e3ebcbcf2e69885a5ee78f9eb80e6e371f1bf57", size = 173082, upload_time = "2025-03-05T20:01:57.563Z" }, + { url = "https://files.pythonhosted.org/packages/86/f2/10b55821dd40eb696ce4704a87d57774696f9451108cff0d2824c97e0f97/websockets-15.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d50fd1ee42388dcfb2b3676132c78116490976f1300da28eb629272d5d93e905", size = 173330, upload_time = "2025-03-05T20:01:59.063Z" }, + { url = "https://files.pythonhosted.org/packages/a5/90/1c37ae8b8a113d3daf1065222b6af61cc44102da95388ac0018fcb7d93d9/websockets-15.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d99e5546bf73dbad5bf3547174cd6cb8ba7273062a23808ffea025ecb1cf8562", size = 182878, upload_time = "2025-03-05T20:02:00.305Z" }, + { url = "https://files.pythonhosted.org/packages/8e/8d/96e8e288b2a41dffafb78e8904ea7367ee4f891dafc2ab8d87e2124cb3d3/websockets-15.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66dd88c918e3287efc22409d426c8f729688d89a0c587c88971a0faa2c2f3792", size = 181883, upload_time = "2025-03-05T20:02:03.148Z" }, + { url = "https://files.pythonhosted.org/packages/93/1f/5d6dbf551766308f6f50f8baf8e9860be6182911e8106da7a7f73785f4c4/websockets-15.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8dd8327c795b3e3f219760fa603dcae1dcc148172290a8ab15158cf85a953413", size = 182252, upload_time = "2025-03-05T20:02:05.29Z" }, + { url = "https://files.pythonhosted.org/packages/d4/78/2d4fed9123e6620cbf1706c0de8a1632e1a28e7774d94346d7de1bba2ca3/websockets-15.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8fdc51055e6ff4adeb88d58a11042ec9a5eae317a0a53d12c062c8a8865909e8", size = 182521, upload_time = "2025-03-05T20:02:07.458Z" }, + { url = "https://files.pythonhosted.org/packages/e7/3b/66d4c1b444dd1a9823c4a81f50231b921bab54eee2f69e70319b4e21f1ca/websockets-15.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:693f0192126df6c2327cce3baa7c06f2a117575e32ab2308f7f8216c29d9e2e3", size = 181958, upload_time = "2025-03-05T20:02:09.842Z" }, + { url = "https://files.pythonhosted.org/packages/08/ff/e9eed2ee5fed6f76fdd6032ca5cd38c57ca9661430bb3d5fb2872dc8703c/websockets-15.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:54479983bd5fb469c38f2f5c7e3a24f9a4e70594cd68cd1fa6b9340dadaff7cf", size = 181918, upload_time = "2025-03-05T20:02:11.968Z" }, + { url = "https://files.pythonhosted.org/packages/d8/75/994634a49b7e12532be6a42103597b71098fd25900f7437d6055ed39930a/websockets-15.0.1-cp311-cp311-win32.whl", hash = "sha256:16b6c1b3e57799b9d38427dda63edcbe4926352c47cf88588c0be4ace18dac85", size = 176388, upload_time = "2025-03-05T20:02:13.32Z" }, + { url = "https://files.pythonhosted.org/packages/98/93/e36c73f78400a65f5e236cd376713c34182e6663f6889cd45a4a04d8f203/websockets-15.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:27ccee0071a0e75d22cb35849b1db43f2ecd3e161041ac1ee9d2352ddf72f065", size = 176828, upload_time = "2025-03-05T20:02:14.585Z" }, + { url = "https://files.pythonhosted.org/packages/51/6b/4545a0d843594f5d0771e86463606a3988b5a09ca5123136f8a76580dd63/websockets-15.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3e90baa811a5d73f3ca0bcbf32064d663ed81318ab225ee4f427ad4e26e5aff3", size = 175437, upload_time = "2025-03-05T20:02:16.706Z" }, + { url = "https://files.pythonhosted.org/packages/f4/71/809a0f5f6a06522af902e0f2ea2757f71ead94610010cf570ab5c98e99ed/websockets-15.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:592f1a9fe869c778694f0aa806ba0374e97648ab57936f092fd9d87f8bc03665", size = 173096, upload_time = "2025-03-05T20:02:18.832Z" }, + { url = "https://files.pythonhosted.org/packages/3d/69/1a681dd6f02180916f116894181eab8b2e25b31e484c5d0eae637ec01f7c/websockets-15.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0701bc3cfcb9164d04a14b149fd74be7347a530ad3bbf15ab2c678a2cd3dd9a2", size = 173332, upload_time = "2025-03-05T20:02:20.187Z" }, + { url = "https://files.pythonhosted.org/packages/a6/02/0073b3952f5bce97eafbb35757f8d0d54812b6174ed8dd952aa08429bcc3/websockets-15.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8b56bdcdb4505c8078cb6c7157d9811a85790f2f2b3632c7d1462ab5783d215", size = 183152, upload_time = "2025-03-05T20:02:22.286Z" }, + { url = "https://files.pythonhosted.org/packages/74/45/c205c8480eafd114b428284840da0b1be9ffd0e4f87338dc95dc6ff961a1/websockets-15.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0af68c55afbd5f07986df82831c7bff04846928ea8d1fd7f30052638788bc9b5", size = 182096, upload_time = "2025-03-05T20:02:24.368Z" }, + { url = "https://files.pythonhosted.org/packages/14/8f/aa61f528fba38578ec553c145857a181384c72b98156f858ca5c8e82d9d3/websockets-15.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64dee438fed052b52e4f98f76c5790513235efaa1ef7f3f2192c392cd7c91b65", size = 182523, upload_time = "2025-03-05T20:02:25.669Z" }, + { url = "https://files.pythonhosted.org/packages/ec/6d/0267396610add5bc0d0d3e77f546d4cd287200804fe02323797de77dbce9/websockets-15.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d5f6b181bb38171a8ad1d6aa58a67a6aa9d4b38d0f8c5f496b9e42561dfc62fe", size = 182790, upload_time = "2025-03-05T20:02:26.99Z" }, + { url = "https://files.pythonhosted.org/packages/02/05/c68c5adbf679cf610ae2f74a9b871ae84564462955d991178f95a1ddb7dd/websockets-15.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5d54b09eba2bada6011aea5375542a157637b91029687eb4fdb2dab11059c1b4", size = 182165, upload_time = "2025-03-05T20:02:30.291Z" }, + { url = "https://files.pythonhosted.org/packages/29/93/bb672df7b2f5faac89761cb5fa34f5cec45a4026c383a4b5761c6cea5c16/websockets-15.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3be571a8b5afed347da347bfcf27ba12b069d9d7f42cb8c7028b5e98bbb12597", size = 182160, upload_time = "2025-03-05T20:02:31.634Z" }, + { url = "https://files.pythonhosted.org/packages/ff/83/de1f7709376dc3ca9b7eeb4b9a07b4526b14876b6d372a4dc62312bebee0/websockets-15.0.1-cp312-cp312-win32.whl", hash = "sha256:c338ffa0520bdb12fbc527265235639fb76e7bc7faafbb93f6ba80d9c06578a9", size = 176395, upload_time = "2025-03-05T20:02:33.017Z" }, + { url = "https://files.pythonhosted.org/packages/7d/71/abf2ebc3bbfa40f391ce1428c7168fb20582d0ff57019b69ea20fa698043/websockets-15.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcd5cf9e305d7b8338754470cf69cf81f420459dbae8a3b40cee57417f4614a7", size = 176841, upload_time = "2025-03-05T20:02:34.498Z" }, + { url = "https://files.pythonhosted.org/packages/cb/9f/51f0cf64471a9d2b4d0fc6c534f323b664e7095640c34562f5182e5a7195/websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931", size = 175440, upload_time = "2025-03-05T20:02:36.695Z" }, + { url = "https://files.pythonhosted.org/packages/8a/05/aa116ec9943c718905997412c5989f7ed671bc0188ee2ba89520e8765d7b/websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675", size = 173098, upload_time = "2025-03-05T20:02:37.985Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0b/33cef55ff24f2d92924923c99926dcce78e7bd922d649467f0eda8368923/websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151", size = 173329, upload_time = "2025-03-05T20:02:39.298Z" }, + { url = "https://files.pythonhosted.org/packages/31/1d/063b25dcc01faa8fada1469bdf769de3768b7044eac9d41f734fd7b6ad6d/websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22", size = 183111, upload_time = "2025-03-05T20:02:40.595Z" }, + { url = "https://files.pythonhosted.org/packages/93/53/9a87ee494a51bf63e4ec9241c1ccc4f7c2f45fff85d5bde2ff74fcb68b9e/websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f", size = 182054, upload_time = "2025-03-05T20:02:41.926Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b2/83a6ddf56cdcbad4e3d841fcc55d6ba7d19aeb89c50f24dd7e859ec0805f/websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8", size = 182496, upload_time = "2025-03-05T20:02:43.304Z" }, + { url = "https://files.pythonhosted.org/packages/98/41/e7038944ed0abf34c45aa4635ba28136f06052e08fc2168520bb8b25149f/websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375", size = 182829, upload_time = "2025-03-05T20:02:48.812Z" }, + { url = "https://files.pythonhosted.org/packages/e0/17/de15b6158680c7623c6ef0db361da965ab25d813ae54fcfeae2e5b9ef910/websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d", size = 182217, upload_time = "2025-03-05T20:02:50.14Z" }, + { url = "https://files.pythonhosted.org/packages/33/2b/1f168cb6041853eef0362fb9554c3824367c5560cbdaad89ac40f8c2edfc/websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4", size = 182195, upload_time = "2025-03-05T20:02:51.561Z" }, + { url = "https://files.pythonhosted.org/packages/86/eb/20b6cdf273913d0ad05a6a14aed4b9a85591c18a987a3d47f20fa13dcc47/websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa", size = 176393, upload_time = "2025-03-05T20:02:53.814Z" }, + { url = "https://files.pythonhosted.org/packages/1b/6c/c65773d6cab416a64d191d6ee8a8b1c68a09970ea6909d16965d26bfed1e/websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561", size = 176837, upload_time = "2025-03-05T20:02:55.237Z" }, + { url = "https://files.pythonhosted.org/packages/36/db/3fff0bcbe339a6fa6a3b9e3fbc2bfb321ec2f4cd233692272c5a8d6cf801/websockets-15.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5f4c04ead5aed67c8a1a20491d54cdfba5884507a48dd798ecaf13c74c4489f5", size = 175424, upload_time = "2025-03-05T20:02:56.505Z" }, + { url = "https://files.pythonhosted.org/packages/46/e6/519054c2f477def4165b0ec060ad664ed174e140b0d1cbb9fafa4a54f6db/websockets-15.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:abdc0c6c8c648b4805c5eacd131910d2a7f6455dfd3becab248ef108e89ab16a", size = 173077, upload_time = "2025-03-05T20:02:58.37Z" }, + { url = "https://files.pythonhosted.org/packages/1a/21/c0712e382df64c93a0d16449ecbf87b647163485ca1cc3f6cbadb36d2b03/websockets-15.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a625e06551975f4b7ea7102bc43895b90742746797e2e14b70ed61c43a90f09b", size = 173324, upload_time = "2025-03-05T20:02:59.773Z" }, + { url = "https://files.pythonhosted.org/packages/1c/cb/51ba82e59b3a664df54beed8ad95517c1b4dc1a913730e7a7db778f21291/websockets-15.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d591f8de75824cbb7acad4e05d2d710484f15f29d4a915092675ad3456f11770", size = 182094, upload_time = "2025-03-05T20:03:01.827Z" }, + { url = "https://files.pythonhosted.org/packages/fb/0f/bf3788c03fec679bcdaef787518dbe60d12fe5615a544a6d4cf82f045193/websockets-15.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:47819cea040f31d670cc8d324bb6435c6f133b8c7a19ec3d61634e62f8d8f9eb", size = 181094, upload_time = "2025-03-05T20:03:03.123Z" }, + { url = "https://files.pythonhosted.org/packages/5e/da/9fb8c21edbc719b66763a571afbaf206cb6d3736d28255a46fc2fe20f902/websockets-15.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac017dd64572e5c3bd01939121e4d16cf30e5d7e110a119399cf3133b63ad054", size = 181397, upload_time = "2025-03-05T20:03:04.443Z" }, + { url = "https://files.pythonhosted.org/packages/2e/65/65f379525a2719e91d9d90c38fe8b8bc62bd3c702ac651b7278609b696c4/websockets-15.0.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4a9fac8e469d04ce6c25bb2610dc535235bd4aa14996b4e6dbebf5e007eba5ee", size = 181794, upload_time = "2025-03-05T20:03:06.708Z" }, + { url = "https://files.pythonhosted.org/packages/d9/26/31ac2d08f8e9304d81a1a7ed2851c0300f636019a57cbaa91342015c72cc/websockets-15.0.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:363c6f671b761efcb30608d24925a382497c12c506b51661883c3e22337265ed", size = 181194, upload_time = "2025-03-05T20:03:08.844Z" }, + { url = "https://files.pythonhosted.org/packages/98/72/1090de20d6c91994cd4b357c3f75a4f25ee231b63e03adea89671cc12a3f/websockets-15.0.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2034693ad3097d5355bfdacfffcbd3ef5694f9718ab7f29c29689a9eae841880", size = 181164, upload_time = "2025-03-05T20:03:10.242Z" }, + { url = "https://files.pythonhosted.org/packages/2d/37/098f2e1c103ae8ed79b0e77f08d83b0ec0b241cf4b7f2f10edd0126472e1/websockets-15.0.1-cp39-cp39-win32.whl", hash = "sha256:3b1ac0d3e594bf121308112697cf4b32be538fb1444468fb0a6ae4feebc83411", size = 176381, upload_time = "2025-03-05T20:03:12.77Z" }, + { url = "https://files.pythonhosted.org/packages/75/8b/a32978a3ab42cebb2ebdd5b05df0696a09f4d436ce69def11893afa301f0/websockets-15.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:b7643a03db5c95c799b89b31c036d5f27eeb4d259c798e878d6937d71832b1e4", size = 176841, upload_time = "2025-03-05T20:03:14.367Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/d40f779fa16f74d3468357197af8d6ad07e7c5a27ea1ca74ceb38986f77a/websockets-15.0.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0c9e74d766f2818bb95f84c25be4dea09841ac0f734d1966f415e4edfc4ef1c3", size = 173109, upload_time = "2025-03-05T20:03:17.769Z" }, + { url = "https://files.pythonhosted.org/packages/bc/cd/5b887b8585a593073fd92f7c23ecd3985cd2c3175025a91b0d69b0551372/websockets-15.0.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1009ee0c7739c08a0cd59de430d6de452a55e42d6b522de7aa15e6f67db0b8e1", size = 173343, upload_time = "2025-03-05T20:03:19.094Z" }, + { url = "https://files.pythonhosted.org/packages/fe/ae/d34f7556890341e900a95acf4886833646306269f899d58ad62f588bf410/websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76d1f20b1c7a2fa82367e04982e708723ba0e7b8d43aa643d3dcd404d74f1475", size = 174599, upload_time = "2025-03-05T20:03:21.1Z" }, + { url = "https://files.pythonhosted.org/packages/71/e6/5fd43993a87db364ec60fc1d608273a1a465c0caba69176dd160e197ce42/websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f29d80eb9a9263b8d109135351caf568cc3f80b9928bccde535c235de55c22d9", size = 174207, upload_time = "2025-03-05T20:03:23.221Z" }, + { url = "https://files.pythonhosted.org/packages/2b/fb/c492d6daa5ec067c2988ac80c61359ace5c4c674c532985ac5a123436cec/websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b359ed09954d7c18bbc1680f380c7301f92c60bf924171629c5db97febb12f04", size = 174155, upload_time = "2025-03-05T20:03:25.321Z" }, + { url = "https://files.pythonhosted.org/packages/68/a1/dcb68430b1d00b698ae7a7e0194433bce4f07ded185f0ee5fb21e2a2e91e/websockets-15.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:cad21560da69f4ce7658ca2cb83138fb4cf695a2ba3e475e0559e05991aa8122", size = 176884, upload_time = "2025-03-05T20:03:27.934Z" }, + { url = "https://files.pythonhosted.org/packages/b7/48/4b67623bac4d79beb3a6bb27b803ba75c1bdedc06bd827e465803690a4b2/websockets-15.0.1-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7f493881579c90fc262d9cdbaa05a6b54b3811c2f300766748db79f098db9940", size = 173106, upload_time = "2025-03-05T20:03:29.404Z" }, + { url = "https://files.pythonhosted.org/packages/ed/f0/adb07514a49fe5728192764e04295be78859e4a537ab8fcc518a3dbb3281/websockets-15.0.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:47b099e1f4fbc95b701b6e85768e1fcdaf1630f3cbe4765fa216596f12310e2e", size = 173339, upload_time = "2025-03-05T20:03:30.755Z" }, + { url = "https://files.pythonhosted.org/packages/87/28/bd23c6344b18fb43df40d0700f6d3fffcd7cef14a6995b4f976978b52e62/websockets-15.0.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67f2b6de947f8c757db2db9c71527933ad0019737ec374a8a6be9a956786aaf9", size = 174597, upload_time = "2025-03-05T20:03:32.247Z" }, + { url = "https://files.pythonhosted.org/packages/6d/79/ca288495863d0f23a60f546f0905ae8f3ed467ad87f8b6aceb65f4c013e4/websockets-15.0.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d08eb4c2b7d6c41da6ca0600c077e93f5adcfd979cd777d747e9ee624556da4b", size = 174205, upload_time = "2025-03-05T20:03:33.731Z" }, + { url = "https://files.pythonhosted.org/packages/04/e4/120ff3180b0872b1fe6637f6f995bcb009fb5c87d597c1fc21456f50c848/websockets-15.0.1-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b826973a4a2ae47ba357e4e82fa44a463b8f168e1ca775ac64521442b19e87f", size = 174150, upload_time = "2025-03-05T20:03:35.757Z" }, + { url = "https://files.pythonhosted.org/packages/cb/c3/30e2f9c539b8da8b1d76f64012f3b19253271a63413b2d3adb94b143407f/websockets-15.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:21c1fa28a6a7e3cbdc171c694398b6df4744613ce9b36b1a498e816787e28123", size = 176877, upload_time = "2025-03-05T20:03:37.199Z" }, + { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload_time = "2025-03-05T20:03:39.41Z" }, ] [[package]] name = "wrapt" version = "1.17.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c3/fc/e91cc220803d7bc4db93fb02facd8461c37364151b8494762cc88b0fbcef/wrapt-1.17.2.tar.gz", hash = "sha256:41388e9d4d1522446fe79d3213196bd9e3b301a336965b9e27ca2788ebd122f3", size = 55531 } +sdist = { url = "https://files.pythonhosted.org/packages/c3/fc/e91cc220803d7bc4db93fb02facd8461c37364151b8494762cc88b0fbcef/wrapt-1.17.2.tar.gz", hash = "sha256:41388e9d4d1522446fe79d3213196bd9e3b301a336965b9e27ca2788ebd122f3", size = 55531, upload_time = "2025-01-14T10:35:45.465Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/d1/1daec934997e8b160040c78d7b31789f19b122110a75eca3d4e8da0049e1/wrapt-1.17.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3d57c572081fed831ad2d26fd430d565b76aa277ed1d30ff4d40670b1c0dd984", size = 53307 }, - { url = "https://files.pythonhosted.org/packages/1b/7b/13369d42651b809389c1a7153baa01d9700430576c81a2f5c5e460df0ed9/wrapt-1.17.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b5e251054542ae57ac7f3fba5d10bfff615b6c2fb09abeb37d2f1463f841ae22", size = 38486 }, - { url = "https://files.pythonhosted.org/packages/62/bf/e0105016f907c30b4bd9e377867c48c34dc9c6c0c104556c9c9126bd89ed/wrapt-1.17.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:80dd7db6a7cb57ffbc279c4394246414ec99537ae81ffd702443335a61dbf3a7", size = 38777 }, - { url = "https://files.pythonhosted.org/packages/27/70/0f6e0679845cbf8b165e027d43402a55494779295c4b08414097b258ac87/wrapt-1.17.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a6e821770cf99cc586d33833b2ff32faebdbe886bd6322395606cf55153246c", size = 83314 }, - { url = "https://files.pythonhosted.org/packages/0f/77/0576d841bf84af8579124a93d216f55d6f74374e4445264cb378a6ed33eb/wrapt-1.17.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b60fb58b90c6d63779cb0c0c54eeb38941bae3ecf7a73c764c52c88c2dcb9d72", size = 74947 }, - { url = "https://files.pythonhosted.org/packages/90/ec/00759565518f268ed707dcc40f7eeec38637d46b098a1f5143bff488fe97/wrapt-1.17.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b870b5df5b71d8c3359d21be8f0d6c485fa0ebdb6477dda51a1ea54a9b558061", size = 82778 }, - { url = "https://files.pythonhosted.org/packages/f8/5a/7cffd26b1c607b0b0c8a9ca9d75757ad7620c9c0a9b4a25d3f8a1480fafc/wrapt-1.17.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4011d137b9955791f9084749cba9a367c68d50ab8d11d64c50ba1688c9b457f2", size = 81716 }, - { url = "https://files.pythonhosted.org/packages/7e/09/dccf68fa98e862df7e6a60a61d43d644b7d095a5fc36dbb591bbd4a1c7b2/wrapt-1.17.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:1473400e5b2733e58b396a04eb7f35f541e1fb976d0c0724d0223dd607e0f74c", size = 74548 }, - { url = "https://files.pythonhosted.org/packages/b7/8e/067021fa3c8814952c5e228d916963c1115b983e21393289de15128e867e/wrapt-1.17.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3cedbfa9c940fdad3e6e941db7138e26ce8aad38ab5fe9dcfadfed9db7a54e62", size = 81334 }, - { url = "https://files.pythonhosted.org/packages/4b/0d/9d4b5219ae4393f718699ca1c05f5ebc0c40d076f7e65fd48f5f693294fb/wrapt-1.17.2-cp310-cp310-win32.whl", hash = "sha256:582530701bff1dec6779efa00c516496968edd851fba224fbd86e46cc6b73563", size = 36427 }, - { url = "https://files.pythonhosted.org/packages/72/6a/c5a83e8f61aec1e1aeef939807602fb880e5872371e95df2137142f5c58e/wrapt-1.17.2-cp310-cp310-win_amd64.whl", hash = "sha256:58705da316756681ad3c9c73fd15499aa4d8c69f9fd38dc8a35e06c12468582f", size = 38774 }, - { url = "https://files.pythonhosted.org/packages/cd/f7/a2aab2cbc7a665efab072344a8949a71081eed1d2f451f7f7d2b966594a2/wrapt-1.17.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ff04ef6eec3eee8a5efef2401495967a916feaa353643defcc03fc74fe213b58", size = 53308 }, - { url = "https://files.pythonhosted.org/packages/50/ff/149aba8365fdacef52b31a258c4dc1c57c79759c335eff0b3316a2664a64/wrapt-1.17.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4db983e7bca53819efdbd64590ee96c9213894272c776966ca6306b73e4affda", size = 38488 }, - { url = "https://files.pythonhosted.org/packages/65/46/5a917ce85b5c3b490d35c02bf71aedaa9f2f63f2d15d9949cc4ba56e8ba9/wrapt-1.17.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9abc77a4ce4c6f2a3168ff34b1da9b0f311a8f1cfd694ec96b0603dff1c79438", size = 38776 }, - { url = "https://files.pythonhosted.org/packages/ca/74/336c918d2915a4943501c77566db41d1bd6e9f4dbc317f356b9a244dfe83/wrapt-1.17.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b929ac182f5ace000d459c59c2c9c33047e20e935f8e39371fa6e3b85d56f4a", size = 83776 }, - { url = "https://files.pythonhosted.org/packages/09/99/c0c844a5ccde0fe5761d4305485297f91d67cf2a1a824c5f282e661ec7ff/wrapt-1.17.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f09b286faeff3c750a879d336fb6d8713206fc97af3adc14def0cdd349df6000", size = 75420 }, - { url = "https://files.pythonhosted.org/packages/b4/b0/9fc566b0fe08b282c850063591a756057c3247b2362b9286429ec5bf1721/wrapt-1.17.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a7ed2d9d039bd41e889f6fb9364554052ca21ce823580f6a07c4ec245c1f5d6", size = 83199 }, - { url = "https://files.pythonhosted.org/packages/9d/4b/71996e62d543b0a0bd95dda485219856def3347e3e9380cc0d6cf10cfb2f/wrapt-1.17.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:129a150f5c445165ff941fc02ee27df65940fcb8a22a61828b1853c98763a64b", size = 82307 }, - { url = "https://files.pythonhosted.org/packages/39/35/0282c0d8789c0dc9bcc738911776c762a701f95cfe113fb8f0b40e45c2b9/wrapt-1.17.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1fb5699e4464afe5c7e65fa51d4f99e0b2eadcc176e4aa33600a3df7801d6662", size = 75025 }, - { url = "https://files.pythonhosted.org/packages/4f/6d/90c9fd2c3c6fee181feecb620d95105370198b6b98a0770cba090441a828/wrapt-1.17.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9a2bce789a5ea90e51a02dfcc39e31b7f1e662bc3317979aa7e5538e3a034f72", size = 81879 }, - { url = "https://files.pythonhosted.org/packages/8f/fa/9fb6e594f2ce03ef03eddbdb5f4f90acb1452221a5351116c7c4708ac865/wrapt-1.17.2-cp311-cp311-win32.whl", hash = "sha256:4afd5814270fdf6380616b321fd31435a462019d834f83c8611a0ce7484c7317", size = 36419 }, - { url = "https://files.pythonhosted.org/packages/47/f8/fb1773491a253cbc123c5d5dc15c86041f746ed30416535f2a8df1f4a392/wrapt-1.17.2-cp311-cp311-win_amd64.whl", hash = "sha256:acc130bc0375999da18e3d19e5a86403667ac0c4042a094fefb7eec8ebac7cf3", size = 38773 }, - { url = "https://files.pythonhosted.org/packages/a1/bd/ab55f849fd1f9a58ed7ea47f5559ff09741b25f00c191231f9f059c83949/wrapt-1.17.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d5e2439eecc762cd85e7bd37161d4714aa03a33c5ba884e26c81559817ca0925", size = 53799 }, - { url = "https://files.pythonhosted.org/packages/53/18/75ddc64c3f63988f5a1d7e10fb204ffe5762bc663f8023f18ecaf31a332e/wrapt-1.17.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fc7cb4c1c744f8c05cd5f9438a3caa6ab94ce8344e952d7c45a8ed59dd88392", size = 38821 }, - { url = "https://files.pythonhosted.org/packages/48/2a/97928387d6ed1c1ebbfd4efc4133a0633546bec8481a2dd5ec961313a1c7/wrapt-1.17.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8fdbdb757d5390f7c675e558fd3186d590973244fab0c5fe63d373ade3e99d40", size = 38919 }, - { url = "https://files.pythonhosted.org/packages/73/54/3bfe5a1febbbccb7a2f77de47b989c0b85ed3a6a41614b104204a788c20e/wrapt-1.17.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bb1d0dbf99411f3d871deb6faa9aabb9d4e744d67dcaaa05399af89d847a91d", size = 88721 }, - { url = "https://files.pythonhosted.org/packages/25/cb/7262bc1b0300b4b64af50c2720ef958c2c1917525238d661c3e9a2b71b7b/wrapt-1.17.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d18a4865f46b8579d44e4fe1e2bcbc6472ad83d98e22a26c963d46e4c125ef0b", size = 80899 }, - { url = "https://files.pythonhosted.org/packages/2a/5a/04cde32b07a7431d4ed0553a76fdb7a61270e78c5fd5a603e190ac389f14/wrapt-1.17.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc570b5f14a79734437cb7b0500376b6b791153314986074486e0b0fa8d71d98", size = 89222 }, - { url = "https://files.pythonhosted.org/packages/09/28/2e45a4f4771fcfb109e244d5dbe54259e970362a311b67a965555ba65026/wrapt-1.17.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6d9187b01bebc3875bac9b087948a2bccefe464a7d8f627cf6e48b1bbae30f82", size = 86707 }, - { url = "https://files.pythonhosted.org/packages/c6/d2/dcb56bf5f32fcd4bd9aacc77b50a539abdd5b6536872413fd3f428b21bed/wrapt-1.17.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9e8659775f1adf02eb1e6f109751268e493c73716ca5761f8acb695e52a756ae", size = 79685 }, - { url = "https://files.pythonhosted.org/packages/80/4e/eb8b353e36711347893f502ce91c770b0b0929f8f0bed2670a6856e667a9/wrapt-1.17.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e8b2816ebef96d83657b56306152a93909a83f23994f4b30ad4573b00bd11bb9", size = 87567 }, - { url = "https://files.pythonhosted.org/packages/17/27/4fe749a54e7fae6e7146f1c7d914d28ef599dacd4416566c055564080fe2/wrapt-1.17.2-cp312-cp312-win32.whl", hash = "sha256:468090021f391fe0056ad3e807e3d9034e0fd01adcd3bdfba977b6fdf4213ea9", size = 36672 }, - { url = "https://files.pythonhosted.org/packages/15/06/1dbf478ea45c03e78a6a8c4be4fdc3c3bddea5c8de8a93bc971415e47f0f/wrapt-1.17.2-cp312-cp312-win_amd64.whl", hash = "sha256:ec89ed91f2fa8e3f52ae53cd3cf640d6feff92ba90d62236a81e4e563ac0e991", size = 38865 }, - { url = "https://files.pythonhosted.org/packages/ce/b9/0ffd557a92f3b11d4c5d5e0c5e4ad057bd9eb8586615cdaf901409920b14/wrapt-1.17.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6ed6ffac43aecfe6d86ec5b74b06a5be33d5bb9243d055141e8cabb12aa08125", size = 53800 }, - { url = "https://files.pythonhosted.org/packages/c0/ef/8be90a0b7e73c32e550c73cfb2fa09db62234227ece47b0e80a05073b375/wrapt-1.17.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:35621ae4c00e056adb0009f8e86e28eb4a41a4bfa8f9bfa9fca7d343fe94f998", size = 38824 }, - { url = "https://files.pythonhosted.org/packages/36/89/0aae34c10fe524cce30fe5fc433210376bce94cf74d05b0d68344c8ba46e/wrapt-1.17.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a604bf7a053f8362d27eb9fefd2097f82600b856d5abe996d623babd067b1ab5", size = 38920 }, - { url = "https://files.pythonhosted.org/packages/3b/24/11c4510de906d77e0cfb5197f1b1445d4fec42c9a39ea853d482698ac681/wrapt-1.17.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cbabee4f083b6b4cd282f5b817a867cf0b1028c54d445b7ec7cfe6505057cf8", size = 88690 }, - { url = "https://files.pythonhosted.org/packages/71/d7/cfcf842291267bf455b3e266c0c29dcb675b5540ee8b50ba1699abf3af45/wrapt-1.17.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49703ce2ddc220df165bd2962f8e03b84c89fee2d65e1c24a7defff6f988f4d6", size = 80861 }, - { url = "https://files.pythonhosted.org/packages/d5/66/5d973e9f3e7370fd686fb47a9af3319418ed925c27d72ce16b791231576d/wrapt-1.17.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8112e52c5822fc4253f3901b676c55ddf288614dc7011634e2719718eaa187dc", size = 89174 }, - { url = "https://files.pythonhosted.org/packages/a7/d3/8e17bb70f6ae25dabc1aaf990f86824e4fd98ee9cadf197054e068500d27/wrapt-1.17.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9fee687dce376205d9a494e9c121e27183b2a3df18037f89d69bd7b35bcf59e2", size = 86721 }, - { url = "https://files.pythonhosted.org/packages/6f/54/f170dfb278fe1c30d0ff864513cff526d624ab8de3254b20abb9cffedc24/wrapt-1.17.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:18983c537e04d11cf027fbb60a1e8dfd5190e2b60cc27bc0808e653e7b218d1b", size = 79763 }, - { url = "https://files.pythonhosted.org/packages/4a/98/de07243751f1c4a9b15c76019250210dd3486ce098c3d80d5f729cba029c/wrapt-1.17.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:703919b1633412ab54bcf920ab388735832fdcb9f9a00ae49387f0fe67dad504", size = 87585 }, - { url = "https://files.pythonhosted.org/packages/f9/f0/13925f4bd6548013038cdeb11ee2cbd4e37c30f8bfd5db9e5a2a370d6e20/wrapt-1.17.2-cp313-cp313-win32.whl", hash = "sha256:abbb9e76177c35d4e8568e58650aa6926040d6a9f6f03435b7a522bf1c487f9a", size = 36676 }, - { url = "https://files.pythonhosted.org/packages/bf/ae/743f16ef8c2e3628df3ddfd652b7d4c555d12c84b53f3d8218498f4ade9b/wrapt-1.17.2-cp313-cp313-win_amd64.whl", hash = "sha256:69606d7bb691b50a4240ce6b22ebb319c1cfb164e5f6569835058196e0f3a845", size = 38871 }, - { url = "https://files.pythonhosted.org/packages/3d/bc/30f903f891a82d402ffb5fda27ec1d621cc97cb74c16fea0b6141f1d4e87/wrapt-1.17.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:4a721d3c943dae44f8e243b380cb645a709ba5bd35d3ad27bc2ed947e9c68192", size = 56312 }, - { url = "https://files.pythonhosted.org/packages/8a/04/c97273eb491b5f1c918857cd26f314b74fc9b29224521f5b83f872253725/wrapt-1.17.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:766d8bbefcb9e00c3ac3b000d9acc51f1b399513f44d77dfe0eb026ad7c9a19b", size = 40062 }, - { url = "https://files.pythonhosted.org/packages/4e/ca/3b7afa1eae3a9e7fefe499db9b96813f41828b9fdb016ee836c4c379dadb/wrapt-1.17.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e496a8ce2c256da1eb98bd15803a79bee00fc351f5dfb9ea82594a3f058309e0", size = 40155 }, - { url = "https://files.pythonhosted.org/packages/89/be/7c1baed43290775cb9030c774bc53c860db140397047cc49aedaf0a15477/wrapt-1.17.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d615e4fe22f4ad3528448c193b218e077656ca9ccb22ce2cb20db730f8d306", size = 113471 }, - { url = "https://files.pythonhosted.org/packages/32/98/4ed894cf012b6d6aae5f5cc974006bdeb92f0241775addad3f8cd6ab71c8/wrapt-1.17.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a5aaeff38654462bc4b09023918b7f21790efb807f54c000a39d41d69cf552cb", size = 101208 }, - { url = "https://files.pythonhosted.org/packages/ea/fd/0c30f2301ca94e655e5e057012e83284ce8c545df7661a78d8bfca2fac7a/wrapt-1.17.2-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a7d15bbd2bc99e92e39f49a04653062ee6085c0e18b3b7512a4f2fe91f2d681", size = 109339 }, - { url = "https://files.pythonhosted.org/packages/75/56/05d000de894c4cfcb84bcd6b1df6214297b8089a7bd324c21a4765e49b14/wrapt-1.17.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e3890b508a23299083e065f435a492b5435eba6e304a7114d2f919d400888cc6", size = 110232 }, - { url = "https://files.pythonhosted.org/packages/53/f8/c3f6b2cf9b9277fb0813418e1503e68414cd036b3b099c823379c9575e6d/wrapt-1.17.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:8c8b293cd65ad716d13d8dd3624e42e5a19cc2a2f1acc74b30c2c13f15cb61a6", size = 100476 }, - { url = "https://files.pythonhosted.org/packages/a7/b1/0bb11e29aa5139d90b770ebbfa167267b1fc548d2302c30c8f7572851738/wrapt-1.17.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c82b8785d98cdd9fed4cac84d765d234ed3251bd6afe34cb7ac523cb93e8b4f", size = 106377 }, - { url = "https://files.pythonhosted.org/packages/6a/e1/0122853035b40b3f333bbb25f1939fc1045e21dd518f7f0922b60c156f7c/wrapt-1.17.2-cp313-cp313t-win32.whl", hash = "sha256:13e6afb7fe71fe7485a4550a8844cc9ffbe263c0f1a1eea569bc7091d4898555", size = 37986 }, - { url = "https://files.pythonhosted.org/packages/09/5e/1655cf481e079c1f22d0cabdd4e51733679932718dc23bf2db175f329b76/wrapt-1.17.2-cp313-cp313t-win_amd64.whl", hash = "sha256:eaf675418ed6b3b31c7a989fd007fa7c3be66ce14e5c3b27336383604c9da85c", size = 40750 }, - { url = "https://files.pythonhosted.org/packages/8a/f4/6ed2b8f6f1c832933283974839b88ec7c983fd12905e01e97889dadf7559/wrapt-1.17.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:99039fa9e6306880572915728d7f6c24a86ec57b0a83f6b2491e1d8ab0235b9a", size = 53308 }, - { url = "https://files.pythonhosted.org/packages/a2/a9/712a53f8f4f4545768ac532619f6e56d5d0364a87b2212531685e89aeef8/wrapt-1.17.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2696993ee1eebd20b8e4ee4356483c4cb696066ddc24bd70bcbb80fa56ff9061", size = 38489 }, - { url = "https://files.pythonhosted.org/packages/fa/9b/e172c8f28a489a2888df18f953e2f6cb8d33b1a2e78c9dfc52d8bf6a5ead/wrapt-1.17.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:612dff5db80beef9e649c6d803a8d50c409082f1fedc9dbcdfde2983b2025b82", size = 38776 }, - { url = "https://files.pythonhosted.org/packages/cf/cb/7a07b51762dcd59bdbe07aa97f87b3169766cadf240f48d1cbe70a1be9db/wrapt-1.17.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62c2caa1585c82b3f7a7ab56afef7b3602021d6da34fbc1cf234ff139fed3cd9", size = 83050 }, - { url = "https://files.pythonhosted.org/packages/a5/51/a42757dd41032afd6d8037617aa3bc6803ba971850733b24dfb7d5c627c4/wrapt-1.17.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c958bcfd59bacc2d0249dcfe575e71da54f9dcf4a8bdf89c4cb9a68a1170d73f", size = 74718 }, - { url = "https://files.pythonhosted.org/packages/bf/bb/d552bfe47db02fcfc950fc563073a33500f8108efa5f7b41db2f83a59028/wrapt-1.17.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc78a84e2dfbc27afe4b2bd7c80c8db9bca75cc5b85df52bfe634596a1da846b", size = 82590 }, - { url = "https://files.pythonhosted.org/packages/77/99/77b06b3c3c410dbae411105bf22496facf03a5496bfaca8fbcf9da381889/wrapt-1.17.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ba0f0eb61ef00ea10e00eb53a9129501f52385c44853dbd6c4ad3f403603083f", size = 81462 }, - { url = "https://files.pythonhosted.org/packages/2d/21/cf0bd85ae66f92600829ea1de8e1da778e5e9f6e574ccbe74b66db0d95db/wrapt-1.17.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1e1fe0e6ab7775fd842bc39e86f6dcfc4507ab0ffe206093e76d61cde37225c8", size = 74309 }, - { url = "https://files.pythonhosted.org/packages/6d/16/112d25e9092398a0dd6fec50ab7ac1b775a0c19b428f049785096067ada9/wrapt-1.17.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c86563182421896d73858e08e1db93afdd2b947a70064b813d515d66549e15f9", size = 81081 }, - { url = "https://files.pythonhosted.org/packages/2b/49/364a615a0cc0872685646c495c7172e4fc7bf1959e3b12a1807a03014e05/wrapt-1.17.2-cp39-cp39-win32.whl", hash = "sha256:f393cda562f79828f38a819f4788641ac7c4085f30f1ce1a68672baa686482bb", size = 36423 }, - { url = "https://files.pythonhosted.org/packages/00/ad/5d2c1b34ba3202cd833d9221833e74d6500ce66730974993a8dc9a94fb8c/wrapt-1.17.2-cp39-cp39-win_amd64.whl", hash = "sha256:36ccae62f64235cf8ddb682073a60519426fdd4725524ae38874adf72b5f2aeb", size = 38772 }, - { url = "https://files.pythonhosted.org/packages/2d/82/f56956041adef78f849db6b289b282e72b55ab8045a75abad81898c28d19/wrapt-1.17.2-py3-none-any.whl", hash = "sha256:b18f2d1533a71f069c7f82d524a52599053d4c7166e9dd374ae2136b7f40f7c8", size = 23594 }, + { url = "https://files.pythonhosted.org/packages/5a/d1/1daec934997e8b160040c78d7b31789f19b122110a75eca3d4e8da0049e1/wrapt-1.17.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3d57c572081fed831ad2d26fd430d565b76aa277ed1d30ff4d40670b1c0dd984", size = 53307, upload_time = "2025-01-14T10:33:13.616Z" }, + { url = "https://files.pythonhosted.org/packages/1b/7b/13369d42651b809389c1a7153baa01d9700430576c81a2f5c5e460df0ed9/wrapt-1.17.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b5e251054542ae57ac7f3fba5d10bfff615b6c2fb09abeb37d2f1463f841ae22", size = 38486, upload_time = "2025-01-14T10:33:15.947Z" }, + { url = "https://files.pythonhosted.org/packages/62/bf/e0105016f907c30b4bd9e377867c48c34dc9c6c0c104556c9c9126bd89ed/wrapt-1.17.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:80dd7db6a7cb57ffbc279c4394246414ec99537ae81ffd702443335a61dbf3a7", size = 38777, upload_time = "2025-01-14T10:33:17.462Z" }, + { url = "https://files.pythonhosted.org/packages/27/70/0f6e0679845cbf8b165e027d43402a55494779295c4b08414097b258ac87/wrapt-1.17.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a6e821770cf99cc586d33833b2ff32faebdbe886bd6322395606cf55153246c", size = 83314, upload_time = "2025-01-14T10:33:21.282Z" }, + { url = "https://files.pythonhosted.org/packages/0f/77/0576d841bf84af8579124a93d216f55d6f74374e4445264cb378a6ed33eb/wrapt-1.17.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b60fb58b90c6d63779cb0c0c54eeb38941bae3ecf7a73c764c52c88c2dcb9d72", size = 74947, upload_time = "2025-01-14T10:33:24.414Z" }, + { url = "https://files.pythonhosted.org/packages/90/ec/00759565518f268ed707dcc40f7eeec38637d46b098a1f5143bff488fe97/wrapt-1.17.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b870b5df5b71d8c3359d21be8f0d6c485fa0ebdb6477dda51a1ea54a9b558061", size = 82778, upload_time = "2025-01-14T10:33:26.152Z" }, + { url = "https://files.pythonhosted.org/packages/f8/5a/7cffd26b1c607b0b0c8a9ca9d75757ad7620c9c0a9b4a25d3f8a1480fafc/wrapt-1.17.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4011d137b9955791f9084749cba9a367c68d50ab8d11d64c50ba1688c9b457f2", size = 81716, upload_time = "2025-01-14T10:33:27.372Z" }, + { url = "https://files.pythonhosted.org/packages/7e/09/dccf68fa98e862df7e6a60a61d43d644b7d095a5fc36dbb591bbd4a1c7b2/wrapt-1.17.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:1473400e5b2733e58b396a04eb7f35f541e1fb976d0c0724d0223dd607e0f74c", size = 74548, upload_time = "2025-01-14T10:33:28.52Z" }, + { url = "https://files.pythonhosted.org/packages/b7/8e/067021fa3c8814952c5e228d916963c1115b983e21393289de15128e867e/wrapt-1.17.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3cedbfa9c940fdad3e6e941db7138e26ce8aad38ab5fe9dcfadfed9db7a54e62", size = 81334, upload_time = "2025-01-14T10:33:29.643Z" }, + { url = "https://files.pythonhosted.org/packages/4b/0d/9d4b5219ae4393f718699ca1c05f5ebc0c40d076f7e65fd48f5f693294fb/wrapt-1.17.2-cp310-cp310-win32.whl", hash = "sha256:582530701bff1dec6779efa00c516496968edd851fba224fbd86e46cc6b73563", size = 36427, upload_time = "2025-01-14T10:33:30.832Z" }, + { url = "https://files.pythonhosted.org/packages/72/6a/c5a83e8f61aec1e1aeef939807602fb880e5872371e95df2137142f5c58e/wrapt-1.17.2-cp310-cp310-win_amd64.whl", hash = "sha256:58705da316756681ad3c9c73fd15499aa4d8c69f9fd38dc8a35e06c12468582f", size = 38774, upload_time = "2025-01-14T10:33:32.897Z" }, + { url = "https://files.pythonhosted.org/packages/cd/f7/a2aab2cbc7a665efab072344a8949a71081eed1d2f451f7f7d2b966594a2/wrapt-1.17.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ff04ef6eec3eee8a5efef2401495967a916feaa353643defcc03fc74fe213b58", size = 53308, upload_time = "2025-01-14T10:33:33.992Z" }, + { url = "https://files.pythonhosted.org/packages/50/ff/149aba8365fdacef52b31a258c4dc1c57c79759c335eff0b3316a2664a64/wrapt-1.17.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4db983e7bca53819efdbd64590ee96c9213894272c776966ca6306b73e4affda", size = 38488, upload_time = "2025-01-14T10:33:35.264Z" }, + { url = "https://files.pythonhosted.org/packages/65/46/5a917ce85b5c3b490d35c02bf71aedaa9f2f63f2d15d9949cc4ba56e8ba9/wrapt-1.17.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9abc77a4ce4c6f2a3168ff34b1da9b0f311a8f1cfd694ec96b0603dff1c79438", size = 38776, upload_time = "2025-01-14T10:33:38.28Z" }, + { url = "https://files.pythonhosted.org/packages/ca/74/336c918d2915a4943501c77566db41d1bd6e9f4dbc317f356b9a244dfe83/wrapt-1.17.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b929ac182f5ace000d459c59c2c9c33047e20e935f8e39371fa6e3b85d56f4a", size = 83776, upload_time = "2025-01-14T10:33:40.678Z" }, + { url = "https://files.pythonhosted.org/packages/09/99/c0c844a5ccde0fe5761d4305485297f91d67cf2a1a824c5f282e661ec7ff/wrapt-1.17.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f09b286faeff3c750a879d336fb6d8713206fc97af3adc14def0cdd349df6000", size = 75420, upload_time = "2025-01-14T10:33:41.868Z" }, + { url = "https://files.pythonhosted.org/packages/b4/b0/9fc566b0fe08b282c850063591a756057c3247b2362b9286429ec5bf1721/wrapt-1.17.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a7ed2d9d039bd41e889f6fb9364554052ca21ce823580f6a07c4ec245c1f5d6", size = 83199, upload_time = "2025-01-14T10:33:43.598Z" }, + { url = "https://files.pythonhosted.org/packages/9d/4b/71996e62d543b0a0bd95dda485219856def3347e3e9380cc0d6cf10cfb2f/wrapt-1.17.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:129a150f5c445165ff941fc02ee27df65940fcb8a22a61828b1853c98763a64b", size = 82307, upload_time = "2025-01-14T10:33:48.499Z" }, + { url = "https://files.pythonhosted.org/packages/39/35/0282c0d8789c0dc9bcc738911776c762a701f95cfe113fb8f0b40e45c2b9/wrapt-1.17.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1fb5699e4464afe5c7e65fa51d4f99e0b2eadcc176e4aa33600a3df7801d6662", size = 75025, upload_time = "2025-01-14T10:33:51.191Z" }, + { url = "https://files.pythonhosted.org/packages/4f/6d/90c9fd2c3c6fee181feecb620d95105370198b6b98a0770cba090441a828/wrapt-1.17.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9a2bce789a5ea90e51a02dfcc39e31b7f1e662bc3317979aa7e5538e3a034f72", size = 81879, upload_time = "2025-01-14T10:33:52.328Z" }, + { url = "https://files.pythonhosted.org/packages/8f/fa/9fb6e594f2ce03ef03eddbdb5f4f90acb1452221a5351116c7c4708ac865/wrapt-1.17.2-cp311-cp311-win32.whl", hash = "sha256:4afd5814270fdf6380616b321fd31435a462019d834f83c8611a0ce7484c7317", size = 36419, upload_time = "2025-01-14T10:33:53.551Z" }, + { url = "https://files.pythonhosted.org/packages/47/f8/fb1773491a253cbc123c5d5dc15c86041f746ed30416535f2a8df1f4a392/wrapt-1.17.2-cp311-cp311-win_amd64.whl", hash = "sha256:acc130bc0375999da18e3d19e5a86403667ac0c4042a094fefb7eec8ebac7cf3", size = 38773, upload_time = "2025-01-14T10:33:56.323Z" }, + { url = "https://files.pythonhosted.org/packages/a1/bd/ab55f849fd1f9a58ed7ea47f5559ff09741b25f00c191231f9f059c83949/wrapt-1.17.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d5e2439eecc762cd85e7bd37161d4714aa03a33c5ba884e26c81559817ca0925", size = 53799, upload_time = "2025-01-14T10:33:57.4Z" }, + { url = "https://files.pythonhosted.org/packages/53/18/75ddc64c3f63988f5a1d7e10fb204ffe5762bc663f8023f18ecaf31a332e/wrapt-1.17.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fc7cb4c1c744f8c05cd5f9438a3caa6ab94ce8344e952d7c45a8ed59dd88392", size = 38821, upload_time = "2025-01-14T10:33:59.334Z" }, + { url = "https://files.pythonhosted.org/packages/48/2a/97928387d6ed1c1ebbfd4efc4133a0633546bec8481a2dd5ec961313a1c7/wrapt-1.17.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8fdbdb757d5390f7c675e558fd3186d590973244fab0c5fe63d373ade3e99d40", size = 38919, upload_time = "2025-01-14T10:34:04.093Z" }, + { url = "https://files.pythonhosted.org/packages/73/54/3bfe5a1febbbccb7a2f77de47b989c0b85ed3a6a41614b104204a788c20e/wrapt-1.17.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bb1d0dbf99411f3d871deb6faa9aabb9d4e744d67dcaaa05399af89d847a91d", size = 88721, upload_time = "2025-01-14T10:34:07.163Z" }, + { url = "https://files.pythonhosted.org/packages/25/cb/7262bc1b0300b4b64af50c2720ef958c2c1917525238d661c3e9a2b71b7b/wrapt-1.17.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d18a4865f46b8579d44e4fe1e2bcbc6472ad83d98e22a26c963d46e4c125ef0b", size = 80899, upload_time = "2025-01-14T10:34:09.82Z" }, + { url = "https://files.pythonhosted.org/packages/2a/5a/04cde32b07a7431d4ed0553a76fdb7a61270e78c5fd5a603e190ac389f14/wrapt-1.17.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc570b5f14a79734437cb7b0500376b6b791153314986074486e0b0fa8d71d98", size = 89222, upload_time = "2025-01-14T10:34:11.258Z" }, + { url = "https://files.pythonhosted.org/packages/09/28/2e45a4f4771fcfb109e244d5dbe54259e970362a311b67a965555ba65026/wrapt-1.17.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6d9187b01bebc3875bac9b087948a2bccefe464a7d8f627cf6e48b1bbae30f82", size = 86707, upload_time = "2025-01-14T10:34:12.49Z" }, + { url = "https://files.pythonhosted.org/packages/c6/d2/dcb56bf5f32fcd4bd9aacc77b50a539abdd5b6536872413fd3f428b21bed/wrapt-1.17.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9e8659775f1adf02eb1e6f109751268e493c73716ca5761f8acb695e52a756ae", size = 79685, upload_time = "2025-01-14T10:34:15.043Z" }, + { url = "https://files.pythonhosted.org/packages/80/4e/eb8b353e36711347893f502ce91c770b0b0929f8f0bed2670a6856e667a9/wrapt-1.17.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e8b2816ebef96d83657b56306152a93909a83f23994f4b30ad4573b00bd11bb9", size = 87567, upload_time = "2025-01-14T10:34:16.563Z" }, + { url = "https://files.pythonhosted.org/packages/17/27/4fe749a54e7fae6e7146f1c7d914d28ef599dacd4416566c055564080fe2/wrapt-1.17.2-cp312-cp312-win32.whl", hash = "sha256:468090021f391fe0056ad3e807e3d9034e0fd01adcd3bdfba977b6fdf4213ea9", size = 36672, upload_time = "2025-01-14T10:34:17.727Z" }, + { url = "https://files.pythonhosted.org/packages/15/06/1dbf478ea45c03e78a6a8c4be4fdc3c3bddea5c8de8a93bc971415e47f0f/wrapt-1.17.2-cp312-cp312-win_amd64.whl", hash = "sha256:ec89ed91f2fa8e3f52ae53cd3cf640d6feff92ba90d62236a81e4e563ac0e991", size = 38865, upload_time = "2025-01-14T10:34:19.577Z" }, + { url = "https://files.pythonhosted.org/packages/ce/b9/0ffd557a92f3b11d4c5d5e0c5e4ad057bd9eb8586615cdaf901409920b14/wrapt-1.17.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6ed6ffac43aecfe6d86ec5b74b06a5be33d5bb9243d055141e8cabb12aa08125", size = 53800, upload_time = "2025-01-14T10:34:21.571Z" }, + { url = "https://files.pythonhosted.org/packages/c0/ef/8be90a0b7e73c32e550c73cfb2fa09db62234227ece47b0e80a05073b375/wrapt-1.17.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:35621ae4c00e056adb0009f8e86e28eb4a41a4bfa8f9bfa9fca7d343fe94f998", size = 38824, upload_time = "2025-01-14T10:34:22.999Z" }, + { url = "https://files.pythonhosted.org/packages/36/89/0aae34c10fe524cce30fe5fc433210376bce94cf74d05b0d68344c8ba46e/wrapt-1.17.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a604bf7a053f8362d27eb9fefd2097f82600b856d5abe996d623babd067b1ab5", size = 38920, upload_time = "2025-01-14T10:34:25.386Z" }, + { url = "https://files.pythonhosted.org/packages/3b/24/11c4510de906d77e0cfb5197f1b1445d4fec42c9a39ea853d482698ac681/wrapt-1.17.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cbabee4f083b6b4cd282f5b817a867cf0b1028c54d445b7ec7cfe6505057cf8", size = 88690, upload_time = "2025-01-14T10:34:28.058Z" }, + { url = "https://files.pythonhosted.org/packages/71/d7/cfcf842291267bf455b3e266c0c29dcb675b5540ee8b50ba1699abf3af45/wrapt-1.17.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49703ce2ddc220df165bd2962f8e03b84c89fee2d65e1c24a7defff6f988f4d6", size = 80861, upload_time = "2025-01-14T10:34:29.167Z" }, + { url = "https://files.pythonhosted.org/packages/d5/66/5d973e9f3e7370fd686fb47a9af3319418ed925c27d72ce16b791231576d/wrapt-1.17.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8112e52c5822fc4253f3901b676c55ddf288614dc7011634e2719718eaa187dc", size = 89174, upload_time = "2025-01-14T10:34:31.702Z" }, + { url = "https://files.pythonhosted.org/packages/a7/d3/8e17bb70f6ae25dabc1aaf990f86824e4fd98ee9cadf197054e068500d27/wrapt-1.17.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9fee687dce376205d9a494e9c121e27183b2a3df18037f89d69bd7b35bcf59e2", size = 86721, upload_time = "2025-01-14T10:34:32.91Z" }, + { url = "https://files.pythonhosted.org/packages/6f/54/f170dfb278fe1c30d0ff864513cff526d624ab8de3254b20abb9cffedc24/wrapt-1.17.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:18983c537e04d11cf027fbb60a1e8dfd5190e2b60cc27bc0808e653e7b218d1b", size = 79763, upload_time = "2025-01-14T10:34:34.903Z" }, + { url = "https://files.pythonhosted.org/packages/4a/98/de07243751f1c4a9b15c76019250210dd3486ce098c3d80d5f729cba029c/wrapt-1.17.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:703919b1633412ab54bcf920ab388735832fdcb9f9a00ae49387f0fe67dad504", size = 87585, upload_time = "2025-01-14T10:34:36.13Z" }, + { url = "https://files.pythonhosted.org/packages/f9/f0/13925f4bd6548013038cdeb11ee2cbd4e37c30f8bfd5db9e5a2a370d6e20/wrapt-1.17.2-cp313-cp313-win32.whl", hash = "sha256:abbb9e76177c35d4e8568e58650aa6926040d6a9f6f03435b7a522bf1c487f9a", size = 36676, upload_time = "2025-01-14T10:34:37.962Z" }, + { url = "https://files.pythonhosted.org/packages/bf/ae/743f16ef8c2e3628df3ddfd652b7d4c555d12c84b53f3d8218498f4ade9b/wrapt-1.17.2-cp313-cp313-win_amd64.whl", hash = "sha256:69606d7bb691b50a4240ce6b22ebb319c1cfb164e5f6569835058196e0f3a845", size = 38871, upload_time = "2025-01-14T10:34:39.13Z" }, + { url = "https://files.pythonhosted.org/packages/3d/bc/30f903f891a82d402ffb5fda27ec1d621cc97cb74c16fea0b6141f1d4e87/wrapt-1.17.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:4a721d3c943dae44f8e243b380cb645a709ba5bd35d3ad27bc2ed947e9c68192", size = 56312, upload_time = "2025-01-14T10:34:40.604Z" }, + { url = "https://files.pythonhosted.org/packages/8a/04/c97273eb491b5f1c918857cd26f314b74fc9b29224521f5b83f872253725/wrapt-1.17.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:766d8bbefcb9e00c3ac3b000d9acc51f1b399513f44d77dfe0eb026ad7c9a19b", size = 40062, upload_time = "2025-01-14T10:34:45.011Z" }, + { url = "https://files.pythonhosted.org/packages/4e/ca/3b7afa1eae3a9e7fefe499db9b96813f41828b9fdb016ee836c4c379dadb/wrapt-1.17.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e496a8ce2c256da1eb98bd15803a79bee00fc351f5dfb9ea82594a3f058309e0", size = 40155, upload_time = "2025-01-14T10:34:47.25Z" }, + { url = "https://files.pythonhosted.org/packages/89/be/7c1baed43290775cb9030c774bc53c860db140397047cc49aedaf0a15477/wrapt-1.17.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d615e4fe22f4ad3528448c193b218e077656ca9ccb22ce2cb20db730f8d306", size = 113471, upload_time = "2025-01-14T10:34:50.934Z" }, + { url = "https://files.pythonhosted.org/packages/32/98/4ed894cf012b6d6aae5f5cc974006bdeb92f0241775addad3f8cd6ab71c8/wrapt-1.17.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a5aaeff38654462bc4b09023918b7f21790efb807f54c000a39d41d69cf552cb", size = 101208, upload_time = "2025-01-14T10:34:52.297Z" }, + { url = "https://files.pythonhosted.org/packages/ea/fd/0c30f2301ca94e655e5e057012e83284ce8c545df7661a78d8bfca2fac7a/wrapt-1.17.2-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a7d15bbd2bc99e92e39f49a04653062ee6085c0e18b3b7512a4f2fe91f2d681", size = 109339, upload_time = "2025-01-14T10:34:53.489Z" }, + { url = "https://files.pythonhosted.org/packages/75/56/05d000de894c4cfcb84bcd6b1df6214297b8089a7bd324c21a4765e49b14/wrapt-1.17.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e3890b508a23299083e065f435a492b5435eba6e304a7114d2f919d400888cc6", size = 110232, upload_time = "2025-01-14T10:34:55.327Z" }, + { url = "https://files.pythonhosted.org/packages/53/f8/c3f6b2cf9b9277fb0813418e1503e68414cd036b3b099c823379c9575e6d/wrapt-1.17.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:8c8b293cd65ad716d13d8dd3624e42e5a19cc2a2f1acc74b30c2c13f15cb61a6", size = 100476, upload_time = "2025-01-14T10:34:58.055Z" }, + { url = "https://files.pythonhosted.org/packages/a7/b1/0bb11e29aa5139d90b770ebbfa167267b1fc548d2302c30c8f7572851738/wrapt-1.17.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c82b8785d98cdd9fed4cac84d765d234ed3251bd6afe34cb7ac523cb93e8b4f", size = 106377, upload_time = "2025-01-14T10:34:59.3Z" }, + { url = "https://files.pythonhosted.org/packages/6a/e1/0122853035b40b3f333bbb25f1939fc1045e21dd518f7f0922b60c156f7c/wrapt-1.17.2-cp313-cp313t-win32.whl", hash = "sha256:13e6afb7fe71fe7485a4550a8844cc9ffbe263c0f1a1eea569bc7091d4898555", size = 37986, upload_time = "2025-01-14T10:35:00.498Z" }, + { url = "https://files.pythonhosted.org/packages/09/5e/1655cf481e079c1f22d0cabdd4e51733679932718dc23bf2db175f329b76/wrapt-1.17.2-cp313-cp313t-win_amd64.whl", hash = "sha256:eaf675418ed6b3b31c7a989fd007fa7c3be66ce14e5c3b27336383604c9da85c", size = 40750, upload_time = "2025-01-14T10:35:03.378Z" }, + { url = "https://files.pythonhosted.org/packages/8a/f4/6ed2b8f6f1c832933283974839b88ec7c983fd12905e01e97889dadf7559/wrapt-1.17.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:99039fa9e6306880572915728d7f6c24a86ec57b0a83f6b2491e1d8ab0235b9a", size = 53308, upload_time = "2025-01-14T10:35:24.413Z" }, + { url = "https://files.pythonhosted.org/packages/a2/a9/712a53f8f4f4545768ac532619f6e56d5d0364a87b2212531685e89aeef8/wrapt-1.17.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2696993ee1eebd20b8e4ee4356483c4cb696066ddc24bd70bcbb80fa56ff9061", size = 38489, upload_time = "2025-01-14T10:35:26.913Z" }, + { url = "https://files.pythonhosted.org/packages/fa/9b/e172c8f28a489a2888df18f953e2f6cb8d33b1a2e78c9dfc52d8bf6a5ead/wrapt-1.17.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:612dff5db80beef9e649c6d803a8d50c409082f1fedc9dbcdfde2983b2025b82", size = 38776, upload_time = "2025-01-14T10:35:28.183Z" }, + { url = "https://files.pythonhosted.org/packages/cf/cb/7a07b51762dcd59bdbe07aa97f87b3169766cadf240f48d1cbe70a1be9db/wrapt-1.17.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62c2caa1585c82b3f7a7ab56afef7b3602021d6da34fbc1cf234ff139fed3cd9", size = 83050, upload_time = "2025-01-14T10:35:30.645Z" }, + { url = "https://files.pythonhosted.org/packages/a5/51/a42757dd41032afd6d8037617aa3bc6803ba971850733b24dfb7d5c627c4/wrapt-1.17.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c958bcfd59bacc2d0249dcfe575e71da54f9dcf4a8bdf89c4cb9a68a1170d73f", size = 74718, upload_time = "2025-01-14T10:35:32.047Z" }, + { url = "https://files.pythonhosted.org/packages/bf/bb/d552bfe47db02fcfc950fc563073a33500f8108efa5f7b41db2f83a59028/wrapt-1.17.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc78a84e2dfbc27afe4b2bd7c80c8db9bca75cc5b85df52bfe634596a1da846b", size = 82590, upload_time = "2025-01-14T10:35:33.329Z" }, + { url = "https://files.pythonhosted.org/packages/77/99/77b06b3c3c410dbae411105bf22496facf03a5496bfaca8fbcf9da381889/wrapt-1.17.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ba0f0eb61ef00ea10e00eb53a9129501f52385c44853dbd6c4ad3f403603083f", size = 81462, upload_time = "2025-01-14T10:35:34.933Z" }, + { url = "https://files.pythonhosted.org/packages/2d/21/cf0bd85ae66f92600829ea1de8e1da778e5e9f6e574ccbe74b66db0d95db/wrapt-1.17.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1e1fe0e6ab7775fd842bc39e86f6dcfc4507ab0ffe206093e76d61cde37225c8", size = 74309, upload_time = "2025-01-14T10:35:37.542Z" }, + { url = "https://files.pythonhosted.org/packages/6d/16/112d25e9092398a0dd6fec50ab7ac1b775a0c19b428f049785096067ada9/wrapt-1.17.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c86563182421896d73858e08e1db93afdd2b947a70064b813d515d66549e15f9", size = 81081, upload_time = "2025-01-14T10:35:38.9Z" }, + { url = "https://files.pythonhosted.org/packages/2b/49/364a615a0cc0872685646c495c7172e4fc7bf1959e3b12a1807a03014e05/wrapt-1.17.2-cp39-cp39-win32.whl", hash = "sha256:f393cda562f79828f38a819f4788641ac7c4085f30f1ce1a68672baa686482bb", size = 36423, upload_time = "2025-01-14T10:35:40.177Z" }, + { url = "https://files.pythonhosted.org/packages/00/ad/5d2c1b34ba3202cd833d9221833e74d6500ce66730974993a8dc9a94fb8c/wrapt-1.17.2-cp39-cp39-win_amd64.whl", hash = "sha256:36ccae62f64235cf8ddb682073a60519426fdd4725524ae38874adf72b5f2aeb", size = 38772, upload_time = "2025-01-14T10:35:42.763Z" }, + { url = "https://files.pythonhosted.org/packages/2d/82/f56956041adef78f849db6b289b282e72b55ab8045a75abad81898c28d19/wrapt-1.17.2-py3-none-any.whl", hash = "sha256:b18f2d1533a71f069c7f82d524a52599053d4c7166e9dd374ae2136b7f40f7c8", size = 23594, upload_time = "2025-01-14T10:35:44.018Z" }, ]