Secure API key management for FastHTML applications with encrypted storage, session/database persistence, and built-in UI components.
Project description
cjm-fasthtml-byok
Install
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
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
from cjm_fasthtml_byok.components.alerts import (
InfoIcon,
SuccessIcon,
WarningIcon,
ErrorIcon,
Alert,
SecurityAlert,
KeyStatusNotification,
ToastContainer,
Toast,
ValidationMessage,
AlertStack
)
Functions
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."
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."
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."
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."
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."
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."
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."
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."
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."
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."
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
from cjm_fasthtml_byok.middleware.beforeware import (
create_byok_beforeware,
require_api_key,
require_any_api_key,
SecurityCheckBeforeware,
CleanupBeforeware,
setup_byok
)
Functions
def create_byok_beforeware(
byok_manager: BYOKManager # The BYOK manager instance
)
"Create a FastHTML beforeware handler for BYOK functionality."
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."
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."
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
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"
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
from cjm_fasthtml_byok.components.forms import (
KeyInputForm,
MultiProviderKeyForm,
KeyManagementCard,
KeyManagerDashboard,
InlineKeyInput
)
Functions
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."
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."
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."
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."
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
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
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."
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."
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."
def format_expiration(
expires_at: Optional[datetime] # Expiration datetime
) -> str: # Human-readable expiration string
"Format expiration time for display."
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."
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."
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
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
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."
def get_or_create_app_key(
"Get or create an app-specific encryption key derived from the app's secret key."
def check_https(
request # FastHTML/Starlette request object
) -> bool: # True if using HTTPS, False otherwise
"Check if the request is using HTTPS."
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."
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."
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
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
from cjm_fasthtml_byok.core.storage import (
SessionStorage,
DatabaseStorage,
HybridStorage,
BYOKManager
)
Classes
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"
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"
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"
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
from cjm_fasthtml_byok.core.types import (
StorageBackend,
APIKey,
KeyStorage,
BYOKConfig,
UserAPIKey,
BYOKException,
EncryptionError,
StorageError,
KeyNotFoundError,
SecurityWarning
)
Classes
class StorageBackend(Enum):
"Available storage backends for API keys"
@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"
@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"
@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
class UserAPIKey:
"Database schema for persistent API key storage (for use with fastsql)"
class BYOKException(Exception):
"Base exception for BYOK errors"
class EncryptionError(BYOKException):
"Error during encryption/decryption"
class StorageError(BYOKException):
"Error during storage operations"
class KeyNotFoundError(BYOKException):
"Requested key not found"
class SecurityWarning(BYOKException):
"Security-related warning"
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file cjm_fasthtml_byok-0.0.3.tar.gz.
File metadata
- Download URL: cjm_fasthtml_byok-0.0.3.tar.gz
- Upload date:
- Size: 38.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.11.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
55f03ccdb2cac8e65e04b011e5e72a7a399d992e1f113b3bd7998895b1c77781
|
|
| MD5 |
9e1dba4c7b92a67d00174432d5fd0820
|
|
| BLAKE2b-256 |
fd3ba66d6cd9073533ff1d285febd2e9bdf750cc2237372b690f8c39b7813585
|
File details
Details for the file cjm_fasthtml_byok-0.0.3-py3-none-any.whl.
File metadata
- Download URL: cjm_fasthtml_byok-0.0.3-py3-none-any.whl
- Upload date:
- Size: 36.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.11.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6a1d9477e645a9ce1e31d60d9c735edc8863fe31ffb683a86274dc8059899e17
|
|
| MD5 |
ed52f745160bb85330f151b639f76e77
|
|
| BLAKE2b-256 |
f82cc08dab4bed11c1ecead1dfab706de0c21510b26a29b7f107627b7f02ece7
|