From f63e22337cbfbebf6a0ab24e3d8bc3dd88eaf00b Mon Sep 17 00:00:00 2001 From: Mathijs de Bruin Date: Thu, 30 Jan 2025 11:59:15 +0000 Subject: [PATCH] README's for all components, descriptions for projects and package overview. --- README.md | 48 ++++-- packages/data_layers/dynamodb/README.md | 141 +++++++++++++++++- packages/data_layers/dynamodb/pyproject.toml | 20 ++- packages/data_layers/literalai/README.md | 49 +++++- packages/data_layers/sqlalchemy/README.md | 64 +++++++- .../data_layers/sqlalchemy/pyproject.toml | 21 ++- packages/data_layers/sqlalchemy/schema.sql | 67 +++++++++ packages/storage_clients/azure/README.md | 96 +++++++++++- packages/storage_clients/azure/pyproject.toml | 2 +- packages/storage_clients/azure_blob/README.md | 67 ++++++++- .../storage_clients/azure_blob/pyproject.toml | 2 +- packages/storage_clients/gcs/README.md | 134 ++++++++++++++++- packages/storage_clients/gcs/pyproject.toml | 2 +- packages/storage_clients/s3/README.md | 127 ++++++++++++++++ packages/storage_clients/s3/pyproject.toml | 2 +- uv.lock | 134 +++++++++++++++++ 16 files changed, 947 insertions(+), 29 deletions(-) create mode 100644 packages/data_layers/sqlalchemy/schema.sql diff --git a/README.md b/README.md index 48ce56a..587f72c 100644 --- a/README.md +++ b/README.md @@ -62,17 +62,43 @@ To maintain high-quality code and reduce technical debt, we enforce the followin - Testing against both the latest Chainlit release and the main branch - Documentation for all added or changed functionality -## 🔧 Features and Integrations - -This repository hosts a variety of community-maintained features and integrations, including but not limited to: - -- Additional LLM framework integrations -- Custom authentication implementations -- Specialized UI components -- Utility functions and helpers -- Domain-specific extensions - -For a full list of available features and integrations, please check our [Features Directory](FEATURES.md). +## Component Architecture + +Chainlit Community components follow a modular architecture with two main component types: + +### 1. Data Layers +**Role**: Persistent structured data storage for conversation elements (users, threads, steps) +**Interactions**: +- Direct integration with Chainlit's data layer system +- Optional integration with Storage Providers for file attachments + +| Package | Description | README | +|---------|-------------|--------| +| `dynamodb` | Amazon DynamoDB implementation with cloud storage integration | [docs](packages/data_layers/dynamodb/README.md) | +| `sqlalchemy` | SQL database support (PostgreSQL/SQLite) with storage provider integration | [docs](packages/data_layers/sqlalchemy/README.md) | +| `literalai` | Official Literal AI observability platform integration | [docs](packages/data_layers/literalai/README.md) | + +### 2. Storage Providers +**Role**: File storage and management for attachments/media +**Interactions**: +- Used by Data Layers through dependency injection +- Handle upload/delete operations and URL generation + +| Package | Cloud Provider | README | +|---------|----------------|--------| +| `azure` | Azure Data Lake | [docs](packages/storage_clients/azure/README.md) | +| `azure-blob` | Azure Blob Storage | [docs](packages/storage_clients/azure_blob/README.md) | +| `gcs` | Google Cloud Storage | [docs](packages/storage_clients/gcs/README.md) | +| `s3` | AWS S3 | [docs](packages/storage_clients/s3/README.md) | + +## Typical Data Flow +```mermaid +graph LR + A[Chainlit App] --> B{Data Layer} + B -->|Persists metadata| C[(Database)] + B -->|Delegates files| D[[Storage Provider]] + D -->|Stores objects| E[(Cloud Storage)] +``` ## 📚 Documentation diff --git a/packages/data_layers/dynamodb/README.md b/packages/data_layers/dynamodb/README.md index 0078853..7dc387c 100644 --- a/packages/data_layers/dynamodb/README.md +++ b/packages/data_layers/dynamodb/README.md @@ -2,4 +2,143 @@ DynamoDB data layer for [Chainlit](https://chainlit.io/). -## Usage +## DynamoDB Data Layer + +This data layer supports Amazon DynamoDB with optional cloud storage integration for elements. + +Key features: +- Single table design with efficient query patterns +- Supports storage clients for attachments (S3, Azure Blob) +- User/thread/step/element/feedback storage in DynamoDB +- Built-in pagination and sorting + +## Setup Example (DynamoDB + Cloud Storage) + +1. Create DynamoDB table using the [CloudFormation template](#table-structure) +2. Install required dependencies: +```bash +# Core requirements +pip install chainlit-dynamodb + +# With cloud storage (choose one): +pip install chainlit-dynamodb[s3] # AWS S3 +pip install chainlit-dynamodb[azure-blob] # Azure Blob Storage +pip install chainlit-dynamodb[gcs] # Google Cloud Storage +pip install chainlit-dynamodb[azure] # Azure Data Lake +``` + +3. Configure in your Chainlit app: +```python +import os +import chainlit as cl +from chainlit.data.dynamodb import DynamoDBDataLayer +from chainlit.data.storage_clients import ( + S3StorageClient, + AzureBlobStorageClient, + GCSStorageClient, + AzureStorageClient +) + +# Security Note: Always store secrets in environment variables +# Never commit credentials to source control +# Consider using secret managers like AWS Secrets Manager + +@cl.data_layer +def get_data_layer(): + # Choose one storage provider: + + # AWS S3 Example + storage_client = S3StorageClient( + bucket="", + region_name="", + aws_access_key_id=os.environ["AWS_ACCESS_KEY_ID"], + aws_secret_access_key=os.environ["AWS_SECRET_ACCESS_KEY"] + ) + + # Azure Blob Example + # storage_client = AzureBlobStorageClient( + # container_name="", + # storage_account="", + # storage_key="" + # ) + + # Google Cloud Storage Example + # storage_client = GCSStorageClient( + # project_id="", + # client_email="", + # private_key="", + # bucket_name="" + # ) + + # Azure Data Lake Example + # storage_client = AzureStorageClient( + # account_url="https://.dfs.core.windows.net", + # credential="", + # container_name="" + # ) + + return DynamoDBDataLayer( + table_name="", + storage_provider=storage_client, + user_thread_limit=10 + ) +``` + +## Table Structure +```yaml +# CloudFormation template for required table structure +AWSTemplateFormatVersion: 2010-09-09 +Resources: + DynamoDBTable: + Type: AWS::DynamoDB::Table + Properties: + TableName: "" + AttributeDefinitions: + - AttributeName: PK + AttributeType: S + - AttributeName: SK + AttributeType: S + - AttributeName: UserThreadPK + AttributeType: S + - AttributeName: UserThreadSK + AttributeType: S + KeySchema: + - AttributeName: PK + KeyType: HASH + - AttributeName: SK + KeyType: RANGE + GlobalSecondaryIndexes: + - IndexName: UserThread + KeySchema: + - AttributeName: UserThreadPK + KeyType: HASH + - AttributeName: UserThreadSK + KeyType: RANGE + Projection: + ProjectionType: INCLUDE + NonKeyAttributes: [id, name] + BillingMode: PAY_PER_REQUEST +``` + +## Logging +```python +import logging +from chainlit import logger + +# Enable debug logging for DynamoDB operations +logger.getChild("DynamoDB").setLevel(logging.DEBUG) +``` + +## Limitations +- Feedback filtering not supported +- Boto3-based implementation uses blocking IO (not async) +- Decimal types in feedback values require special handling + +## Design +Uses single-table design with entity prefixes: +- Users: `USER#{identifier}` +- Threads: `THREAD#{thread_id}` +- Steps: `STEP#{step_id}` +- Elements: `ELEMENT#{element_id}` + +Global Secondary Index (UserThread) enables efficient user thread queries. diff --git a/packages/data_layers/dynamodb/pyproject.toml b/packages/data_layers/dynamodb/pyproject.toml index 41969b4..bb403d6 100644 --- a/packages/data_layers/dynamodb/pyproject.toml +++ b/packages/data_layers/dynamodb/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "chainlit-dynamodb" version = "0.1.0" -description = "Add your description here" +description = "DynamoDB data layer for Chainlit" readme = "README.md" requires-python = ">=3.10" dependencies = [ @@ -9,12 +9,30 @@ dependencies = [ "boto3>=1.34.73,<2", ] +[project.optional-dependencies] +s3 = [ + "chainlit-s3", +] +azure-blob = [ + "chainlit-azure-blob", +] +gcs = [ + "chainlit-gcs", +] +azure = [ + "chainlit-azure", +] + [dependency-groups] dev = [ "pytest-chainlit", ] [tool.uv.sources] +chainlit-s3 = { workspace = true } +chainlit-azure-blob = { workspace = true } +chainlit-gcs = { workspace = true } +chainlit-azure = { workspace = true } pytest-chainlit = { workspace = true } [build-system] diff --git a/packages/data_layers/literalai/README.md b/packages/data_layers/literalai/README.md index 6b679ca..83cf182 100644 --- a/packages/data_layers/literalai/README.md +++ b/packages/data_layers/literalai/README.md @@ -1,5 +1,50 @@ # chainlit-literalai -[Literal AI](https://www.literalai.com/) data layer for [Chainlit](https://chainlit.io/). +[Literal AI](https://www.literal.ai/) integration for [Chainlit](https://chainlit.io/) applications. -## Usage +## Overview + +Official data persistence layer connecting Chainlit with Literal AI's LLM observability platform. Enables production-grade monitoring, evaluation and analytics while maintaining Chainlit's conversation structure. + +**Key Features**: +- Full conversation history preservation (threads, steps, elements) +- Multimodal logging (text, images, audio, video) +- User feedback tracking +- Automated performance metrics +- Collaborative prompt versioning & A/B testing + +## Setup + +1. **Install package**: +```bash +pip install chainlit-literalai +``` + +2. **Configure environment**: +```bash +# .env file +# Security Best Practices: +# - Restrict .env file permissions +# - Never commit .env to version control +# - Use CI/CD secret management +LITERAL_API_KEY="your-api-key-from-literal-ai" +``` + +3. **Run your app**: +```bash +chainlit run app.py +``` + +## Documentation + +- [Literal AI Documentation](https://docs.literalai.com) +- [Chainlit + Literal AI Integration Guide](https://docs.chainlit.io/llmops/literalai) + +## Data Privacy + +- Data retention policy: [literalai.com/security](https://www.literalai.com/security) +- Contact: + +> **Note** +> Developed by the Chainlit team for seamless integration. +> Literal AI is SOC 2 Type 2 compliant. diff --git a/packages/data_layers/sqlalchemy/README.md b/packages/data_layers/sqlalchemy/README.md index 49971fa..bf0483e 100644 --- a/packages/data_layers/sqlalchemy/README.md +++ b/packages/data_layers/sqlalchemy/README.md @@ -2,4 +2,66 @@ SQLAlchemy data layer for [Chainlit](https://chainlit.io/). -## Usage +## SQLAlchemy Data Layer + +This data layer supports PostgreSQL and other SQL databases via SQLAlchemy. + +Key features: + +- Supports storage clients for attachments (currently: Azure, Azure Blobs, GCS, S3) +- User/thread/step/element/feedback storage in SQL +- Async operations + +## Setup Example (PostgreSQL + Azure Blob) + +1. Load [schema.sql](schema.sql) into your database. +1. Install required dependencies: + +```bash +# For PostgreSQL +pip install chainlit-sqlalchemy[postgres] + +# For SQLite +pip install chainlit-sqlalchemy[sqlite] + +# With cloud storage +pip install chainlit-sqlalchemy[postgres,gcs] # PostgreSQL + Google Cloud Storage +pip install chainlit-sqlalchemy[sqlite,azure-blob] # SQLite + Azure Blob +``` + +2. Configure in your Chainlit app: + +```python +import chainlit as cl +from chainlit.data.sql_alchemy import SQLAlchemyDataLayer +from chainlit.data.storage_clients import AzureBlobStorageClient + +@cl.data_layer +def get_data_layer(): + storage_client = AzureBlobStorageClient( + container_name="", + storage_account="", + storage_key="" + ) + + return SQLAlchemyDataLayer( + conninfo="postgresql+asyncpg://user:password@host/dbname", + storage_provider=storage_client + ) +``` + +> [!NOTE] +> - Add `+asyncpg` to PostgreSQL connection strings for async support +> - See SQLAlchemy docs for other database connection formats + +## Dependencies + +- Core: `SQLAlchemy` +- Database Drivers (optional): + - PostgreSQL: `asyncpg`, `psycopg2-binary` + - SQLite: `aiosqlite` +- Cloud Storage (optional): + - Azure (Data Lake): `chainlit-azure` + - Azure Blob: `chainlit-azure-blob` + - Google Cloud: `chainlit-gcs` + - AWS S3: `chainlit-s3` diff --git a/packages/data_layers/sqlalchemy/pyproject.toml b/packages/data_layers/sqlalchemy/pyproject.toml index 6797afb..e7e3c45 100644 --- a/packages/data_layers/sqlalchemy/pyproject.toml +++ b/packages/data_layers/sqlalchemy/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "chainlit-sqlalchemy" version = "0.1.0" -description = "Add your description here" +description = "SQLAlchemy data layer for Chainlit." readme = "README.md" requires-python = ">=3.10" dependencies = [ @@ -10,11 +10,24 @@ dependencies = [ ] [project.optional-dependencies] +postgres = [ + "asyncpg>=0.29.0", + "psycopg2-binary>=2.9.9" +] +sqlite = [ + "aiosqlite>=0.20.0" +] azure = [ - "chainlit-azure", + "chainlit-azure", +] +azure-blob = [ + "chainlit-azure-blob", +] +gcs = [ + "chainlit-gcs", ] s3 = [ - "chainlit-s3", + "chainlit-s3", ] [dependency-groups] @@ -25,6 +38,8 @@ dev = [ [tool.uv.sources] chainlit-azure = { workspace = true } +chainlit-azure-blob = { workspace = true } +chainlit-gcs = { workspace = true } chainlit-s3 = { workspace = true } pytest-chainlit = { workspace = true } diff --git a/packages/data_layers/sqlalchemy/schema.sql b/packages/data_layers/sqlalchemy/schema.sql new file mode 100644 index 0000000..0b12cb7 --- /dev/null +++ b/packages/data_layers/sqlalchemy/schema.sql @@ -0,0 +1,67 @@ +CREATE TABLE users ( + "id" UUID PRIMARY KEY, + "identifier" TEXT NOT NULL UNIQUE, + "metadata" JSONB NOT NULL, + "createdAt" TEXT +); + +CREATE TABLE IF NOT EXISTS threads ( + "id" UUID PRIMARY KEY, + "createdAt" TEXT, + "name" TEXT, + "userId" UUID, + "userIdentifier" TEXT, + "tags" TEXT[], + "metadata" JSONB, + FOREIGN KEY ("userId") REFERENCES users("id") ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS steps ( + "id" UUID PRIMARY KEY, + "name" TEXT NOT NULL, + "type" TEXT NOT NULL, + "threadId" UUID NOT NULL, + "parentId" UUID, + "streaming" BOOLEAN NOT NULL, + "waitForAnswer" BOOLEAN, + "isError" BOOLEAN, + "metadata" JSONB, + "tags" TEXT[], + "input" TEXT, + "output" TEXT, + "createdAt" TEXT, + "start" TEXT, + "end" TEXT, + "generation" JSONB, + "showInput" TEXT, + "language" TEXT, + "indent" INT, + FOREIGN KEY ("threadId") REFERENCES threads("id") ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS elements ( + "id" UUID PRIMARY KEY, + "threadId" UUID, + "type" TEXT, + "url" TEXT, + "chainlitKey" TEXT, + "name" TEXT NOT NULL, + "display" TEXT, + "objectKey" TEXT, + "size" TEXT, + "page" INT, + "language" TEXT, + "forId" UUID, + "mime" TEXT, + "props" JSONB, + FOREIGN KEY ("threadId") REFERENCES threads("id") ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS feedbacks ( + "id" UUID PRIMARY KEY, + "forId" UUID NOT NULL, + "threadId" UUID NOT NULL, + "value" INT NOT NULL, + "comment" TEXT, + FOREIGN KEY ("threadId") REFERENCES threads("id") ON DELETE CASCADE +); diff --git a/packages/storage_clients/azure/README.md b/packages/storage_clients/azure/README.md index 140d83f..d6c5d7e 100644 --- a/packages/storage_clients/azure/README.md +++ b/packages/storage_clients/azure/README.md @@ -1,5 +1,97 @@ # chainlit-azure -Azure Storage client for use with [Chainlit](https://chainlit.io/). +Azure Data Lake Storage Gen2 client for [Chainlit](https://chainlit.io/). -## Usage +## Azure Data Lake Storage Client + +This storage client enables integration with Azure Data Lake Storage Gen2 for file operations. + +Key features: +- File upload/delete operations +- Presigned URL generation +- SAS token support +- Multiple authentication methods (account key, connection string, SAS token) + +## Setup Example + +1. Install required dependencies: +```bash +pip install chainlit-azure[azure-datalake] +``` + +2. Configure in your Chainlit app: +```python +# Security Recommendation: +# Use Azure Managed Identity in production +# For local development, use azure-identity DefaultAzureCredential + +import os +from chainlit.data.sql_alchemy import SQLAlchemyDataLayer +from chainlit_azure import AzureStorageClient + +@cl.data_layer +def get_data_layer(): + # Example with connection string credential + storage_client = AzureStorageClient( + account_url="https://.dfs.core.windows.net", + container="", + credential=os.environ["AZURE_STORAGE_CONNECTION_STRING"] + ) + + return SQLAlchemyDataLayer( + conninfo=os.environ["DATABASE_URL"], + storage_provider=storage_client + ) +``` + +## Authentication Options + +### Connection String +```python +AzureStorageClient( + account_url="https://account.dfs.core.windows.net", + container="container", + credential="DefaultEndpointsProtocol=https;AccountName=...;AccountKey=..." +) +``` + +### Account Key +```python +from azure.identity import AzureNamedKeyCredential + +credential = AzureNamedKeyCredential("", "") +AzureStorageClient( + account_url="https://account.dfs.core.windows.net", + container="container", + credential=credential +) +``` + +### SAS Token +```python +AzureStorageClient( + account_url="https://account.dfs.core.windows.net", + container="container", + credential=None, # Not needed if using SAS + sas_token="" +) +``` + +## Dependencies + +- Required: `azure-storage-file-datalake` +- Optional: `azure-identity` (for token-based authentication) + +## Notes + +1. SAS tokens can be generated automatically if: + - Account key is provided + - No existing SAS token is configured + - Read permissions are required for presigned URLs + +2. Supported credential types: + - Connection strings + - Account keys + - AzureNamedKeyCredential + - AzureSasCredential + - TokenCredential (Azure Active Directory) diff --git a/packages/storage_clients/azure/pyproject.toml b/packages/storage_clients/azure/pyproject.toml index cd2df9f..e0992cd 100644 --- a/packages/storage_clients/azure/pyproject.toml +++ b/packages/storage_clients/azure/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "chainlit-azure" version = "0.1.0" -description = "Add your description here" +description = "Azure Datalake storage client for use with Chainlit." readme = "README.md" requires-python = ">=3.10" dependencies = [ diff --git a/packages/storage_clients/azure_blob/README.md b/packages/storage_clients/azure_blob/README.md index dfcf0c2..fc79ef8 100644 --- a/packages/storage_clients/azure_blob/README.md +++ b/packages/storage_clients/azure_blob/README.md @@ -1,5 +1,68 @@ # chainlit-azure-blob -Azure Blob Storage client for use with [Chainlit](https://chainlit.io/). +Azure Blob Storage integration for [Chainlit](https://chainlit.io/) applications. -## Usage +## Features + +- Seamless file management with Azure Blob Storage +- Automatic SAS token generation for secure access +- Integrated with Chainlit's data layer system +- Supports both development and production environments + +## Quick Start + +1. Install the package: +```bash +pip install chainlit-azure-blob[azure-blob] +``` + +2. Configure in your Chainlit app: +```python +import chainlit as cl +from chainlit.data.sql_alchemy import SQLAlchemyDataLayer +from chainlit_azure_blob import AzureBlobStorageClient + +@cl.data_layer +def get_data_layer(): + # Configure Azure Blob Storage + storage_client = AzureBlobStorageClient( + container_name="your-container", + storage_account="your-account", + storage_key="your-access-key" + ) + + return SQLAlchemyDataLayer( + conninfo="postgresql+asyncpg://user:password@host/dbname", + storage_provider=storage_client + ) +``` + +## Configuration + +### Required Parameters +- `container_name`: Your Azure storage container name +- `storage_account`: Azure storage account name +- `storage_key`: Storage account access key (store in environment variables) + +> **Security Warning** +> Always rotate storage keys regularly and monitor access logs + +```python +AzureBlobStorageClient( + container_name="chat-attachments", + storage_account="myappstorage", + storage_key="abc123..." +) +``` + +## Integration Notes + +- Files are automatically managed through Chainlit's storage system +- Presigned URLs use 1-hour valid SAS tokens +- Supports automatic MIME type detection +- Overwrite protection enabled by default + +## Dependencies + +- Azure Blob Storage SDK: `azure-storage-blob>=12.19.0` +- Chainlit compatibility: Requires `chainlit-sqlalchemy` data layer diff --git a/packages/storage_clients/azure_blob/pyproject.toml b/packages/storage_clients/azure_blob/pyproject.toml index 56f11e1..8f432c5 100644 --- a/packages/storage_clients/azure_blob/pyproject.toml +++ b/packages/storage_clients/azure_blob/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "chainlit-azure-blob" version = "0.1.0" -description = "Add your description here" +description = "Azure Blob Storage client for use with Chainlit." readme = "README.md" requires-python = ">=3.10" dependencies = [ diff --git a/packages/storage_clients/gcs/README.md b/packages/storage_clients/gcs/README.md index d03ee9a..7230973 100644 --- a/packages/storage_clients/gcs/README.md +++ b/packages/storage_clients/gcs/README.md @@ -1,5 +1,135 @@ # chainlit-gcs -Google Cloud Storage client for use with [Chainlit](https://chainlit.io/). +Google Cloud Storage client for [Chainlit](https://chainlit.io/). -## Usage +## Google Cloud Storage Client + +This storage client enables integration with Google Cloud Storage for file operations in Chainlit applications. + +Key features: +- File upload/delete operations +- Signed URL generation +- Service account authentication +- Base64-encoded private key support + +## Setup Example + +1. Install required dependencies: +```bash +pip install chainlit-gcs[google-cloud-storage] +``` + +2. **Configure environment**: +```bash +# .env file +# Security: Never commit this file to version control +GCS_PROJECT_ID="your-project-id" +GCS_CLIENT_EMAIL="service-account-email@project.iam.gserviceaccount.com" +GCS_PRIVATE_KEY="base64_encoded_private_key" +GCS_BUCKET_NAME="your-bucket-name" +``` + +3. **Configure in your Chainlit app**: +```python +import os +from chainlit.data.sql_alchemy import SQLAlchemyDataLayer +from chainlit_gcs import GCSStorageClient + +@cl.data_layer +def get_data_layer(): + """ + Security Best Practices: + 1. Store credentials in environment variables + 2. Use base64 encoding for private key + 3. Regularly rotate service account keys + 4. Grant minimum required permissions (Storage Object Admin) + """ + + # Decoding not needed - client handles base64 encoded key automatically + private_key = os.environ["GCS_PRIVATE_KEY"] + + storage_client = GCSStorageClient( + project_id="your-project-id", + client_email="service-account-email@project.iam.gserviceaccount.com", + private_key="base64_encoded_private_key", + bucket_name="your-bucket-name" + ) + + return SQLAlchemyDataLayer( + conninfo="postgresql+asyncpg://user:password@host/dbname", + storage_provider=storage_client + ) +``` + +## Service Account Setup Guide + +1. **Create Service Account**: + - Go to Google Cloud Console > IAM & Admin > Service Accounts + - Click "Create Service Account" + - Add "Storage Object Admin" role + +2. **Generate JSON Key**: + - Under Actions ⋮ for the service account, select "Manage Keys" + - Click "Add Key" > "Create New Key" > JSON + - The key file will download automatically + +3. **Encode Private Key**: +```bash +# Encode the private key from the JSON file +cat keyfile.json | jq -r .private_key | base64 -w 0 >> .env +``` + +## Authentication Configuration + +### Service Account Credentials +Obtain credentials from Google Cloud Console: +1. Create a service account with "Storage Admin" role +2. Generate JSON key file +3. Base64 encode the private key value + +Example credential configuration: +```python +GCSStorageClient( + project_id="your-project-id", + client_email="service-account-email@project.iam.gserviceaccount.com", + private_key="base64_encoded_private_key", # Original private key from JSON key file + bucket_name="your-bucket-name" +) +``` + +## Operations + +### File Upload +- Supports bytes or string data +- Automatic content type detection (default: application/octet-stream) +- Overwrite protection option + +### Signed URLs +- Automatically generates v4 signed URLs with expiration +- URLs valid for 1 hour by default + +## Dependencies + +- Required: `google-cloud-storage` +- Required: `google-oauth2` + +## Notes + +> **Critical Security Practices** +> - 🔒 Never commit service account keys to version control +> - 🔄 Rotate keys every 90 days minimum +> - 🛡️ Use Google Secret Manager for production deployments +> - 🔐 Set .env file permissions to 600 + +1. Private key handling: + - Must be base64 encoded before passing to constructor + - Original value from service account JSON key file ("private_key" field) + - Decoding is handled automatically by the client + +2. Bucket permissions: + - Service account requires "Storage Object Admin" IAM role + - Ensure bucket exists before operations + +3. Overwrite behavior: + - Defaults to True (existing files will be replaced) + - Set `overwrite=False` to prevent accidental overwrites diff --git a/packages/storage_clients/gcs/pyproject.toml b/packages/storage_clients/gcs/pyproject.toml index df0add7..96d3763 100644 --- a/packages/storage_clients/gcs/pyproject.toml +++ b/packages/storage_clients/gcs/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "chainlit-gcs" version = "0.1.0" -description = "Add your description here" +description = "Google Cloud Storage client for use with Chainlit." readme = "README.md" requires-python = ">=3.10" dependencies = [ diff --git a/packages/storage_clients/s3/README.md b/packages/storage_clients/s3/README.md index e69de29..105e5d0 100644 --- a/packages/storage_clients/s3/README.md +++ b/packages/storage_clients/s3/README.md @@ -0,0 +1,127 @@ +# chainlit-s3 + +Amazon S3 storage client for [Chainlit](https://chainlit.io/). + +## Amazon S3 Storage Client + +This storage client enables integration with Amazon S3 for file operations. + +Key features: +- File upload/delete operations +- Presigned URL generation +- Automatic URL construction for public objects +- Support for multiple authentication methods +- Seamless integration with boto3 client configuration + +## Setup Example + +1. Install required dependencies: +```bash +pip install chainlit-s3[s3] +``` + +2. Configure in your Chainlit app: +```python +# Security Best Practice: +# Prefer IAM roles over access keys for production workloads + +import os +from chainlit.data.sql_alchemy import SQLAlchemyDataLayer +from chainlit_s3 import S3StorageClient + +@cl.data_layer +def get_data_layer(): + # Example with explicit credentials + storage_client = S3StorageClient( + bucket="your-bucket-name", + aws_access_key_id=os.environ["AWS_ACCESS_KEY_ID"], + aws_secret_access_key=os.environ["AWS_SECRET_ACCESS_KEY"], + region_name="us-west-2" + ) + + return SQLAlchemyDataLayer( + conninfo="postgresql+asyncpg://user:password@host/dbname", + storage_provider=storage_client + ) +``` + +## Authentication Options + +### Access Key/Secret +```python +S3StorageClient( + bucket="your-bucket", + aws_access_key_id="", + aws_secret_access_key="", + region_name="us-west-2" +) +``` + +### IAM Role +```python +# When running in AWS environment with configured IAM role +S3StorageClient(bucket="your-bucket") +``` + +### Session Token +```python +S3StorageClient( + bucket="your-bucket", + aws_access_key_id="", + aws_secret_access_key="", + aws_session_token="", + region_name="us-west-2" +) +``` + +## Dependencies + +- Required: `boto3` + +## Features + +### Presigned URLs +Automatically generates presigned URLs with default 1-hour expiration: +```python +url = await storage_client.get_read_url("path/to/object") +``` + +### Upload Behavior +- Supports both bytes and string data +- Automatic MIME type detection (default: application/octet-stream) +- Returns public URL format: `https://{bucket}.s3.amazonaws.com/{object_key}` + +### Error Handling +- Failed operations return empty dict/False +- Detailed warnings logged for troubleshooting + +## Notes + +1. Required IAM Permissions: +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "s3:PutObject", + "s3:GetObject", + "s3:DeleteObject" + ], + "Resource": "arn:aws:s3:::your-bucket/*" + } + ] +} +``` + +2. Supported boto3 client parameters can be passed directly to the constructor: +```python +S3StorageClient( + bucket="your-bucket", + endpoint_url="http://localhost:4566", # For LocalStack + config=Config(connect_timeout=30) +) +``` + +3. Public URL format assumes standard S3 URL structure. For custom domains, subclass and override `sync_upload_file`. diff --git a/packages/storage_clients/s3/pyproject.toml b/packages/storage_clients/s3/pyproject.toml index bcf37dc..93bb565 100644 --- a/packages/storage_clients/s3/pyproject.toml +++ b/packages/storage_clients/s3/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "chainlit-s3" version = "0.1.0" -description = "Add your description here" +description = "Amazon S3 storage client for use with Chainlit." readme = "README.md" requires-python = ">=3.10" dependencies = [ diff --git a/uv.lock b/uv.lock index 3ac9949..15ee1e7 100644 --- a/uv.lock +++ b/uv.lock @@ -183,6 +183,49 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3e/4b/40a1dc52fc26695b1e80a9e67dfb0fe7e6ddc57bbc5b61348e40c0045abb/asyncer-0.0.7-py3-none-any.whl", hash = "sha256:f0d579d4f67c4ead52ede3a45c854f462cae569058a8a6a68a4ebccac1c335d8", size = 8476 }, ] +[[package]] +name = "asyncpg" +version = "0.30.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "async-timeout", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2f/4c/7c991e080e106d854809030d8584e15b2e996e26f16aee6d757e387bc17d/asyncpg-0.30.0.tar.gz", hash = "sha256:c551e9928ab6707602f44811817f82ba3c446e018bfe1d3abecc8ba5f3eac851", size = 957746 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bb/07/1650a8c30e3a5c625478fa8aafd89a8dd7d85999bf7169b16f54973ebf2c/asyncpg-0.30.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bfb4dd5ae0699bad2b233672c8fc5ccbd9ad24b89afded02341786887e37927e", size = 673143 }, + { url = "https://files.pythonhosted.org/packages/a0/9a/568ff9b590d0954553c56806766914c149609b828c426c5118d4869111d3/asyncpg-0.30.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dc1f62c792752a49f88b7e6f774c26077091b44caceb1983509edc18a2222ec0", size = 645035 }, + { url = "https://files.pythonhosted.org/packages/de/11/6f2fa6c902f341ca10403743701ea952bca896fc5b07cc1f4705d2bb0593/asyncpg-0.30.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3152fef2e265c9c24eec4ee3d22b4f4d2703d30614b0b6753e9ed4115c8a146f", size = 2912384 }, + { url = "https://files.pythonhosted.org/packages/83/83/44bd393919c504ffe4a82d0aed8ea0e55eb1571a1dea6a4922b723f0a03b/asyncpg-0.30.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7255812ac85099a0e1ffb81b10dc477b9973345793776b128a23e60148dd1af", size = 2947526 }, + { url = "https://files.pythonhosted.org/packages/08/85/e23dd3a2b55536eb0ded80c457b0693352262dc70426ef4d4a6fc994fa51/asyncpg-0.30.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:578445f09f45d1ad7abddbff2a3c7f7c291738fdae0abffbeb737d3fc3ab8b75", size = 2895390 }, + { url = "https://files.pythonhosted.org/packages/9b/26/fa96c8f4877d47dc6c1864fef5500b446522365da3d3d0ee89a5cce71a3f/asyncpg-0.30.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c42f6bb65a277ce4d93f3fba46b91a265631c8df7250592dd4f11f8b0152150f", size = 3015630 }, + { url = "https://files.pythonhosted.org/packages/34/00/814514eb9287614188a5179a8b6e588a3611ca47d41937af0f3a844b1b4b/asyncpg-0.30.0-cp310-cp310-win32.whl", hash = "sha256:aa403147d3e07a267ada2ae34dfc9324e67ccc4cdca35261c8c22792ba2b10cf", size = 568760 }, + { url = "https://files.pythonhosted.org/packages/f0/28/869a7a279400f8b06dd237266fdd7220bc5f7c975348fea5d1e6909588e9/asyncpg-0.30.0-cp310-cp310-win_amd64.whl", hash = "sha256:fb622c94db4e13137c4c7f98834185049cc50ee01d8f657ef898b6407c7b9c50", size = 625764 }, + { url = "https://files.pythonhosted.org/packages/4c/0e/f5d708add0d0b97446c402db7e8dd4c4183c13edaabe8a8500b411e7b495/asyncpg-0.30.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5e0511ad3dec5f6b4f7a9e063591d407eee66b88c14e2ea636f187da1dcfff6a", size = 674506 }, + { url = "https://files.pythonhosted.org/packages/6a/a0/67ec9a75cb24a1d99f97b8437c8d56da40e6f6bd23b04e2f4ea5d5ad82ac/asyncpg-0.30.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:915aeb9f79316b43c3207363af12d0e6fd10776641a7de8a01212afd95bdf0ed", size = 645922 }, + { url = "https://files.pythonhosted.org/packages/5c/d9/a7584f24174bd86ff1053b14bb841f9e714380c672f61c906eb01d8ec433/asyncpg-0.30.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c198a00cce9506fcd0bf219a799f38ac7a237745e1d27f0e1f66d3707c84a5a", size = 3079565 }, + { url = "https://files.pythonhosted.org/packages/a0/d7/a4c0f9660e333114bdb04d1a9ac70db690dd4ae003f34f691139a5cbdae3/asyncpg-0.30.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3326e6d7381799e9735ca2ec9fd7be4d5fef5dcbc3cb555d8a463d8460607956", size = 3109962 }, + { url = "https://files.pythonhosted.org/packages/3c/21/199fd16b5a981b1575923cbb5d9cf916fdc936b377e0423099f209e7e73d/asyncpg-0.30.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:51da377487e249e35bd0859661f6ee2b81db11ad1f4fc036194bc9cb2ead5056", size = 3064791 }, + { url = "https://files.pythonhosted.org/packages/77/52/0004809b3427534a0c9139c08c87b515f1c77a8376a50ae29f001e53962f/asyncpg-0.30.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bc6d84136f9c4d24d358f3b02be4b6ba358abd09f80737d1ac7c444f36108454", size = 3188696 }, + { url = "https://files.pythonhosted.org/packages/52/cb/fbad941cd466117be58b774a3f1cc9ecc659af625f028b163b1e646a55fe/asyncpg-0.30.0-cp311-cp311-win32.whl", hash = "sha256:574156480df14f64c2d76450a3f3aaaf26105869cad3865041156b38459e935d", size = 567358 }, + { url = "https://files.pythonhosted.org/packages/3c/0a/0a32307cf166d50e1ad120d9b81a33a948a1a5463ebfa5a96cc5606c0863/asyncpg-0.30.0-cp311-cp311-win_amd64.whl", hash = "sha256:3356637f0bd830407b5597317b3cb3571387ae52ddc3bca6233682be88bbbc1f", size = 629375 }, + { url = "https://files.pythonhosted.org/packages/4b/64/9d3e887bb7b01535fdbc45fbd5f0a8447539833b97ee69ecdbb7a79d0cb4/asyncpg-0.30.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c902a60b52e506d38d7e80e0dd5399f657220f24635fee368117b8b5fce1142e", size = 673162 }, + { url = "https://files.pythonhosted.org/packages/6e/eb/8b236663f06984f212a087b3e849731f917ab80f84450e943900e8ca4052/asyncpg-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aca1548e43bbb9f0f627a04666fedaca23db0a31a84136ad1f868cb15deb6e3a", size = 637025 }, + { url = "https://files.pythonhosted.org/packages/cc/57/2dc240bb263d58786cfaa60920779af6e8d32da63ab9ffc09f8312bd7a14/asyncpg-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c2a2ef565400234a633da0eafdce27e843836256d40705d83ab7ec42074efb3", size = 3496243 }, + { url = "https://files.pythonhosted.org/packages/f4/40/0ae9d061d278b10713ea9021ef6b703ec44698fe32178715a501ac696c6b/asyncpg-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1292b84ee06ac8a2ad8e51c7475aa309245874b61333d97411aab835c4a2f737", size = 3575059 }, + { url = "https://files.pythonhosted.org/packages/c3/75/d6b895a35a2c6506952247640178e5f768eeb28b2e20299b6a6f1d743ba0/asyncpg-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0f5712350388d0cd0615caec629ad53c81e506b1abaaf8d14c93f54b35e3595a", size = 3473596 }, + { url = "https://files.pythonhosted.org/packages/c8/e7/3693392d3e168ab0aebb2d361431375bd22ffc7b4a586a0fc060d519fae7/asyncpg-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:db9891e2d76e6f425746c5d2da01921e9a16b5a71a1c905b13f30e12a257c4af", size = 3641632 }, + { url = "https://files.pythonhosted.org/packages/32/ea/15670cea95745bba3f0352341db55f506a820b21c619ee66b7d12ea7867d/asyncpg-0.30.0-cp312-cp312-win32.whl", hash = "sha256:68d71a1be3d83d0570049cd1654a9bdfe506e794ecc98ad0873304a9f35e411e", size = 560186 }, + { url = "https://files.pythonhosted.org/packages/7e/6b/fe1fad5cee79ca5f5c27aed7bd95baee529c1bf8a387435c8ba4fe53d5c1/asyncpg-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:9a0292c6af5c500523949155ec17b7fe01a00ace33b68a476d6b5059f9630305", size = 621064 }, + { url = "https://files.pythonhosted.org/packages/3a/22/e20602e1218dc07692acf70d5b902be820168d6282e69ef0d3cb920dc36f/asyncpg-0.30.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:05b185ebb8083c8568ea8a40e896d5f7af4b8554b64d7719c0eaa1eb5a5c3a70", size = 670373 }, + { url = "https://files.pythonhosted.org/packages/3d/b3/0cf269a9d647852a95c06eb00b815d0b95a4eb4b55aa2d6ba680971733b9/asyncpg-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c47806b1a8cbb0a0db896f4cd34d89942effe353a5035c62734ab13b9f938da3", size = 634745 }, + { url = "https://files.pythonhosted.org/packages/8e/6d/a4f31bf358ce8491d2a31bfe0d7bcf25269e80481e49de4d8616c4295a34/asyncpg-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b6fde867a74e8c76c71e2f64f80c64c0f3163e687f1763cfaf21633ec24ec33", size = 3512103 }, + { url = "https://files.pythonhosted.org/packages/96/19/139227a6e67f407b9c386cb594d9628c6c78c9024f26df87c912fabd4368/asyncpg-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46973045b567972128a27d40001124fbc821c87a6cade040cfcd4fa8a30bcdc4", size = 3592471 }, + { url = "https://files.pythonhosted.org/packages/67/e4/ab3ca38f628f53f0fd28d3ff20edff1c975dd1cb22482e0061916b4b9a74/asyncpg-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9110df111cabc2ed81aad2f35394a00cadf4f2e0635603db6ebbd0fc896f46a4", size = 3496253 }, + { url = "https://files.pythonhosted.org/packages/ef/5f/0bf65511d4eeac3a1f41c54034a492515a707c6edbc642174ae79034d3ba/asyncpg-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:04ff0785ae7eed6cc138e73fc67b8e51d54ee7a3ce9b63666ce55a0bf095f7ba", size = 3662720 }, + { url = "https://files.pythonhosted.org/packages/e7/31/1513d5a6412b98052c3ed9158d783b1e09d0910f51fbe0e05f56cc370bc4/asyncpg-0.30.0-cp313-cp313-win32.whl", hash = "sha256:ae374585f51c2b444510cdf3595b97ece4f233fde739aa14b50e0d64e8a7a590", size = 560404 }, + { url = "https://files.pythonhosted.org/packages/c8/a4/cec76b3389c4c5ff66301cd100fe88c318563ec8a520e0b2e792b5b84972/asyncpg-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:f59b430b8e27557c3fb9869222559f7417ced18688375825f8f12302c34e915e", size = 621623 }, +] + [[package]] name = "attrs" version = "24.2.0" @@ -490,6 +533,20 @@ dependencies = [ { name = "boto3" }, ] +[package.optional-dependencies] +azure = [ + { name = "chainlit-azure" }, +] +azure-blob = [ + { name = "chainlit-azure-blob" }, +] +gcs = [ + { name = "chainlit-gcs" }, +] +s3 = [ + { name = "chainlit-s3" }, +] + [package.dev-dependencies] dev = [ { name = "pytest-chainlit" }, @@ -499,6 +556,10 @@ dev = [ requires-dist = [ { name = "aiohttp", specifier = ">=3.11.10" }, { name = "boto3", specifier = ">=1.34.73,<2" }, + { name = "chainlit-azure", marker = "extra == 'azure'", editable = "packages/storage_clients/azure" }, + { name = "chainlit-azure-blob", marker = "extra == 'azure-blob'", editable = "packages/storage_clients/azure_blob" }, + { name = "chainlit-gcs", marker = "extra == 'gcs'", editable = "packages/storage_clients/gcs" }, + { name = "chainlit-s3", marker = "extra == 's3'", editable = "packages/storage_clients/s3" }, ] [package.metadata.requires-dev] @@ -580,9 +641,22 @@ dependencies = [ azure = [ { name = "chainlit-azure" }, ] +azure-blob = [ + { name = "chainlit-azure-blob" }, +] +gcs = [ + { name = "chainlit-gcs" }, +] +postgres = [ + { name = "asyncpg" }, + { name = "psycopg2-binary" }, +] s3 = [ { name = "chainlit-s3" }, ] +sqlite = [ + { name = "aiosqlite" }, +] [package.dev-dependencies] dev = [ @@ -593,8 +667,13 @@ dev = [ [package.metadata] requires-dist = [ { name = "aiohttp", specifier = ">=3.11.10" }, + { name = "aiosqlite", marker = "extra == 'sqlite'", specifier = ">=0.20.0" }, + { name = "asyncpg", marker = "extra == 'postgres'", specifier = ">=0.29.0" }, { name = "chainlit-azure", marker = "extra == 'azure'", editable = "packages/storage_clients/azure" }, + { name = "chainlit-azure-blob", marker = "extra == 'azure-blob'", editable = "packages/storage_clients/azure_blob" }, + { name = "chainlit-gcs", marker = "extra == 'gcs'", editable = "packages/storage_clients/gcs" }, { name = "chainlit-s3", marker = "extra == 's3'", editable = "packages/storage_clients/s3" }, + { name = "psycopg2-binary", marker = "extra == 'postgres'", specifier = ">=2.9.9" }, { name = "sqlalchemy", extras = ["asyncio"], specifier = ">=2.0.28,<3" }, ] @@ -1755,6 +1834,61 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3b/24/c8c49df8f6587719e1d400109b16c10c6902d0c9adddc8fff82840146f99/protobuf-5.29.1-py3-none-any.whl", hash = "sha256:32600ddb9c2a53dedc25b8581ea0f1fd8ea04956373c0c07577ce58d312522e0", size = 172547 }, ] +[[package]] +name = "psycopg2-binary" +version = "2.9.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cb/0e/bdc8274dc0585090b4e3432267d7be4dfbfd8971c0fa59167c711105a6bf/psycopg2-binary-2.9.10.tar.gz", hash = "sha256:4b3df0e6990aa98acda57d983942eff13d824135fe2250e6522edaa782a06de2", size = 385764 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7a/81/331257dbf2801cdb82105306042f7a1637cc752f65f2bb688188e0de5f0b/psycopg2_binary-2.9.10-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:0ea8e3d0ae83564f2fc554955d327fa081d065c8ca5cc6d2abb643e2c9c1200f", size = 3043397 }, + { url = "https://files.pythonhosted.org/packages/e7/9a/7f4f2f031010bbfe6a02b4a15c01e12eb6b9b7b358ab33229f28baadbfc1/psycopg2_binary-2.9.10-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:3e9c76f0ac6f92ecfc79516a8034a544926430f7b080ec5a0537bca389ee0906", size = 3274806 }, + { url = "https://files.pythonhosted.org/packages/e5/57/8ddd4b374fa811a0b0a0f49b6abad1cde9cb34df73ea3348cc283fcd70b4/psycopg2_binary-2.9.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ad26b467a405c798aaa1458ba09d7e2b6e5f96b1ce0ac15d82fd9f95dc38a92", size = 2851361 }, + { url = "https://files.pythonhosted.org/packages/f9/66/d1e52c20d283f1f3a8e7e5c1e06851d432f123ef57b13043b4f9b21ffa1f/psycopg2_binary-2.9.10-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:270934a475a0e4b6925b5f804e3809dd5f90f8613621d062848dd82f9cd62007", size = 3080836 }, + { url = "https://files.pythonhosted.org/packages/a0/cb/592d44a9546aba78f8a1249021fe7c59d3afb8a0ba51434d6610cc3462b6/psycopg2_binary-2.9.10-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:48b338f08d93e7be4ab2b5f1dbe69dc5e9ef07170fe1f86514422076d9c010d0", size = 3264552 }, + { url = "https://files.pythonhosted.org/packages/64/33/c8548560b94b7617f203d7236d6cdf36fe1a5a3645600ada6efd79da946f/psycopg2_binary-2.9.10-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f4152f8f76d2023aac16285576a9ecd2b11a9895373a1f10fd9db54b3ff06b4", size = 3019789 }, + { url = "https://files.pythonhosted.org/packages/b0/0e/c2da0db5bea88a3be52307f88b75eec72c4de62814cbe9ee600c29c06334/psycopg2_binary-2.9.10-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:32581b3020c72d7a421009ee1c6bf4a131ef5f0a968fab2e2de0c9d2bb4577f1", size = 2871776 }, + { url = "https://files.pythonhosted.org/packages/15/d7/774afa1eadb787ddf41aab52d4c62785563e29949613c958955031408ae6/psycopg2_binary-2.9.10-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:2ce3e21dc3437b1d960521eca599d57408a695a0d3c26797ea0f72e834c7ffe5", size = 2820959 }, + { url = "https://files.pythonhosted.org/packages/5e/ed/440dc3f5991a8c6172a1cde44850ead0e483a375277a1aef7cfcec00af07/psycopg2_binary-2.9.10-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e984839e75e0b60cfe75e351db53d6db750b00de45644c5d1f7ee5d1f34a1ce5", size = 2919329 }, + { url = "https://files.pythonhosted.org/packages/03/be/2cc8f4282898306732d2ae7b7378ae14e8df3c1231b53579efa056aae887/psycopg2_binary-2.9.10-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3c4745a90b78e51d9ba06e2088a2fe0c693ae19cc8cb051ccda44e8df8a6eb53", size = 2957659 }, + { url = "https://files.pythonhosted.org/packages/d0/12/fb8e4f485d98c570e00dad5800e9a2349cfe0f71a767c856857160d343a5/psycopg2_binary-2.9.10-cp310-cp310-win32.whl", hash = "sha256:e5720a5d25e3b99cd0dc5c8a440570469ff82659bb09431c1439b92caf184d3b", size = 1024605 }, + { url = "https://files.pythonhosted.org/packages/22/4f/217cd2471ecf45d82905dd09085e049af8de6cfdc008b6663c3226dc1c98/psycopg2_binary-2.9.10-cp310-cp310-win_amd64.whl", hash = "sha256:3c18f74eb4386bf35e92ab2354a12c17e5eb4d9798e4c0ad3a00783eae7cd9f1", size = 1163817 }, + { url = "https://files.pythonhosted.org/packages/9c/8f/9feb01291d0d7a0a4c6a6bab24094135c2b59c6a81943752f632c75896d6/psycopg2_binary-2.9.10-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:04392983d0bb89a8717772a193cfaac58871321e3ec69514e1c4e0d4957b5aff", size = 3043397 }, + { url = "https://files.pythonhosted.org/packages/15/30/346e4683532011561cd9c8dfeac6a8153dd96452fee0b12666058ab7893c/psycopg2_binary-2.9.10-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:1a6784f0ce3fec4edc64e985865c17778514325074adf5ad8f80636cd029ef7c", size = 3274806 }, + { url = "https://files.pythonhosted.org/packages/66/6e/4efebe76f76aee7ec99166b6c023ff8abdc4e183f7b70913d7c047701b79/psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5f86c56eeb91dc3135b3fd8a95dc7ae14c538a2f3ad77a19645cf55bab1799c", size = 2851370 }, + { url = "https://files.pythonhosted.org/packages/7f/fd/ff83313f86b50f7ca089b161b8e0a22bb3c319974096093cd50680433fdb/psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b3d2491d4d78b6b14f76881905c7a8a8abcf974aad4a8a0b065273a0ed7a2cb", size = 3080780 }, + { url = "https://files.pythonhosted.org/packages/e6/c4/bfadd202dcda8333a7ccafdc51c541dbdfce7c2c7cda89fa2374455d795f/psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2286791ececda3a723d1910441c793be44625d86d1a4e79942751197f4d30341", size = 3264583 }, + { url = "https://files.pythonhosted.org/packages/5d/f1/09f45ac25e704ac954862581f9f9ae21303cc5ded3d0b775532b407f0e90/psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:512d29bb12608891e349af6a0cccedce51677725a921c07dba6342beaf576f9a", size = 3019831 }, + { url = "https://files.pythonhosted.org/packages/9e/2e/9beaea078095cc558f215e38f647c7114987d9febfc25cb2beed7c3582a5/psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5a507320c58903967ef7384355a4da7ff3f28132d679aeb23572753cbf2ec10b", size = 2871822 }, + { url = "https://files.pythonhosted.org/packages/01/9e/ef93c5d93f3dc9fc92786ffab39e323b9aed066ba59fdc34cf85e2722271/psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6d4fa1079cab9018f4d0bd2db307beaa612b0d13ba73b5c6304b9fe2fb441ff7", size = 2820975 }, + { url = "https://files.pythonhosted.org/packages/a5/f0/049e9631e3268fe4c5a387f6fc27e267ebe199acf1bc1bc9cbde4bd6916c/psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:851485a42dbb0bdc1edcdabdb8557c09c9655dfa2ca0460ff210522e073e319e", size = 2919320 }, + { url = "https://files.pythonhosted.org/packages/dc/9a/bcb8773b88e45fb5a5ea8339e2104d82c863a3b8558fbb2aadfe66df86b3/psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:35958ec9e46432d9076286dda67942ed6d968b9c3a6a2fd62b48939d1d78bf68", size = 2957617 }, + { url = "https://files.pythonhosted.org/packages/e2/6b/144336a9bf08a67d217b3af3246abb1d027095dab726f0687f01f43e8c03/psycopg2_binary-2.9.10-cp311-cp311-win32.whl", hash = "sha256:ecced182e935529727401b24d76634a357c71c9275b356efafd8a2a91ec07392", size = 1024618 }, + { url = "https://files.pythonhosted.org/packages/61/69/3b3d7bd583c6d3cbe5100802efa5beacaacc86e37b653fc708bf3d6853b8/psycopg2_binary-2.9.10-cp311-cp311-win_amd64.whl", hash = "sha256:ee0e8c683a7ff25d23b55b11161c2663d4b099770f6085ff0a20d4505778d6b4", size = 1163816 }, + { url = "https://files.pythonhosted.org/packages/49/7d/465cc9795cf76f6d329efdafca74693714556ea3891813701ac1fee87545/psycopg2_binary-2.9.10-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:880845dfe1f85d9d5f7c412efea7a08946a46894537e4e5d091732eb1d34d9a0", size = 3044771 }, + { url = "https://files.pythonhosted.org/packages/8b/31/6d225b7b641a1a2148e3ed65e1aa74fc86ba3fee850545e27be9e1de893d/psycopg2_binary-2.9.10-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9440fa522a79356aaa482aa4ba500b65f28e5d0e63b801abf6aa152a29bd842a", size = 3275336 }, + { url = "https://files.pythonhosted.org/packages/30/b7/a68c2b4bff1cbb1728e3ec864b2d92327c77ad52edcd27922535a8366f68/psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3923c1d9870c49a2d44f795df0c889a22380d36ef92440ff618ec315757e539", size = 2851637 }, + { url = "https://files.pythonhosted.org/packages/0b/b1/cfedc0e0e6f9ad61f8657fd173b2f831ce261c02a08c0b09c652b127d813/psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b2c956c028ea5de47ff3a8d6b3cc3330ab45cf0b7c3da35a2d6ff8420896526", size = 3082097 }, + { url = "https://files.pythonhosted.org/packages/18/ed/0a8e4153c9b769f59c02fb5e7914f20f0b2483a19dae7bf2db54b743d0d0/psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f758ed67cab30b9a8d2833609513ce4d3bd027641673d4ebc9c067e4d208eec1", size = 3264776 }, + { url = "https://files.pythonhosted.org/packages/10/db/d09da68c6a0cdab41566b74e0a6068a425f077169bed0946559b7348ebe9/psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cd9b4f2cfab88ed4a9106192de509464b75a906462fb846b936eabe45c2063e", size = 3020968 }, + { url = "https://files.pythonhosted.org/packages/94/28/4d6f8c255f0dfffb410db2b3f9ac5218d959a66c715c34cac31081e19b95/psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dc08420625b5a20b53551c50deae6e231e6371194fa0651dbe0fb206452ae1f", size = 2872334 }, + { url = "https://files.pythonhosted.org/packages/05/f7/20d7bf796593c4fea95e12119d6cc384ff1f6141a24fbb7df5a668d29d29/psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:d7cd730dfa7c36dbe8724426bf5612798734bff2d3c3857f36f2733f5bfc7c00", size = 2822722 }, + { url = "https://files.pythonhosted.org/packages/4d/e4/0c407ae919ef626dbdb32835a03b6737013c3cc7240169843965cada2bdf/psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:155e69561d54d02b3c3209545fb08938e27889ff5a10c19de8d23eb5a41be8a5", size = 2920132 }, + { url = "https://files.pythonhosted.org/packages/2d/70/aa69c9f69cf09a01da224909ff6ce8b68faeef476f00f7ec377e8f03be70/psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3cc28a6fd5a4a26224007712e79b81dbaee2ffb90ff406256158ec4d7b52b47", size = 2959312 }, + { url = "https://files.pythonhosted.org/packages/d3/bd/213e59854fafe87ba47814bf413ace0dcee33a89c8c8c814faca6bc7cf3c/psycopg2_binary-2.9.10-cp312-cp312-win32.whl", hash = "sha256:ec8a77f521a17506a24a5f626cb2aee7850f9b69a0afe704586f63a464f3cd64", size = 1025191 }, + { url = "https://files.pythonhosted.org/packages/92/29/06261ea000e2dc1e22907dbbc483a1093665509ea586b29b8986a0e56733/psycopg2_binary-2.9.10-cp312-cp312-win_amd64.whl", hash = "sha256:18c5ee682b9c6dd3696dad6e54cc7ff3a1a9020df6a5c0f861ef8bfd338c3ca0", size = 1164031 }, + { url = "https://files.pythonhosted.org/packages/3e/30/d41d3ba765609c0763505d565c4d12d8f3c79793f0d0f044ff5a28bf395b/psycopg2_binary-2.9.10-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:26540d4a9a4e2b096f1ff9cce51253d0504dca5a85872c7f7be23be5a53eb18d", size = 3044699 }, + { url = "https://files.pythonhosted.org/packages/35/44/257ddadec7ef04536ba71af6bc6a75ec05c5343004a7ec93006bee66c0bc/psycopg2_binary-2.9.10-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e217ce4d37667df0bc1c397fdcd8de5e81018ef305aed9415c3b093faaeb10fb", size = 3275245 }, + { url = "https://files.pythonhosted.org/packages/1b/11/48ea1cd11de67f9efd7262085588790a95d9dfcd9b8a687d46caf7305c1a/psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:245159e7ab20a71d989da00f280ca57da7641fa2cdcf71749c193cea540a74f7", size = 2851631 }, + { url = "https://files.pythonhosted.org/packages/62/e0/62ce5ee650e6c86719d621a761fe4bc846ab9eff8c1f12b1ed5741bf1c9b/psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c4ded1a24b20021ebe677b7b08ad10bf09aac197d6943bfe6fec70ac4e4690d", size = 3082140 }, + { url = "https://files.pythonhosted.org/packages/27/ce/63f946c098611f7be234c0dd7cb1ad68b0b5744d34f68062bb3c5aa510c8/psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3abb691ff9e57d4a93355f60d4f4c1dd2d68326c968e7db17ea96df3c023ef73", size = 3264762 }, + { url = "https://files.pythonhosted.org/packages/43/25/c603cd81402e69edf7daa59b1602bd41eb9859e2824b8c0855d748366ac9/psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8608c078134f0b3cbd9f89b34bd60a943b23fd33cc5f065e8d5f840061bd0673", size = 3020967 }, + { url = "https://files.pythonhosted.org/packages/5f/d6/8708d8c6fca531057fa170cdde8df870e8b6a9b136e82b361c65e42b841e/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:230eeae2d71594103cd5b93fd29d1ace6420d0b86f4778739cb1a5a32f607d1f", size = 2872326 }, + { url = "https://files.pythonhosted.org/packages/ce/ac/5b1ea50fc08a9df82de7e1771537557f07c2632231bbab652c7e22597908/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:bb89f0a835bcfc1d42ccd5f41f04870c1b936d8507c6df12b7737febc40f0909", size = 2822712 }, + { url = "https://files.pythonhosted.org/packages/c4/fc/504d4503b2abc4570fac3ca56eb8fed5e437bf9c9ef13f36b6621db8ef00/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f0c2d907a1e102526dd2986df638343388b94c33860ff3bbe1384130828714b1", size = 2920155 }, + { url = "https://files.pythonhosted.org/packages/b2/d1/323581e9273ad2c0dbd1902f3fb50c441da86e894b6e25a73c3fda32c57e/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f8157bed2f51db683f31306aa497311b560f2265998122abe1dce6428bd86567", size = 2959356 }, + { url = "https://files.pythonhosted.org/packages/08/50/d13ea0a054189ae1bc21af1d85b6f8bb9bbc5572991055d70ad9006fe2d6/psycopg2_binary-2.9.10-cp313-cp313-win_amd64.whl", hash = "sha256:27422aa5f11fbcd9b18da48373eb67081243662f9b46e6fd07c3eb46e4535142", size = 2569224 }, +] + [[package]] name = "pyasn1" version = "0.6.1"