cjm-fasthtml-byok


Namecjm-fasthtml-byok JSON
Version 0.0.3 PyPI version JSON
download
home_pagehttps://github.com/cj-mills/cjm-fasthtml-byok
SummarySecure API key management for FastHTML applications with encrypted storage, session/database persistence, and built-in UI components.
upload_time2025-10-24 21:46:08
maintainerNone
docs_urlNone
authorChristian J. Mills
requires_python>=3.9
licenseApache Software License 2.0
keywords nbdev jupyter notebook python
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # cjm-fasthtml-byok


<!-- WARNING: THIS FILE WAS AUTOGENERATED! DO NOT EDIT! -->

## Install

``` bash
pip install cjm_fasthtml_byok
```

## Project Structure

    nbs/
    ├── components/ (2)
    │   ├── alerts.ipynb  # FastHTML alert and notification components for user feedback
    │   └── forms.ipynb   # FastHTML form components for API key input and management
    ├── core/ (3)
    │   ├── security.ipynb  # Encryption and security utilities for API key management
    │   ├── storage.ipynb   # Storage backends for API keys (session and database)
    │   └── types.ipynb     # Type definitions and protocols for the BYOK system
    ├── middleware/ (1)
    │   └── beforeware.ipynb  # FastHTML beforeware for API key management
    └── utils/ (1)
        └── helpers.ipynb  # Helper functions for BYOK system

Total: 7 notebooks across 4 directories

## Module Dependencies

``` mermaid
graph LR
    components_alerts[components.alerts<br/>Alerts]
    components_forms[components.forms<br/>Forms]
    core_security[core.security<br/>Security]
    core_storage[core.storage<br/>Storage]
    core_types[core.types<br/>Types]
    middleware_beforeware[middleware.beforeware<br/>Beforeware]
    utils_helpers[utils.helpers<br/>Helpers]

    components_forms --> core_security
    components_forms --> utils_helpers
    core_security --> core_types
    core_storage --> core_types
    core_storage --> core_security
    middleware_beforeware --> core_security
    middleware_beforeware --> core_types
    middleware_beforeware --> core_storage
    utils_helpers --> core_security
```

*9 cross-module dependencies detected*

## CLI Reference

No CLI commands found in this project.

## Module Overview

Detailed documentation for each module in the project:

### Alerts (`alerts.ipynb`)

> FastHTML alert and notification components for user feedback

#### Import

``` python
from cjm_fasthtml_byok.components.alerts import (
    InfoIcon,
    SuccessIcon,
    WarningIcon,
    ErrorIcon,
    Alert,
    SecurityAlert,
    KeyStatusNotification,
    ToastContainer,
    Toast,
    ValidationMessage,
    AlertStack
)
```

#### Functions

``` python
def InfoIcon(
    size: str = "6"  # Size of the icon (matches Tailwind h-{size} and w-{size} classes)
) -> FT:  # SVG element for the info icon
    "Create an info icon SVG."
```

``` python
def SuccessIcon(
    size: str = "6"  # Size of the icon (matches Tailwind h-{size} and w-{size} classes)
) -> FT:  # SVG element for the success icon
    "Create a success/check icon SVG."
```

``` python
def WarningIcon(
    size: str = "6"  # Size of the icon (matches Tailwind h-{size} and w-{size} classes)
) -> FT:  # SVG element for the warning icon
    "Create a warning/exclamation icon SVG."
```

``` python
def ErrorIcon(
    size: str = "6"  # Size of the icon (matches Tailwind h-{size} and w-{size} classes)
) -> FT:  # SVG element for the error icon
    "Create an error/X icon SVG."
```

``` python
def Alert(
    message: str,  # The alert message
    kind: Literal["info", "success", "warning", "error"] = "info",
    title: Optional[str] = None,  # Optional title for the alert
    dismissible: bool = False,  # Whether the alert can be dismissed
    show_icon: bool = True,  # Whether to show an icon
    style: Optional[str] = None,  # Alert style ("soft", "outline", or None for default)
    id: Optional[str] = None  # HTML ID for the alert element
) -> FT:  # Alert component
    "Create an alert component for displaying messages."
```

``` python
def SecurityAlert(
    message: str,  # Security alert message
    severity: Literal["low", "medium", "high", "critical"] = "medium",
    action_url: Optional[str] = None,  # Optional URL for remediation action
    action_text: str = "Fix Now"  # Text for the action button
) -> FT:  # Security alert component
    "Create a security-focused alert with severity levels."
```

``` python
def KeyStatusNotification(
    provider: str,  # Provider name
    status: Literal["added", "updated", "deleted", "expired", "invalid"],
    masked_key: Optional[str] = None,  # Masked version of the key
    auto_dismiss: bool = True,  # Whether to auto-dismiss
    dismiss_after: int = 5000  # Milliseconds before auto-dismiss
) -> FT:  # Key status notification component
    "Create a notification for API key status changes."
```

``` python
def ToastContainer(
    position: Literal["top", "middle", "bottom"] = "top",
    align: Literal["start", "center", "end"] = "end",
    id: str = "toast-container"  # HTML ID for the container
) -> FT:  # Toast container component
    "Create a container for toast notifications."
```

``` python
def Toast(
    message: str,  # Toast message
    kind: Literal["info", "success", "warning", "error"] = "info",
    duration: int = 3000  # Duration in milliseconds
) -> FT:  # Toast notification component
    "Create a toast notification."
```

``` python
def ValidationMessage(
    message: str,  # Validation message
    is_valid: bool = False,  # Whether the validation passed
    show_icon: bool = True  # Whether to show an icon
) -> FT:  # Validation message component
    "Create an inline validation message for form fields."
```

``` python
def AlertStack(
    alerts: list,  # List of alert components
    max_visible: int = 3,  # Maximum number of visible alerts
    spacing: str = "4"  # Gap between alerts
) -> FT:  # Alert stack component
    "Create a stack of alerts with optional limit."
```

### Beforeware (`beforeware.ipynb`)

> FastHTML beforeware for API key management

#### Import

``` python
from cjm_fasthtml_byok.middleware.beforeware import (
    create_byok_beforeware,
    require_api_key,
    require_any_api_key,
    SecurityCheckBeforeware,
    CleanupBeforeware,
    setup_byok
)
```

#### Functions

``` python
def create_byok_beforeware(
    byok_manager: BYOKManager  # The BYOK manager instance
)
    "Create a FastHTML beforeware handler for BYOK functionality."
```

``` python
def require_api_key(
    provider: str,  # The provider name to check for
    user_id_func: Optional[Callable] = None  # Optional function to get user_id from request Usage: @rt @require_api_key("openai") def chat(request): byok = request.scope['byok'] api_key = byok.get_key(request, "openai") # Use the API key...
)
    "Decorator that requires an API key to be present for a route."
```

``` python
def require_any_api_key(
    providers: List[str],  # List of provider names to check
    user_id_func: Optional[Callable] = None  # Optional function to get user_id from request Usage: @rt @require_any_api_key(["openai", "anthropic", "google"]) def chat(request): # Use whichever API key is available pass
)
    "Decorator that requires at least one of the specified API keys."
```

``` python
def setup_byok(
    secret_key: str,  # Application secret key
    db: Optional[Any] = None,  # Optional database for persistent storage
    user_id_func: Optional[Callable] = None,  # Optional function to get user_id from request
    enable_security_checks: bool = True,  # Enable HTTPS checking
    enable_cleanup: bool = True  # Enable automatic cleanup of expired keys
)
    "Complete setup helper for BYOK with FastHTML. Returns beforeware functions and the BYOK manager."
```

#### Classes

``` python
class SecurityCheckBeforeware:
    def __init__(
        self,
        require_https: bool = True,  # Whether to require HTTPS in production
        is_production: Optional[bool] = None  # Whether running in production (auto-detected if None)
    )
    "Beforeware that performs security checks."
    
    def __init__(
            self,
            require_https: bool = True,  # Whether to require HTTPS in production
            is_production: Optional[bool] = None  # Whether running in production (auto-detected if None)
        )
        "Initialize security check beforeware with HTTPS requirements"
```

``` python
class CleanupBeforeware:
    def __init__(
        self,
        byok_manager: BYOKManager,  # The BYOK manager instance
        user_id_func: Optional[Callable] = None  # Optional function to get user_id from request
    )
    "Beforeware that cleans up expired keys."
    
    def __init__(
            self,
            byok_manager: BYOKManager,  # The BYOK manager instance
            user_id_func: Optional[Callable] = None  # Optional function to get user_id from request
        )
        "Initialize cleanup beforeware with BYOK manager"
```

### Forms (`forms.ipynb`)

> FastHTML form components for API key input and management

#### Import

``` python
from cjm_fasthtml_byok.components.forms import (
    KeyInputForm,
    MultiProviderKeyForm,
    KeyManagementCard,
    KeyManagerDashboard,
    InlineKeyInput
)
```

#### Functions

``` python
def KeyInputForm(
    provider: str,  # The API provider identifier
    action: Optional[str] = None,  # Form action URL (defaults to /api/keys/{provider})
    method: str = "post",  # HTTP method (default: "post")
    show_help: bool = True,  # Whether to show help text
    custom_placeholder: Optional[str] = None,  # Custom placeholder text
    extra_fields: Optional[List[tuple]] = None,  # Additional form fields as [(name, type, placeholder, required), ...]
    provider_config: Optional[Dict[str, Any]] = None  # Optional provider configuration
) -> FT:  # FastHTML Form component
    "Create a form for inputting an API key with improved design."
```

``` python
def MultiProviderKeyForm(
    providers: List[str],  # List of provider identifiers
    action: str = "/api/keys",  # Form action URL
    method: str = "post",  # HTTP method
    default_provider: Optional[str] = None,  # Initially selected provider
    provider_config: Optional[Dict[str, Any]] = None  # Optional provider configuration
) -> FT:  # FastHTML Form component with provider selection
    "Create a form that allows selecting from multiple providers with enhanced UX."
```

``` python
def KeyManagementCard(
    provider: str,  # Provider identifier
    has_key: bool,  # Whether a key is stored
    masked_key: Optional[str] = None,  # Masked version of the key for display
    created_at: Optional[str] = None,  # When the key was stored
    expires_at: Optional[str] = None,  # When the key expires
    delete_action: Optional[str] = None,  # URL for delete action
    update_action: Optional[str] = None,  # URL for update action
    provider_config: Optional[Dict[str, Any]] = None  # Optional provider configuration
) -> FT:  # Card component for key management
    "Create a card component for managing a stored API key with enhanced design."
```

``` python
def KeyManagerDashboard(
    request,  # FastHTML request object
    providers: List[str],  # List of provider identifiers to manage
    byok_manager = None,
    user_id: Optional[str] = None,  # Optional user ID for database storage
    base_url: str = "/api/keys",  # Base URL for API endpoints
    provider_config: Optional[Dict[str, Any]] = None  # Optional provider configuration
) -> FT:  # Dashboard component with all provider cards
    "Create a complete dashboard for managing multiple API keys with improved layout."
```

``` python
def InlineKeyInput(
    provider: str,  # Provider identifier
    input_id: Optional[str] = None,  # HTML ID for the input element
    on_save: Optional[str] = None,  # JavaScript to execute on save (or hx-post URL for HTMX)
    compact: bool = True,  # Whether to use compact styling
    provider_config: Optional[Dict[str, Any]] = None  # Optional provider configuration
) -> FT:  # Inline input component
    "Create a compact inline key input component with polished design."
```

### Helpers (`helpers.ipynb`)

> Helper functions for BYOK system

#### Import

``` python
from cjm_fasthtml_byok.utils.helpers import (
    get_provider_info,
    format_provider_name,
    format_key_age,
    format_expiration,
    get_key_summary,
    get_env_key,
    import_from_env
)
```

#### Functions

``` python
def get_provider_info(
    provider: str,  # Provider identifier
    provider_config: Optional[Dict[str, Any]] = None  # Optional provider configuration
) -> Dict[str, Any]:  # Provider info dict with defaults
    "Get provider information from config or generate defaults."
```

``` python
def format_provider_name(
    provider: str,  # Provider identifier
    provider_config: Optional[Dict[str, Any]] = None  # Optional provider configuration
) -> str:  # Formatted provider name
    "Format provider name for display."
```

``` python
def format_key_age(
    created_at: datetime  # When the key was created
) -> str:  # Human-readable age string
    "Format the age of a key for display."
```

``` python
def format_expiration(
    expires_at: Optional[datetime]  # Expiration datetime
) -> str:  # Human-readable expiration string
    "Format expiration time for display."
```

``` python
def get_key_summary(
    byok_manager,  # BYOK manager instance
    request,  # FastHTML request
    user_id: Optional[str] = None,  # Optional user ID
    provider_config: Optional[Dict[str, Any]] = None  # Optional provider configuration
) -> Dict[str, Any]:  # Summary dictionary with provider info
    "Get a summary of all stored keys."
```

``` python
def get_env_key(
    provider: str,  # Provider name
    env_prefix: str = "API_KEY_"  # Environment variable prefix
) -> Optional[str]:  # API key from environment or None
    "Get an API key from environment variables."
```

``` python
def import_from_env(
    byok_manager,  # BYOK manager instance
    request,  # FastHTML request
    providers: List[str],  # List of providers to check
    user_id: Optional[str] = None,  # Optional user ID
    env_prefix: str = "API_KEY_"  # Environment variable prefix
) -> Dict[str, bool]:  # Dict of provider: success status
    "Import API keys from environment variables."
```

### Security (`security.ipynb`)

> Encryption and security utilities for API key management

#### Import

``` python
from cjm_fasthtml_byok.core.security import (
    generate_encryption_key,
    get_or_create_app_key,
    KeyEncryptor,
    check_https,
    validate_environment,
    mask_key,
    get_key_fingerprint
)
```

#### Functions

``` python
def generate_encryption_key(
    password: Optional[str] = None,  # Optional password to derive key from
    salt: Optional[bytes] = None  # Optional salt for key derivation (required if password provided)
) -> bytes:  # 32-byte encryption key suitable for Fernet
    "Generate or derive an encryption key."
```

``` python
def get_or_create_app_key(
    "Get or create an app-specific encryption key derived from the app's secret key."
```

``` python
def check_https(
    request  # FastHTML/Starlette request object
) -> bool:  # True if using HTTPS, False otherwise
    "Check if the request is using HTTPS."
```

``` python
def validate_environment(
    request,  # FastHTML/Starlette request object
    require_https: bool = True,  # Whether to require HTTPS
    is_production: bool = None  # Whether running in production (auto-detected if None)
) -> None
    "Validate the security environment."
```

``` python
def mask_key(
    key: str,  # The API key to mask
    visible_chars: int = 4  # Number of characters to show at start and end
) -> str:  # Masked key like 'sk-a...xyz'
    "Mask an API key for display purposes."
```

``` python
def get_key_fingerprint(
    key: str  # The API key
) -> str:  # SHA256 fingerprint of the key (first 16 chars)
    "Generate a fingerprint for an API key (for logging/tracking without exposing the key)."
```

#### Classes

``` python
class KeyEncryptor:
    def __init__(
        self,
        encryption_key: Optional[bytes] = None  # Encryption key to use. If None, generates a new one
    )
    "Handles encryption and decryption of API keys."
    
    def __init__(
            self,
            encryption_key: Optional[bytes] = None  # Encryption key to use. If None, generates a new one
        )
        "Initialize the encryptor.

Args:
    encryption_key: Encryption key to use. If None, generates a new one."
    
    def encrypt(
            self,
            value: str  # Plain text API key to encrypt
        ) -> bytes:  # Encrypted bytes
        "Encrypt an API key value.

Args:
    value: Plain text API key

Returns:
    Encrypted bytes

Raises:
    EncryptionError: If encryption fails"
    
    def decrypt(
            self,
            encrypted_value: bytes  # Encrypted bytes to decrypt
        ) -> str:  # Decrypted plain text API key
        "Decrypt an API key value.

Args:
    encrypted_value: Encrypted bytes

Returns:
    Decrypted API key

Raises:
    EncryptionError: If decryption fails"
    
    def rotate_key(
            self,
            new_key: bytes,  # New encryption key to use
            encrypted_value: bytes  # Value encrypted with current key
        ) -> bytes:  # Value re-encrypted with new key
        "Re-encrypt a value with a new key.

Args:
    new_key: New encryption key
    encrypted_value: Value encrypted with current key

Returns:
    Value encrypted with new key"
```

### Storage (`storage.ipynb`)

> Storage backends for API keys (session and database)

#### Import

``` python
from cjm_fasthtml_byok.core.storage import (
    SessionStorage,
    DatabaseStorage,
    HybridStorage,
    BYOKManager
)
```

#### Classes

``` python
class SessionStorage:
    def __init__(
        self,
        config: BYOKConfig  # BYOK configuration object
    )
    """
    Session-based storage for API keys.
    Keys are stored in the user's session and expire with the session.
    """
    
    def __init__(
            self,
            config: BYOKConfig  # BYOK configuration object
        )
        "Initialize session storage with configuration"
    
    def store(
            self,
            request: Any,  # FastHTML/Starlette request object with session
            key: APIKey  # API key object to store
        ) -> None
        "Store an API key in the session"
    
    def retrieve(
            self,
            request: Any,  # FastHTML/Starlette request object with session
            provider: str,  # Provider name to retrieve key for
            user_id: Optional[str] = None  # User ID (unused in session storage)
        ) -> Optional[APIKey]:  # API key object if found and valid, None otherwise
        "Retrieve an API key from the session"
    
    def delete(
            self,
            request: Any,  # FastHTML/Starlette request object with session
            provider: str,  # Provider name to delete key for
            user_id: Optional[str] = None  # User ID (unused in session storage)
        ) -> None
        "Delete an API key from the session"
    
    def list_providers(
            self,
            request: Any,  # FastHTML/Starlette request object with session
            user_id: Optional[str] = None  # User ID (unused in session storage)
        ) -> List[str]:  # List of provider names with stored keys
        "List all providers with stored keys"
    
    def clear_all(
            self,
            request: Any,  # FastHTML/Starlette request object with session
            user_id: Optional[str] = None  # User ID (unused in session storage)
        ) -> None
        "Clear all API keys from the session"
```

``` python
class DatabaseStorage:
    def __init__(
        self,
        config: BYOKConfig,  # BYOK configuration object
        db_url: str = "sqlite:///byok_keys.db"  # Database URL (defaults to SQLite)
    )
    """
    Database-backed storage for API keys using SQLAlchemy 2.0+.
    Keys persist across sessions and devices.
    """
    
    def __init__(
            self,
            config: BYOKConfig,  # BYOK configuration object
            db_url: str = "sqlite:///byok_keys.db"  # Database URL (defaults to SQLite)
        )
        "Initialize database storage with SQLAlchemy."
    
    def store(
            self,
            request: Any,  # FastHTML/Starlette request object (unused but kept for interface consistency)
            key: APIKey  # API key object to store in database
        ) -> None
        "Store an API key in the database"
    
    def retrieve(
            self,
            request: Any,  # FastHTML/Starlette request object (unused but kept for interface consistency)
            provider: str,  # Provider name to retrieve key for
            user_id: Optional[str] = None  # User ID to retrieve key for (required for database)
        ) -> Optional[APIKey]:  # API key object if found and valid, None otherwise
        "Retrieve an API key from the database"
    
    def delete(
            self,
            request: Any,  # FastHTML/Starlette request object (unused but kept for interface consistency)
            provider: str,  # Provider name to delete key for
            user_id: Optional[str] = None  # User ID to delete key for (required for database)
        ) -> None
        "Delete an API key from the database"
    
    def list_providers(
            self,
            request: Any,  # FastHTML/Starlette request object (unused but kept for interface consistency)
            user_id: Optional[str] = None  # User ID to list providers for (required for database)
        ) -> List[str]:  # List of provider names with stored keys
        "List all providers with stored keys for a user"
    
    def clear_all(
            self,
            request: Any,  # FastHTML/Starlette request object (unused but kept for interface consistency)
            user_id: Optional[str] = None  # User ID to clear keys for (required for database)
        ) -> None
        "Clear all API keys for a user"
```

``` python
class HybridStorage:
    def __init__(
        self,
        config: BYOKConfig,  # BYOK configuration object
        db_url: Optional[str] = None  # Optional database URL for persistent storage
    )
    """
    Hybrid storage using both session and database.
    Session acts as a cache, database provides persistence.
    """
    
    def __init__(
            self,
            config: BYOKConfig,  # BYOK configuration object
            db_url: Optional[str] = None  # Optional database URL for persistent storage
        )
        "Initialize hybrid storage with session and optional database backends"
    
    def store(
            self,
            request: Any,  # FastHTML/Starlette request object with session
            key: APIKey  # API key object to store
        ) -> None
        "Store in both session and database"
    
    def retrieve(
            self,
            request: Any,  # FastHTML/Starlette request object with session
            provider: str,  # Provider name to retrieve key for
            user_id: Optional[str] = None  # User ID for database lookup
        ) -> Optional[APIKey]:  # API key object if found, None otherwise
        "Retrieve from session first, then database"
    
    def delete(
            self,
            request: Any,  # FastHTML/Starlette request object with session
            provider: str,  # Provider name to delete key for
            user_id: Optional[str] = None  # User ID for database deletion
        ) -> None
        "Delete from both storages"
    
    def list_providers(
            self,
            request: Any,  # FastHTML/Starlette request object with session
            user_id: Optional[str] = None  # User ID for database lookup
        ) -> List[str]:  # Combined list of providers from both storages
        "List providers from both storages"
    
    def clear_all(
            self,
            request: Any,  # FastHTML/Starlette request object with session
            user_id: Optional[str] = None  # User ID for database clearing
        ) -> None
        "Clear from both storages"
```

``` python
class BYOKManager:
    def __init__(
        self,
        secret_key: str,  # Application secret key for encryption
        db_url: Optional[str] = None,  # Optional database URL for persistent storage (e.g., "sqlite:///keys.db")
        config: Optional[BYOKConfig] = None  # Optional configuration (uses defaults if not provided)
    )
    """
    Main manager for the BYOK system.
    Handles encryption and storage coordination.
    """
    
    def __init__(
            self,
            secret_key: str,  # Application secret key for encryption
            db_url: Optional[str] = None,  # Optional database URL for persistent storage (e.g., "sqlite:///keys.db")
            config: Optional[BYOKConfig] = None  # Optional configuration (uses defaults if not provided)
        )
        "Initialize the BYOK manager."
    
    def set_key(
            self,
            request: Any,  # FastHTML/Starlette request object
            provider: str,  # Provider name (e.g., 'openai', 'anthropic')
            api_key: str,  # The API key to store
            user_id: Optional[str] = None,  # Optional user ID for database storage
            ttl: Optional[timedelta] = None  # Optional time-to-live for the key
        ) -> None
        "Store an API key."
    
    def get_key(
            self,
            request: Any,  # FastHTML/Starlette request object
            provider: str,  # Provider name
            user_id: Optional[str] = None  # Optional user ID for database lookup
        ) -> Optional[str]:  # Decrypted API key or None if not found
        "Retrieve and decrypt an API key."
    
    def delete_key(
            self,
            request: Any,  # FastHTML/Starlette request object
            provider: str,  # Provider name
            user_id: Optional[str] = None  # Optional user ID
        ) -> None
        "Delete an API key."
    
    def list_providers(
            self,
            request: Any,  # FastHTML/Starlette request object
            user_id: Optional[str] = None  # Optional user ID
        ) -> List[str]:  # List of provider names
        "List all providers with stored keys."
    
    def clear_keys(
            self,
            request: Any,  # FastHTML/Starlette request object
            user_id: Optional[str] = None  # Optional user ID
        ) -> None
        "Clear all stored API keys."
    
    def has_key(
            self,
            request: Any,  # FastHTML/Starlette request object
            provider: str,  # Provider name
            user_id: Optional[str] = None  # Optional user ID
        ) -> bool:  # True if key exists, False otherwise
        "Check if a key exists for a provider."
```

### Types (`types.ipynb`)

> Type definitions and protocols for the BYOK system

#### Import

``` python
from cjm_fasthtml_byok.core.types import (
    StorageBackend,
    APIKey,
    KeyStorage,
    BYOKConfig,
    UserAPIKey,
    BYOKException,
    EncryptionError,
    StorageError,
    KeyNotFoundError,
    SecurityWarning
)
```

#### Classes

``` python
class StorageBackend(Enum):
    "Available storage backends for API keys"
```

``` python
@dataclass
class APIKey:
    "Represents an encrypted API key with metadata"
    
    provider: str  # e.g., 'openai', 'anthropic', 'google'
    encrypted_value: bytes  # Encrypted key value
    created_at: datetime = field(...)
    expires_at: Optional[datetime]
    user_id: Optional[str]  # For database storage
    
    def is_expired(
            self
        ) -> bool:  # True if key has expired, False otherwise
        "Check if the key has expired"
    
    def to_dict(
            self
        ) -> Dict[str, Any]:  # Dictionary representation for serialization
        "Convert to dictionary for storage"
    
    def from_dict(
            cls,  # The APIKey class
            data: Dict[str, Any]  # Dictionary containing serialized key data
        ) -> 'APIKey':  # Reconstructed APIKey instance
        "Create from dictionary"
```

``` python
@runtime_checkable
class KeyStorage(Protocol):
    "Protocol for key storage implementations"
    
    def store(
            self,
            request: Any,  # FastHTML/Starlette request object
            key: APIKey  # API key object to store
        ) -> None
        "Store an API key"
    
    def retrieve(
            self,
            request: Any,  # FastHTML/Starlette request object
            provider: str,  # Provider name to retrieve key for
            user_id: Optional[str] = None  # Optional user ID for database lookup
        ) -> Optional[APIKey]:  # API key object if found, None otherwise
        "Retrieve an API key for a provider"
    
    def delete(
            self,
            request: Any,  # FastHTML/Starlette request object
            provider: str,  # Provider name to delete key for
            user_id: Optional[str] = None  # Optional user ID for database deletion
        ) -> None
        "Delete an API key"
    
    def list_providers(
            self,
            request: Any,  # FastHTML/Starlette request object
            user_id: Optional[str] = None  # Optional user ID for database lookup
        ) -> list[str]:  # List of provider names with stored keys
        "List all stored providers"
    
    def clear_all(
            self,
            request: Any,  # FastHTML/Starlette request object
            user_id: Optional[str] = None  # Optional user ID for database clearing
        ) -> None
        "Clear all stored keys"
```

``` python
@dataclass
class BYOKConfig:
    "Configuration for the BYOK system"
    
    storage_backend: StorageBackend = StorageBackend.SESSION
    encryption_key: Optional[bytes]  # If None, will be generated
    default_ttl: Optional[timedelta] = timedelta(hours=24)  # Default key expiration
    session_key_prefix: str = 'byok_'  # Prefix for session storage keys
    db_table_name: str = 'user_api_keys'  # Database table name
    auto_cleanup: bool = True  # Auto-cleanup expired keys
    require_https: bool = True  # Warn if not using HTTPS in production
```

``` python
class UserAPIKey:
    "Database schema for persistent API key storage (for use with fastsql)"
```

``` python
class BYOKException(Exception):
    "Base exception for BYOK errors"
```

``` python
class EncryptionError(BYOKException):
    "Error during encryption/decryption"
```

``` python
class StorageError(BYOKException):
    "Error during storage operations"
```

``` python
class KeyNotFoundError(BYOKException):
    "Requested key not found"
```

``` python
class SecurityWarning(BYOKException):
    "Security-related warning"
```

            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/cj-mills/cjm-fasthtml-byok",
    "name": "cjm-fasthtml-byok",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.9",
    "maintainer_email": null,
    "keywords": "nbdev jupyter notebook python",
    "author": "Christian J. Mills",
    "author_email": "9126128+cj-mills@users.noreply.github.com",
    "download_url": "https://files.pythonhosted.org/packages/fd/3b/a66d6cd9073533ff1d285febd2e9bdf750cc2237372b690f8c39b7813585/cjm_fasthtml_byok-0.0.3.tar.gz",
    "platform": null,
    "description": "# cjm-fasthtml-byok\n\n\n<!-- WARNING: THIS FILE WAS AUTOGENERATED! DO NOT EDIT! -->\n\n## Install\n\n``` bash\npip install cjm_fasthtml_byok\n```\n\n## Project Structure\n\n    nbs/\n    \u251c\u2500\u2500 components/ (2)\n    \u2502   \u251c\u2500\u2500 alerts.ipynb  # FastHTML alert and notification components for user feedback\n    \u2502   \u2514\u2500\u2500 forms.ipynb   # FastHTML form components for API key input and management\n    \u251c\u2500\u2500 core/ (3)\n    \u2502   \u251c\u2500\u2500 security.ipynb  # Encryption and security utilities for API key management\n    \u2502   \u251c\u2500\u2500 storage.ipynb   # Storage backends for API keys (session and database)\n    \u2502   \u2514\u2500\u2500 types.ipynb     # Type definitions and protocols for the BYOK system\n    \u251c\u2500\u2500 middleware/ (1)\n    \u2502   \u2514\u2500\u2500 beforeware.ipynb  # FastHTML beforeware for API key management\n    \u2514\u2500\u2500 utils/ (1)\n        \u2514\u2500\u2500 helpers.ipynb  # Helper functions for BYOK system\n\nTotal: 7 notebooks across 4 directories\n\n## Module Dependencies\n\n``` mermaid\ngraph LR\n    components_alerts[components.alerts<br/>Alerts]\n    components_forms[components.forms<br/>Forms]\n    core_security[core.security<br/>Security]\n    core_storage[core.storage<br/>Storage]\n    core_types[core.types<br/>Types]\n    middleware_beforeware[middleware.beforeware<br/>Beforeware]\n    utils_helpers[utils.helpers<br/>Helpers]\n\n    components_forms --> core_security\n    components_forms --> utils_helpers\n    core_security --> core_types\n    core_storage --> core_types\n    core_storage --> core_security\n    middleware_beforeware --> core_security\n    middleware_beforeware --> core_types\n    middleware_beforeware --> core_storage\n    utils_helpers --> core_security\n```\n\n*9 cross-module dependencies detected*\n\n## CLI Reference\n\nNo CLI commands found in this project.\n\n## Module Overview\n\nDetailed documentation for each module in the project:\n\n### Alerts (`alerts.ipynb`)\n\n> FastHTML alert and notification components for user feedback\n\n#### Import\n\n``` python\nfrom cjm_fasthtml_byok.components.alerts import (\n    InfoIcon,\n    SuccessIcon,\n    WarningIcon,\n    ErrorIcon,\n    Alert,\n    SecurityAlert,\n    KeyStatusNotification,\n    ToastContainer,\n    Toast,\n    ValidationMessage,\n    AlertStack\n)\n```\n\n#### Functions\n\n``` python\ndef InfoIcon(\n    size: str = \"6\"  # Size of the icon (matches Tailwind h-{size} and w-{size} classes)\n) -> FT:  # SVG element for the info icon\n    \"Create an info icon SVG.\"\n```\n\n``` python\ndef SuccessIcon(\n    size: str = \"6\"  # Size of the icon (matches Tailwind h-{size} and w-{size} classes)\n) -> FT:  # SVG element for the success icon\n    \"Create a success/check icon SVG.\"\n```\n\n``` python\ndef WarningIcon(\n    size: str = \"6\"  # Size of the icon (matches Tailwind h-{size} and w-{size} classes)\n) -> FT:  # SVG element for the warning icon\n    \"Create a warning/exclamation icon SVG.\"\n```\n\n``` python\ndef ErrorIcon(\n    size: str = \"6\"  # Size of the icon (matches Tailwind h-{size} and w-{size} classes)\n) -> FT:  # SVG element for the error icon\n    \"Create an error/X icon SVG.\"\n```\n\n``` python\ndef Alert(\n    message: str,  # The alert message\n    kind: Literal[\"info\", \"success\", \"warning\", \"error\"] = \"info\",\n    title: Optional[str] = None,  # Optional title for the alert\n    dismissible: bool = False,  # Whether the alert can be dismissed\n    show_icon: bool = True,  # Whether to show an icon\n    style: Optional[str] = None,  # Alert style (\"soft\", \"outline\", or None for default)\n    id: Optional[str] = None  # HTML ID for the alert element\n) -> FT:  # Alert component\n    \"Create an alert component for displaying messages.\"\n```\n\n``` python\ndef SecurityAlert(\n    message: str,  # Security alert message\n    severity: Literal[\"low\", \"medium\", \"high\", \"critical\"] = \"medium\",\n    action_url: Optional[str] = None,  # Optional URL for remediation action\n    action_text: str = \"Fix Now\"  # Text for the action button\n) -> FT:  # Security alert component\n    \"Create a security-focused alert with severity levels.\"\n```\n\n``` python\ndef KeyStatusNotification(\n    provider: str,  # Provider name\n    status: Literal[\"added\", \"updated\", \"deleted\", \"expired\", \"invalid\"],\n    masked_key: Optional[str] = None,  # Masked version of the key\n    auto_dismiss: bool = True,  # Whether to auto-dismiss\n    dismiss_after: int = 5000  # Milliseconds before auto-dismiss\n) -> FT:  # Key status notification component\n    \"Create a notification for API key status changes.\"\n```\n\n``` python\ndef ToastContainer(\n    position: Literal[\"top\", \"middle\", \"bottom\"] = \"top\",\n    align: Literal[\"start\", \"center\", \"end\"] = \"end\",\n    id: str = \"toast-container\"  # HTML ID for the container\n) -> FT:  # Toast container component\n    \"Create a container for toast notifications.\"\n```\n\n``` python\ndef Toast(\n    message: str,  # Toast message\n    kind: Literal[\"info\", \"success\", \"warning\", \"error\"] = \"info\",\n    duration: int = 3000  # Duration in milliseconds\n) -> FT:  # Toast notification component\n    \"Create a toast notification.\"\n```\n\n``` python\ndef ValidationMessage(\n    message: str,  # Validation message\n    is_valid: bool = False,  # Whether the validation passed\n    show_icon: bool = True  # Whether to show an icon\n) -> FT:  # Validation message component\n    \"Create an inline validation message for form fields.\"\n```\n\n``` python\ndef AlertStack(\n    alerts: list,  # List of alert components\n    max_visible: int = 3,  # Maximum number of visible alerts\n    spacing: str = \"4\"  # Gap between alerts\n) -> FT:  # Alert stack component\n    \"Create a stack of alerts with optional limit.\"\n```\n\n### Beforeware (`beforeware.ipynb`)\n\n> FastHTML beforeware for API key management\n\n#### Import\n\n``` python\nfrom cjm_fasthtml_byok.middleware.beforeware import (\n    create_byok_beforeware,\n    require_api_key,\n    require_any_api_key,\n    SecurityCheckBeforeware,\n    CleanupBeforeware,\n    setup_byok\n)\n```\n\n#### Functions\n\n``` python\ndef create_byok_beforeware(\n    byok_manager: BYOKManager  # The BYOK manager instance\n)\n    \"Create a FastHTML beforeware handler for BYOK functionality.\"\n```\n\n``` python\ndef require_api_key(\n    provider: str,  # The provider name to check for\n    user_id_func: Optional[Callable] = None  # Optional function to get user_id from request Usage: @rt @require_api_key(\"openai\") def chat(request): byok = request.scope['byok'] api_key = byok.get_key(request, \"openai\") # Use the API key...\n)\n    \"Decorator that requires an API key to be present for a route.\"\n```\n\n``` python\ndef require_any_api_key(\n    providers: List[str],  # List of provider names to check\n    user_id_func: Optional[Callable] = None  # Optional function to get user_id from request Usage: @rt @require_any_api_key([\"openai\", \"anthropic\", \"google\"]) def chat(request): # Use whichever API key is available pass\n)\n    \"Decorator that requires at least one of the specified API keys.\"\n```\n\n``` python\ndef setup_byok(\n    secret_key: str,  # Application secret key\n    db: Optional[Any] = None,  # Optional database for persistent storage\n    user_id_func: Optional[Callable] = None,  # Optional function to get user_id from request\n    enable_security_checks: bool = True,  # Enable HTTPS checking\n    enable_cleanup: bool = True  # Enable automatic cleanup of expired keys\n)\n    \"Complete setup helper for BYOK with FastHTML. Returns beforeware functions and the BYOK manager.\"\n```\n\n#### Classes\n\n``` python\nclass SecurityCheckBeforeware:\n    def __init__(\n        self,\n        require_https: bool = True,  # Whether to require HTTPS in production\n        is_production: Optional[bool] = None  # Whether running in production (auto-detected if None)\n    )\n    \"Beforeware that performs security checks.\"\n    \n    def __init__(\n            self,\n            require_https: bool = True,  # Whether to require HTTPS in production\n            is_production: Optional[bool] = None  # Whether running in production (auto-detected if None)\n        )\n        \"Initialize security check beforeware with HTTPS requirements\"\n```\n\n``` python\nclass CleanupBeforeware:\n    def __init__(\n        self,\n        byok_manager: BYOKManager,  # The BYOK manager instance\n        user_id_func: Optional[Callable] = None  # Optional function to get user_id from request\n    )\n    \"Beforeware that cleans up expired keys.\"\n    \n    def __init__(\n            self,\n            byok_manager: BYOKManager,  # The BYOK manager instance\n            user_id_func: Optional[Callable] = None  # Optional function to get user_id from request\n        )\n        \"Initialize cleanup beforeware with BYOK manager\"\n```\n\n### Forms (`forms.ipynb`)\n\n> FastHTML form components for API key input and management\n\n#### Import\n\n``` python\nfrom cjm_fasthtml_byok.components.forms import (\n    KeyInputForm,\n    MultiProviderKeyForm,\n    KeyManagementCard,\n    KeyManagerDashboard,\n    InlineKeyInput\n)\n```\n\n#### Functions\n\n``` python\ndef KeyInputForm(\n    provider: str,  # The API provider identifier\n    action: Optional[str] = None,  # Form action URL (defaults to /api/keys/{provider})\n    method: str = \"post\",  # HTTP method (default: \"post\")\n    show_help: bool = True,  # Whether to show help text\n    custom_placeholder: Optional[str] = None,  # Custom placeholder text\n    extra_fields: Optional[List[tuple]] = None,  # Additional form fields as [(name, type, placeholder, required), ...]\n    provider_config: Optional[Dict[str, Any]] = None  # Optional provider configuration\n) -> FT:  # FastHTML Form component\n    \"Create a form for inputting an API key with improved design.\"\n```\n\n``` python\ndef MultiProviderKeyForm(\n    providers: List[str],  # List of provider identifiers\n    action: str = \"/api/keys\",  # Form action URL\n    method: str = \"post\",  # HTTP method\n    default_provider: Optional[str] = None,  # Initially selected provider\n    provider_config: Optional[Dict[str, Any]] = None  # Optional provider configuration\n) -> FT:  # FastHTML Form component with provider selection\n    \"Create a form that allows selecting from multiple providers with enhanced UX.\"\n```\n\n``` python\ndef KeyManagementCard(\n    provider: str,  # Provider identifier\n    has_key: bool,  # Whether a key is stored\n    masked_key: Optional[str] = None,  # Masked version of the key for display\n    created_at: Optional[str] = None,  # When the key was stored\n    expires_at: Optional[str] = None,  # When the key expires\n    delete_action: Optional[str] = None,  # URL for delete action\n    update_action: Optional[str] = None,  # URL for update action\n    provider_config: Optional[Dict[str, Any]] = None  # Optional provider configuration\n) -> FT:  # Card component for key management\n    \"Create a card component for managing a stored API key with enhanced design.\"\n```\n\n``` python\ndef KeyManagerDashboard(\n    request,  # FastHTML request object\n    providers: List[str],  # List of provider identifiers to manage\n    byok_manager = None,\n    user_id: Optional[str] = None,  # Optional user ID for database storage\n    base_url: str = \"/api/keys\",  # Base URL for API endpoints\n    provider_config: Optional[Dict[str, Any]] = None  # Optional provider configuration\n) -> FT:  # Dashboard component with all provider cards\n    \"Create a complete dashboard for managing multiple API keys with improved layout.\"\n```\n\n``` python\ndef InlineKeyInput(\n    provider: str,  # Provider identifier\n    input_id: Optional[str] = None,  # HTML ID for the input element\n    on_save: Optional[str] = None,  # JavaScript to execute on save (or hx-post URL for HTMX)\n    compact: bool = True,  # Whether to use compact styling\n    provider_config: Optional[Dict[str, Any]] = None  # Optional provider configuration\n) -> FT:  # Inline input component\n    \"Create a compact inline key input component with polished design.\"\n```\n\n### Helpers (`helpers.ipynb`)\n\n> Helper functions for BYOK system\n\n#### Import\n\n``` python\nfrom cjm_fasthtml_byok.utils.helpers import (\n    get_provider_info,\n    format_provider_name,\n    format_key_age,\n    format_expiration,\n    get_key_summary,\n    get_env_key,\n    import_from_env\n)\n```\n\n#### Functions\n\n``` python\ndef get_provider_info(\n    provider: str,  # Provider identifier\n    provider_config: Optional[Dict[str, Any]] = None  # Optional provider configuration\n) -> Dict[str, Any]:  # Provider info dict with defaults\n    \"Get provider information from config or generate defaults.\"\n```\n\n``` python\ndef format_provider_name(\n    provider: str,  # Provider identifier\n    provider_config: Optional[Dict[str, Any]] = None  # Optional provider configuration\n) -> str:  # Formatted provider name\n    \"Format provider name for display.\"\n```\n\n``` python\ndef format_key_age(\n    created_at: datetime  # When the key was created\n) -> str:  # Human-readable age string\n    \"Format the age of a key for display.\"\n```\n\n``` python\ndef format_expiration(\n    expires_at: Optional[datetime]  # Expiration datetime\n) -> str:  # Human-readable expiration string\n    \"Format expiration time for display.\"\n```\n\n``` python\ndef get_key_summary(\n    byok_manager,  # BYOK manager instance\n    request,  # FastHTML request\n    user_id: Optional[str] = None,  # Optional user ID\n    provider_config: Optional[Dict[str, Any]] = None  # Optional provider configuration\n) -> Dict[str, Any]:  # Summary dictionary with provider info\n    \"Get a summary of all stored keys.\"\n```\n\n``` python\ndef get_env_key(\n    provider: str,  # Provider name\n    env_prefix: str = \"API_KEY_\"  # Environment variable prefix\n) -> Optional[str]:  # API key from environment or None\n    \"Get an API key from environment variables.\"\n```\n\n``` python\ndef import_from_env(\n    byok_manager,  # BYOK manager instance\n    request,  # FastHTML request\n    providers: List[str],  # List of providers to check\n    user_id: Optional[str] = None,  # Optional user ID\n    env_prefix: str = \"API_KEY_\"  # Environment variable prefix\n) -> Dict[str, bool]:  # Dict of provider: success status\n    \"Import API keys from environment variables.\"\n```\n\n### Security (`security.ipynb`)\n\n> Encryption and security utilities for API key management\n\n#### Import\n\n``` python\nfrom cjm_fasthtml_byok.core.security import (\n    generate_encryption_key,\n    get_or_create_app_key,\n    KeyEncryptor,\n    check_https,\n    validate_environment,\n    mask_key,\n    get_key_fingerprint\n)\n```\n\n#### Functions\n\n``` python\ndef generate_encryption_key(\n    password: Optional[str] = None,  # Optional password to derive key from\n    salt: Optional[bytes] = None  # Optional salt for key derivation (required if password provided)\n) -> bytes:  # 32-byte encryption key suitable for Fernet\n    \"Generate or derive an encryption key.\"\n```\n\n``` python\ndef get_or_create_app_key(\n    \"Get or create an app-specific encryption key derived from the app's secret key.\"\n```\n\n``` python\ndef check_https(\n    request  # FastHTML/Starlette request object\n) -> bool:  # True if using HTTPS, False otherwise\n    \"Check if the request is using HTTPS.\"\n```\n\n``` python\ndef validate_environment(\n    request,  # FastHTML/Starlette request object\n    require_https: bool = True,  # Whether to require HTTPS\n    is_production: bool = None  # Whether running in production (auto-detected if None)\n) -> None\n    \"Validate the security environment.\"\n```\n\n``` python\ndef mask_key(\n    key: str,  # The API key to mask\n    visible_chars: int = 4  # Number of characters to show at start and end\n) -> str:  # Masked key like 'sk-a...xyz'\n    \"Mask an API key for display purposes.\"\n```\n\n``` python\ndef get_key_fingerprint(\n    key: str  # The API key\n) -> str:  # SHA256 fingerprint of the key (first 16 chars)\n    \"Generate a fingerprint for an API key (for logging/tracking without exposing the key).\"\n```\n\n#### Classes\n\n``` python\nclass KeyEncryptor:\n    def __init__(\n        self,\n        encryption_key: Optional[bytes] = None  # Encryption key to use. If None, generates a new one\n    )\n    \"Handles encryption and decryption of API keys.\"\n    \n    def __init__(\n            self,\n            encryption_key: Optional[bytes] = None  # Encryption key to use. If None, generates a new one\n        )\n        \"Initialize the encryptor.\n\nArgs:\n    encryption_key: Encryption key to use. If None, generates a new one.\"\n    \n    def encrypt(\n            self,\n            value: str  # Plain text API key to encrypt\n        ) -> bytes:  # Encrypted bytes\n        \"Encrypt an API key value.\n\nArgs:\n    value: Plain text API key\n\nReturns:\n    Encrypted bytes\n\nRaises:\n    EncryptionError: If encryption fails\"\n    \n    def decrypt(\n            self,\n            encrypted_value: bytes  # Encrypted bytes to decrypt\n        ) -> str:  # Decrypted plain text API key\n        \"Decrypt an API key value.\n\nArgs:\n    encrypted_value: Encrypted bytes\n\nReturns:\n    Decrypted API key\n\nRaises:\n    EncryptionError: If decryption fails\"\n    \n    def rotate_key(\n            self,\n            new_key: bytes,  # New encryption key to use\n            encrypted_value: bytes  # Value encrypted with current key\n        ) -> bytes:  # Value re-encrypted with new key\n        \"Re-encrypt a value with a new key.\n\nArgs:\n    new_key: New encryption key\n    encrypted_value: Value encrypted with current key\n\nReturns:\n    Value encrypted with new key\"\n```\n\n### Storage (`storage.ipynb`)\n\n> Storage backends for API keys (session and database)\n\n#### Import\n\n``` python\nfrom cjm_fasthtml_byok.core.storage import (\n    SessionStorage,\n    DatabaseStorage,\n    HybridStorage,\n    BYOKManager\n)\n```\n\n#### Classes\n\n``` python\nclass SessionStorage:\n    def __init__(\n        self,\n        config: BYOKConfig  # BYOK configuration object\n    )\n    \"\"\"\n    Session-based storage for API keys.\n    Keys are stored in the user's session and expire with the session.\n    \"\"\"\n    \n    def __init__(\n            self,\n            config: BYOKConfig  # BYOK configuration object\n        )\n        \"Initialize session storage with configuration\"\n    \n    def store(\n            self,\n            request: Any,  # FastHTML/Starlette request object with session\n            key: APIKey  # API key object to store\n        ) -> None\n        \"Store an API key in the session\"\n    \n    def retrieve(\n            self,\n            request: Any,  # FastHTML/Starlette request object with session\n            provider: str,  # Provider name to retrieve key for\n            user_id: Optional[str] = None  # User ID (unused in session storage)\n        ) -> Optional[APIKey]:  # API key object if found and valid, None otherwise\n        \"Retrieve an API key from the session\"\n    \n    def delete(\n            self,\n            request: Any,  # FastHTML/Starlette request object with session\n            provider: str,  # Provider name to delete key for\n            user_id: Optional[str] = None  # User ID (unused in session storage)\n        ) -> None\n        \"Delete an API key from the session\"\n    \n    def list_providers(\n            self,\n            request: Any,  # FastHTML/Starlette request object with session\n            user_id: Optional[str] = None  # User ID (unused in session storage)\n        ) -> List[str]:  # List of provider names with stored keys\n        \"List all providers with stored keys\"\n    \n    def clear_all(\n            self,\n            request: Any,  # FastHTML/Starlette request object with session\n            user_id: Optional[str] = None  # User ID (unused in session storage)\n        ) -> None\n        \"Clear all API keys from the session\"\n```\n\n``` python\nclass DatabaseStorage:\n    def __init__(\n        self,\n        config: BYOKConfig,  # BYOK configuration object\n        db_url: str = \"sqlite:///byok_keys.db\"  # Database URL (defaults to SQLite)\n    )\n    \"\"\"\n    Database-backed storage for API keys using SQLAlchemy 2.0+.\n    Keys persist across sessions and devices.\n    \"\"\"\n    \n    def __init__(\n            self,\n            config: BYOKConfig,  # BYOK configuration object\n            db_url: str = \"sqlite:///byok_keys.db\"  # Database URL (defaults to SQLite)\n        )\n        \"Initialize database storage with SQLAlchemy.\"\n    \n    def store(\n            self,\n            request: Any,  # FastHTML/Starlette request object (unused but kept for interface consistency)\n            key: APIKey  # API key object to store in database\n        ) -> None\n        \"Store an API key in the database\"\n    \n    def retrieve(\n            self,\n            request: Any,  # FastHTML/Starlette request object (unused but kept for interface consistency)\n            provider: str,  # Provider name to retrieve key for\n            user_id: Optional[str] = None  # User ID to retrieve key for (required for database)\n        ) -> Optional[APIKey]:  # API key object if found and valid, None otherwise\n        \"Retrieve an API key from the database\"\n    \n    def delete(\n            self,\n            request: Any,  # FastHTML/Starlette request object (unused but kept for interface consistency)\n            provider: str,  # Provider name to delete key for\n            user_id: Optional[str] = None  # User ID to delete key for (required for database)\n        ) -> None\n        \"Delete an API key from the database\"\n    \n    def list_providers(\n            self,\n            request: Any,  # FastHTML/Starlette request object (unused but kept for interface consistency)\n            user_id: Optional[str] = None  # User ID to list providers for (required for database)\n        ) -> List[str]:  # List of provider names with stored keys\n        \"List all providers with stored keys for a user\"\n    \n    def clear_all(\n            self,\n            request: Any,  # FastHTML/Starlette request object (unused but kept for interface consistency)\n            user_id: Optional[str] = None  # User ID to clear keys for (required for database)\n        ) -> None\n        \"Clear all API keys for a user\"\n```\n\n``` python\nclass HybridStorage:\n    def __init__(\n        self,\n        config: BYOKConfig,  # BYOK configuration object\n        db_url: Optional[str] = None  # Optional database URL for persistent storage\n    )\n    \"\"\"\n    Hybrid storage using both session and database.\n    Session acts as a cache, database provides persistence.\n    \"\"\"\n    \n    def __init__(\n            self,\n            config: BYOKConfig,  # BYOK configuration object\n            db_url: Optional[str] = None  # Optional database URL for persistent storage\n        )\n        \"Initialize hybrid storage with session and optional database backends\"\n    \n    def store(\n            self,\n            request: Any,  # FastHTML/Starlette request object with session\n            key: APIKey  # API key object to store\n        ) -> None\n        \"Store in both session and database\"\n    \n    def retrieve(\n            self,\n            request: Any,  # FastHTML/Starlette request object with session\n            provider: str,  # Provider name to retrieve key for\n            user_id: Optional[str] = None  # User ID for database lookup\n        ) -> Optional[APIKey]:  # API key object if found, None otherwise\n        \"Retrieve from session first, then database\"\n    \n    def delete(\n            self,\n            request: Any,  # FastHTML/Starlette request object with session\n            provider: str,  # Provider name to delete key for\n            user_id: Optional[str] = None  # User ID for database deletion\n        ) -> None\n        \"Delete from both storages\"\n    \n    def list_providers(\n            self,\n            request: Any,  # FastHTML/Starlette request object with session\n            user_id: Optional[str] = None  # User ID for database lookup\n        ) -> List[str]:  # Combined list of providers from both storages\n        \"List providers from both storages\"\n    \n    def clear_all(\n            self,\n            request: Any,  # FastHTML/Starlette request object with session\n            user_id: Optional[str] = None  # User ID for database clearing\n        ) -> None\n        \"Clear from both storages\"\n```\n\n``` python\nclass BYOKManager:\n    def __init__(\n        self,\n        secret_key: str,  # Application secret key for encryption\n        db_url: Optional[str] = None,  # Optional database URL for persistent storage (e.g., \"sqlite:///keys.db\")\n        config: Optional[BYOKConfig] = None  # Optional configuration (uses defaults if not provided)\n    )\n    \"\"\"\n    Main manager for the BYOK system.\n    Handles encryption and storage coordination.\n    \"\"\"\n    \n    def __init__(\n            self,\n            secret_key: str,  # Application secret key for encryption\n            db_url: Optional[str] = None,  # Optional database URL for persistent storage (e.g., \"sqlite:///keys.db\")\n            config: Optional[BYOKConfig] = None  # Optional configuration (uses defaults if not provided)\n        )\n        \"Initialize the BYOK manager.\"\n    \n    def set_key(\n            self,\n            request: Any,  # FastHTML/Starlette request object\n            provider: str,  # Provider name (e.g., 'openai', 'anthropic')\n            api_key: str,  # The API key to store\n            user_id: Optional[str] = None,  # Optional user ID for database storage\n            ttl: Optional[timedelta] = None  # Optional time-to-live for the key\n        ) -> None\n        \"Store an API key.\"\n    \n    def get_key(\n            self,\n            request: Any,  # FastHTML/Starlette request object\n            provider: str,  # Provider name\n            user_id: Optional[str] = None  # Optional user ID for database lookup\n        ) -> Optional[str]:  # Decrypted API key or None if not found\n        \"Retrieve and decrypt an API key.\"\n    \n    def delete_key(\n            self,\n            request: Any,  # FastHTML/Starlette request object\n            provider: str,  # Provider name\n            user_id: Optional[str] = None  # Optional user ID\n        ) -> None\n        \"Delete an API key.\"\n    \n    def list_providers(\n            self,\n            request: Any,  # FastHTML/Starlette request object\n            user_id: Optional[str] = None  # Optional user ID\n        ) -> List[str]:  # List of provider names\n        \"List all providers with stored keys.\"\n    \n    def clear_keys(\n            self,\n            request: Any,  # FastHTML/Starlette request object\n            user_id: Optional[str] = None  # Optional user ID\n        ) -> None\n        \"Clear all stored API keys.\"\n    \n    def has_key(\n            self,\n            request: Any,  # FastHTML/Starlette request object\n            provider: str,  # Provider name\n            user_id: Optional[str] = None  # Optional user ID\n        ) -> bool:  # True if key exists, False otherwise\n        \"Check if a key exists for a provider.\"\n```\n\n### Types (`types.ipynb`)\n\n> Type definitions and protocols for the BYOK system\n\n#### Import\n\n``` python\nfrom cjm_fasthtml_byok.core.types import (\n    StorageBackend,\n    APIKey,\n    KeyStorage,\n    BYOKConfig,\n    UserAPIKey,\n    BYOKException,\n    EncryptionError,\n    StorageError,\n    KeyNotFoundError,\n    SecurityWarning\n)\n```\n\n#### Classes\n\n``` python\nclass StorageBackend(Enum):\n    \"Available storage backends for API keys\"\n```\n\n``` python\n@dataclass\nclass APIKey:\n    \"Represents an encrypted API key with metadata\"\n    \n    provider: str  # e.g., 'openai', 'anthropic', 'google'\n    encrypted_value: bytes  # Encrypted key value\n    created_at: datetime = field(...)\n    expires_at: Optional[datetime]\n    user_id: Optional[str]  # For database storage\n    \n    def is_expired(\n            self\n        ) -> bool:  # True if key has expired, False otherwise\n        \"Check if the key has expired\"\n    \n    def to_dict(\n            self\n        ) -> Dict[str, Any]:  # Dictionary representation for serialization\n        \"Convert to dictionary for storage\"\n    \n    def from_dict(\n            cls,  # The APIKey class\n            data: Dict[str, Any]  # Dictionary containing serialized key data\n        ) -> 'APIKey':  # Reconstructed APIKey instance\n        \"Create from dictionary\"\n```\n\n``` python\n@runtime_checkable\nclass KeyStorage(Protocol):\n    \"Protocol for key storage implementations\"\n    \n    def store(\n            self,\n            request: Any,  # FastHTML/Starlette request object\n            key: APIKey  # API key object to store\n        ) -> None\n        \"Store an API key\"\n    \n    def retrieve(\n            self,\n            request: Any,  # FastHTML/Starlette request object\n            provider: str,  # Provider name to retrieve key for\n            user_id: Optional[str] = None  # Optional user ID for database lookup\n        ) -> Optional[APIKey]:  # API key object if found, None otherwise\n        \"Retrieve an API key for a provider\"\n    \n    def delete(\n            self,\n            request: Any,  # FastHTML/Starlette request object\n            provider: str,  # Provider name to delete key for\n            user_id: Optional[str] = None  # Optional user ID for database deletion\n        ) -> None\n        \"Delete an API key\"\n    \n    def list_providers(\n            self,\n            request: Any,  # FastHTML/Starlette request object\n            user_id: Optional[str] = None  # Optional user ID for database lookup\n        ) -> list[str]:  # List of provider names with stored keys\n        \"List all stored providers\"\n    \n    def clear_all(\n            self,\n            request: Any,  # FastHTML/Starlette request object\n            user_id: Optional[str] = None  # Optional user ID for database clearing\n        ) -> None\n        \"Clear all stored keys\"\n```\n\n``` python\n@dataclass\nclass BYOKConfig:\n    \"Configuration for the BYOK system\"\n    \n    storage_backend: StorageBackend = StorageBackend.SESSION\n    encryption_key: Optional[bytes]  # If None, will be generated\n    default_ttl: Optional[timedelta] = timedelta(hours=24)  # Default key expiration\n    session_key_prefix: str = 'byok_'  # Prefix for session storage keys\n    db_table_name: str = 'user_api_keys'  # Database table name\n    auto_cleanup: bool = True  # Auto-cleanup expired keys\n    require_https: bool = True  # Warn if not using HTTPS in production\n```\n\n``` python\nclass UserAPIKey:\n    \"Database schema for persistent API key storage (for use with fastsql)\"\n```\n\n``` python\nclass BYOKException(Exception):\n    \"Base exception for BYOK errors\"\n```\n\n``` python\nclass EncryptionError(BYOKException):\n    \"Error during encryption/decryption\"\n```\n\n``` python\nclass StorageError(BYOKException):\n    \"Error during storage operations\"\n```\n\n``` python\nclass KeyNotFoundError(BYOKException):\n    \"Requested key not found\"\n```\n\n``` python\nclass SecurityWarning(BYOKException):\n    \"Security-related warning\"\n```\n",
    "bugtrack_url": null,
    "license": "Apache Software License 2.0",
    "summary": "Secure API key management for FastHTML applications with encrypted storage, session/database persistence, and built-in UI components.",
    "version": "0.0.3",
    "project_urls": {
        "Homepage": "https://github.com/cj-mills/cjm-fasthtml-byok"
    },
    "split_keywords": [
        "nbdev",
        "jupyter",
        "notebook",
        "python"
    ],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "f82cc08dab4bed11c1ecead1dfab706de0c21510b26a29b7f107627b7f02ece7",
                "md5": "ed52f745160bb85330f151b639f76e77",
                "sha256": "6a1d9477e645a9ce1e31d60d9c735edc8863fe31ffb683a86274dc8059899e17"
            },
            "downloads": -1,
            "filename": "cjm_fasthtml_byok-0.0.3-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "ed52f745160bb85330f151b639f76e77",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.9",
            "size": 35976,
            "upload_time": "2025-10-24T21:46:07",
            "upload_time_iso_8601": "2025-10-24T21:46:07.032404Z",
            "url": "https://files.pythonhosted.org/packages/f8/2c/c08dab4bed11c1ecead1dfab706de0c21510b26a29b7f107627b7f02ece7/cjm_fasthtml_byok-0.0.3-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "fd3ba66d6cd9073533ff1d285febd2e9bdf750cc2237372b690f8c39b7813585",
                "md5": "9e1dba4c7b92a67d00174432d5fd0820",
                "sha256": "55f03ccdb2cac8e65e04b011e5e72a7a399d992e1f113b3bd7998895b1c77781"
            },
            "downloads": -1,
            "filename": "cjm_fasthtml_byok-0.0.3.tar.gz",
            "has_sig": false,
            "md5_digest": "9e1dba4c7b92a67d00174432d5fd0820",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.9",
            "size": 38425,
            "upload_time": "2025-10-24T21:46:08",
            "upload_time_iso_8601": "2025-10-24T21:46:08.440135Z",
            "url": "https://files.pythonhosted.org/packages/fd/3b/a66d6cd9073533ff1d285febd2e9bdf750cc2237372b690f8c39b7813585/cjm_fasthtml_byok-0.0.3.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-10-24 21:46:08",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "cj-mills",
    "github_project": "cjm-fasthtml-byok",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "lcname": "cjm-fasthtml-byok"
}
        
Elapsed time: 1.75986s