Metadata-Version: 2.4
Name: cjm-fasthtml-byok
Version: 0.0.3
Summary: Secure API key management for FastHTML applications with encrypted storage, session/database persistence, and built-in UI components.
Home-page: https://github.com/cj-mills/cjm-fasthtml-byok
Author: Christian J. Mills
Author-email: 9126128+cj-mills@users.noreply.github.com
License: Apache Software License 2.0
Keywords: nbdev jupyter notebook python
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Natural Language :: English
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: License :: OSI Approved :: Apache Software License
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: fastcore
Requires-Dist: cryptography
Requires-Dist: python-fasthtml
Requires-Dist: cjm_fasthtml_tailwind
Requires-Dist: cjm_fasthtml_daisyui
Requires-Dist: fastsql
Provides-Extra: dev
Dynamic: author
Dynamic: author-email
Dynamic: classifier
Dynamic: description
Dynamic: description-content-type
Dynamic: home-page
Dynamic: keywords
Dynamic: license
Dynamic: license-file
Dynamic: provides-extra
Dynamic: requires-dist
Dynamic: requires-python
Dynamic: summary

# 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"
```
