A drop-in schema-based configuration system for FastHTML applications with automatic UI generation, sidebar navigation, and persistent storage.
Project description
cjm-fasthtml-settings
Install
pip install cjm_fasthtml_settings
Project Structure
nbs/
├── components/ (3)
│ ├── dashboard.ipynb # Settings dashboard layout components
│ ├── forms.ipynb # Form generation components for settings interfaces
│ └── sidebar.ipynb # Navigation menu components for settings sidebar
├── core/ (5)
│ ├── config.ipynb # Configuration constants, directory management, and base application schema
│ ├── html_ids.ipynb # Centralized HTML ID constants for settings components
│ ├── schema_group.ipynb # Grouping related configuration schemas for better organization
│ ├── schemas.ipynb # Schema registry and management for settings
│ └── utils.ipynb # Configuration loading, saving, and conversion utilities
├── plugins.ipynb # Optional plugin integration for extensible settings systems
└── routes.ipynb # FastHTML route handlers for settings interface
Total: 10 notebooks across 2 directories
Module Dependencies
graph LR
components_dashboard[components.dashboard<br/>Dashboard]
components_forms[components.forms<br/>Forms]
components_sidebar[components.sidebar<br/>Sidebar]
core_config[core.config<br/>Config]
core_html_ids[core.html_ids<br/>HTML IDs]
core_schema_group[core.schema_group<br/>Schema Group]
core_schemas[core.schemas<br/>Schemas]
core_utils[core.utils<br/>Utils]
plugins[plugins<br/>Plugins]
routes[routes<br/>Routes]
components_dashboard --> core_html_ids
components_dashboard --> components_sidebar
components_dashboard --> core_utils
components_dashboard --> core_config
components_dashboard --> components_forms
components_forms --> core_html_ids
components_forms --> core_config
components_forms --> core_utils
components_sidebar --> core_html_ids
components_sidebar --> core_schemas
components_sidebar --> core_config
core_schemas --> core_schema_group
core_schemas --> core_config
core_schemas --> core_schemas
core_utils --> core_config
routes --> core_utils
routes --> core_html_ids
routes --> components_sidebar
routes --> components_dashboard
routes --> routes
routes --> core_config
routes --> core_schemas
routes --> components_forms
23 cross-module dependencies detected
CLI Reference
No CLI commands found in this project.
Module Overview
Detailed documentation for each module in the project:
Config (config.ipynb)
Configuration constants, directory management, and base application schema
Import
from cjm_fasthtml_settings.core.config import (
DEFAULT_CONFIG_DIR,
get_app_config_schema
)
Functions
def get_app_config_schema(
app_title: str = "FastHTML Application", # Default application title
config_dir: str = "configs", # Default configuration directory
server_port: int = 5000, # Default server port
themes_enum: Optional[List[str]] = None, # Optional list of theme values
themes_enum_names: Optional[List[str]] = None, # Optional list of theme display names
default_theme: Optional[str] = None, # Default theme value
include_theme: bool = True, # Whether to include theme selection
**extra_properties # Additional custom properties to add to the schema
) -> Dict[str, Any]: # JSON Schema for application configuration
"""
Generate a customizable application configuration schema.
This function creates a JSON Schema for application settings that can be customized
with your own defaults and additional properties.
Returns:
A JSON Schema dictionary with application configuration structure
"""
Variables
DEFAULT_CONFIG_DIR
Dashboard (dashboard.ipynb)
Settings dashboard layout components
Import
from cjm_fasthtml_settings.components.dashboard import (
create_form_skeleton,
render_schema_settings_content,
settings_content
)
Functions
def create_form_skeleton(
schema_id: str, # The schema ID for the settings
hx_get_url: str # URL to fetch the actual form content
) -> Div: # Div element with loading trigger
"""
Create a loading skeleton for the settings form that loads asynchronously.
This provides a placeholder that triggers an HTMX request to load the actual form,
improving perceived performance for complex forms.
"""
def render_schema_settings_content(
schema: Dict, # JSON schema for the settings
config_dir: Optional[Path] = None # Config directory path
) -> FT: # Settings form container
"""
Render settings content for a schema-based configuration.
Args:
schema: The JSON schema to render
config_dir: Directory where configs are stored
"""
def settings_content(
request, # FastHTML request object
schema: Dict, # Schema to display
schemas: Dict, # All registered schemas for sidebar
config_dir: Optional[Path] = None, # Config directory
menu_section_title: str = "Settings", # Sidebar section title
plugin_registry: Optional[Any] = None # Optional plugin registry
) -> Div: # Settings content layout
"""
Return settings content with sidebar and form.
Handles both full page loads and HTMX partial updates.
Args:
request: The request object (to check for HTMX)
schema: The schema to display
schemas: All available schemas for the sidebar
config_dir: Config directory path
menu_section_title: Title for the sidebar menu section
plugin_registry: Optional plugin registry for showing plugins in sidebar
"""
Forms (forms.ipynb)
Form generation components for settings interfaces
Import
from cjm_fasthtml_settings.components.forms import (
create_settings_form,
create_settings_form_container
)
Functions
def create_settings_form(
schema: Dict[str, Any], # JSON schema for the form
values: Dict[str, Any], # Current values for the form fields
post_url: str, # URL for form submission
reset_url: str # URL for resetting form to defaults
) -> Form: # Form element with settings and action buttons
"""
Create a settings form with action buttons.
Generates a form using cjm-fasthtml-jsonschema based on the provided schema,
with Save and Reset buttons.
"""
def create_settings_form_container(
schema: Dict[str, Any], # JSON schema for the form
values: Dict[str, Any], # Current values for the form fields
post_url: str, # URL for form submission
reset_url: str, # URL for resetting form to defaults
alert_message: Optional[Any] = None, # Optional alert element to display
use_alert_container: bool = False # If True, add empty alert-container div
) -> Div: # Div containing the alert (if any) and the settings form
"""
Create a container with optional alert and settings form.
This is useful for wrapping a settings form with an alert area that can
display success/error messages.
"""
HTML IDs (html_ids.ipynb)
Centralized HTML ID constants for settings components
Import
from cjm_fasthtml_settings.core.html_ids import (
SettingsHtmlIds
)
Classes
class SettingsHtmlIds(AppHtmlIds):
"""
HTML ID constants for settings components.
This class extends AppHtmlIds from cjm_fasthtml_app_core with settings-specific IDs.
It inherits ALERT_CONTAINER and as_selector() from the parent class.
Inherited from AppHtmlIds:
- ALERT_CONTAINER: "alert-container"
- MAIN_CONTENT: "main-content"
- as_selector(id_str): Converts an ID to CSS selector format (with #)
For IDE Support:
IDEs like VS Code with Pylance will autocomplete these attributes and warn
if you try to access non-existent attributes.
Note:
The typing.Final annotation indicates these are constants that shouldn't
be reassigned at runtime.
"""
def menu_item(name: str) -> str
"Generate a menu item ID for a given settings name."
Plugins (plugins.ipynb)
Optional plugin integration for extensible settings systems
Import
from cjm_fasthtml_settings.plugins import (
PluginRegistryProtocol,
SimplePluginRegistry
)
Classes
@runtime_checkable
class PluginRegistryProtocol(Protocol):
"""
Protocol that plugin registries should implement.
This allows the settings library to work with any plugin system
that implements these methods.
"""
def get_plugin(self, unique_id: str) -> Optional[PluginMetadata]:
"""Get plugin metadata by unique ID."""
...
def get_plugins_by_category(self, category: str) -> list[PluginMetadata]
"Get plugin metadata by unique ID."
def get_plugins_by_category(self, category: str) -> list[PluginMetadata]:
"""Get all plugins in a category."""
...
def get_categories_with_plugins(self) -> list[str]
"Get all plugins in a category."
def get_categories_with_plugins(self) -> list[str]:
"""Get all categories that have registered plugins."""
...
def load_plugin_config(self, unique_id: str) -> Dict[str, Any]
"Get all categories that have registered plugins."
def load_plugin_config(self, unique_id: str) -> Dict[str, Any]:
"""Load saved configuration for a plugin."""
...
def save_plugin_config(self, unique_id: str, config: Dict[str, Any]) -> bool
"Load saved configuration for a plugin."
def save_plugin_config(self, unique_id: str, config: Dict[str, Any]) -> bool
"Save configuration for a plugin."
class SimplePluginRegistry:
def __init__(self, config_dir: Optional[Path] = None):
self._plugins: Dict[str, PluginMetadata] = {}
"""
Simple implementation of PluginRegistryProtocol.
This provides a basic plugin registry that can be used with the settings
library. Applications with more complex needs can implement their own
registry that follows the PluginRegistryProtocol.
Categories are arbitrary strings defined by the application.
"""
def __init__(self, config_dir: Optional[Path] = None):
self._plugins: Dict[str, PluginMetadata] = {}
def register_plugin(self, metadata: PluginMetadata):
"""Register a plugin."""
# Check if plugin is configured
config_file = self._config_dir / f"{metadata.get_unique_id()}.json"
metadata.is_configured = config_file.exists()
self._plugins[metadata.get_unique_id()] = metadata
def get_plugin(self, unique_id: str) -> Optional[PluginMetadata]
"Register a plugin."
def get_plugin(self, unique_id: str) -> Optional[PluginMetadata]:
"""Get plugin metadata by unique ID."""
return self._plugins.get(unique_id)
def get_plugins_by_category(self, category: str) -> list
"Get plugin metadata by unique ID."
def get_plugins_by_category(self, category: str) -> list:
"""Get all plugins in a category."""
return [p for p in self._plugins.values() if p.category == category]
def get_categories_with_plugins(self) -> list
"Get all plugins in a category."
def get_categories_with_plugins(self) -> list:
"""Get all categories that have registered plugins."""
categories = set(p.category for p in self._plugins.values())
return sorted(categories)
def load_plugin_config(self, unique_id: str) -> Dict[str, Any]
"Get all categories that have registered plugins."
def load_plugin_config(self, unique_id: str) -> Dict[str, Any]:
"""Load saved configuration for a plugin."""
import json
config_file = self._config_dir / f"{unique_id}.json"
if config_file.exists()
"Load saved configuration for a plugin."
def save_plugin_config(self, unique_id: str, config: Dict[str, Any]) -> bool:
"""Save configuration for a plugin."""
import json
try
"Save configuration for a plugin."
Routes (routes.ipynb)
FastHTML route handlers for settings interface
Import
from cjm_fasthtml_settings.routes import (
config,
settings_ar,
RoutesConfig,
index,
load_form,
save,
reset,
plugin_reset,
plugin_save,
plugin
)
Functions
def _resolve_schema(id: str)
"""
Resolve schema from ID using the registry.
Handles both direct schemas and grouped schemas via registry.resolve_schema().
Returns:
tuple: (schema, error_message) - schema is None if error occurred
"""
def _handle_htmx_request(request, content_fn: Callable, *args, **kwargs):
"""Handle HTMX vs full page response pattern."""
content = content_fn(*args, **kwargs)
# Check if this is an HTMX request
if request.headers.get('HX-Request')
"Handle HTMX vs full page response pattern."
def _create_settings_response(
schema: Dict[str, Any],
values: Dict[str, Any],
save_url: str,
reset_url: str,
alert_msg,
sidebar_id: str
)
"Create standardized settings form response with sidebar."
@settings_ar
def index(request, id: str = None):
"""Main settings page.
Args:
request: FastHTML request object
id: Schema ID to display (defaults to config.default_schema)
"""
if id is None
"""
Main settings page.
Args:
request: FastHTML request object
id: Schema ID to display (defaults to config.default_schema)
"""
@settings_ar
def load_form(id: str = None):
"""Async endpoint that loads the settings form.
Args:
id: Schema ID to load (defaults to config.default_schema)
"""
from cjm_fasthtml_tailwind.utilities.flexbox_and_grid import flex
from cjm_fasthtml_tailwind.utilities.sizing import min_h
from cjm_fasthtml_tailwind.core.base import combine_classes
if id is None
"""
Async endpoint that loads the settings form.
Args:
id: Schema ID to load (defaults to config.default_schema)
"""
@settings_ar
async def save(request, id: str):
"""Save configuration handler.
Args:
request: FastHTML request object
id: Schema ID to save
"""
schema, error_msg = _resolve_schema(id)
if error_msg
"""
Save configuration handler.
Args:
request: FastHTML request object
id: Schema ID to save
"""
@settings_ar
def reset(id: str):
"""Reset configuration to defaults handler.
Args:
id: Schema ID to reset
"""
schema, error_msg = _resolve_schema(id)
if error_msg
"""
Reset configuration to defaults handler.
Args:
id: Schema ID to reset
"""
@settings_ar
def plugin_reset(id: str):
"""Reset plugin configuration to defaults handler.
Args:
id: Plugin unique ID
"""
if not config.plugin_registry
"""
Reset plugin configuration to defaults handler.
Args:
id: Plugin unique ID
"""
@settings_ar
async def plugin_save(request, id: str):
"""Save plugin configuration handler.
Args:
request: FastHTML request object
id: Plugin unique ID
"""
if not config.plugin_registry
"""
Save plugin configuration handler.
Args:
request: FastHTML request object
id: Plugin unique ID
"""
@settings_ar
def plugin(request, id: str):
"""Plugin settings page.
Args:
request: FastHTML request object
id: Plugin unique ID
"""
if not config.plugin_registry
"""
Plugin settings page.
Args:
request: FastHTML request object
id: Plugin unique ID
"""
Classes
class RoutesConfig:
"""
Configuration for settings routes behavior.
Users can modify these before importing the router:
Example:
```python
from cjm_fasthtml_settings.routes import config
config.config_dir = Path("my_configs")
config.default_schema = "database"
config.wrap_with_layout = my_layout_function
config.plugin_registry = my_plugin_registry
# Now import the router
from cjm_fasthtml_settings.routes import settings_ar
```
"""
Schema Group (schema_group.ipynb)
Grouping related configuration schemas for better organization
Import
from cjm_fasthtml_settings.core.schema_group import (
SchemaGroup
)
Classes
@dataclass
class SchemaGroup:
"""
A group of related configuration schemas.
Use SchemaGroup to organize multiple related schemas under a single
collapsible section in the settings sidebar. This is useful for
applications with many configuration options.
Example:
```python
media_group = SchemaGroup(
name="media",
title="Media Settings",
schemas={
"scanner": MEDIA_SCAN_SCHEMA,
"player": MEDIA_PLAYER_SCHEMA,
"library": MEDIA_LIBRARY_SCHEMA
},
default_open=True,
description="Configure media scanning, playback, and library"
)
```
Attributes:
name: Internal identifier for the group (used in unique IDs)
title: Display title shown in the sidebar
schemas: Dictionary mapping schema keys to schema definitions
icon: Optional icon/SVG element for the group
default_open: Whether the group is expanded by default
description: Optional description of the group
"""
name: str
title: str
schemas: Dict[str, Dict[str, Any]]
icon: Optional[Any]
default_open: bool = True
description: Optional[str]
def get_schema(self, schema_name: str) -> Optional[Dict[str, Any]]:
"""Get a specific schema from the group by name."""
return self.schemas.get(schema_name)
def get_unique_id(self, schema_name: str) -> str
"Get a specific schema from the group by name."
def get_unique_id(self, schema_name: str) -> str:
"""Generate a unique ID for a schema within this group.
Format: {group_name}_{schema_name}
Example: "media_scanner"
"""
return f"{self.name}_{schema_name}"
def has_configured_schemas(
self,
config_dir: Path # Directory where config files are stored
) -> bool: # True if any schema in group has saved config
"Generate a unique ID for a schema within this group.
Format: {group_name}_{schema_name}
Example: "media_scanner""
def has_configured_schemas(
self,
config_dir: Path # Directory where config files are stored
) -> bool: # True if any schema in group has saved config
"Check if any schemas in this group have saved configurations."
def get_configured_schemas(
self,
config_dir: Path # Directory where config files are stored
) -> list: # List of schema names that have saved configs
"Get list of configured schema names in this group."
Schemas (schemas.ipynb)
Schema registry and management for settings
Import
from cjm_fasthtml_settings.core.schemas import (
registry,
SettingsRegistry
)
Classes
class SettingsRegistry:
def __init__(self):
self._schemas: Dict[str, Union[Dict[str, Any], 'SchemaGroup']] = {}
"""
Registry for managing settings schemas and schema groups.
Provides a centralized place to register and access settings schemas.
Supports both individual schemas and SchemaGroup objects for organizing
related configurations.
Example:
```python
from cjm_fasthtml_settings.core.schemas import registry
from cjm_fasthtml_settings.core.schema_group import SchemaGroup
# Register a simple schema
registry.register(my_schema)
# Register a schema group
registry.register(SchemaGroup(
name="media",
title="Media Settings",
schemas={"scanner": SCAN_SCHEMA, "player": PLAYER_SCHEMA}
))
```
"""
def __init__(self):
self._schemas: Dict[str, Union[Dict[str, Any], 'SchemaGroup']] = {}
def register(
self,
schema: Union[Dict[str, Any], 'SchemaGroup'], # Schema or SchemaGroup to register
name: Optional[str] = None # Optional name override
)
"Register a settings schema or schema group.
For schemas: must have a 'name' field or provide name parameter
For SchemaGroups: uses the group's name attribute"
def get(
self,
name: str # Name of the schema/group to retrieve
) -> Optional[Union[Dict[str, Any], 'SchemaGroup']]: # The schema/group, or None if not found
"Get a registered schema or group by name."
def list_schemas(self) -> list: # List of registered schema/group names
"""List all registered schema and group names."""
return list(self._schemas.keys())
def get_all(self) -> Dict[str, Union[Dict[str, Any], 'SchemaGroup']]: # All schemas and groups
"List all registered schema and group names."
def get_all(self) -> Dict[str, Union[Dict[str, Any], 'SchemaGroup']]: # All schemas and groups
"""Get all registered schemas and groups."""
return self._schemas.copy()
def resolve_schema(
self,
id: str # Schema ID (can be 'name' or 'group_schema' format)
) -> tuple: # (schema_dict, error_message)
"Get all registered schemas and groups."
def resolve_schema(
self,
id: str # Schema ID (can be 'name' or 'group_schema' format)
) -> tuple: # (schema_dict, error_message)
"Resolve a schema ID to a schema dictionary.
Handles both direct schemas and grouped schemas:
- Direct: 'general' -> returns the general schema
- Grouped: 'media_scanner' -> returns scanner schema from media group
Args:
id: Schema identifier
Returns:
Tuple of (schema_dict, error_message). If successful, error_message is None.
If failed, schema_dict is None and error_message explains the issue."
Sidebar (sidebar.ipynb)
Navigation menu components for settings sidebar
Import
from cjm_fasthtml_settings.components.sidebar import (
create_sidebar_menu,
create_oob_sidebar_menu
)
Functions
def create_sidebar_menu(
schemas: Dict[str, Any], # Dictionary of schemas/groups to display in sidebar
active_schema: Optional[str] = None, # The currently active schema name
config_dir: Optional[Path] = None, # Directory where config files are stored
include_wrapper: bool = True, # Whether to include the outer wrapper div
menu_section_title: str = "Settings", # Title for the settings section
plugin_registry: Optional[Any] = None # Optional plugin registry
) -> Union[Div, Ul]: # Div or Ul element containing the sidebar menu
"""
Create the sidebar navigation menu with support for schema groups and plugins.
Renders schemas, SchemaGroup objects, and plugins (if registry provided).
Args:
schemas: Dictionary mapping names to schemas or SchemaGroup objects
active_schema: Name of the currently active schema (can be 'group_schema' format)
config_dir: Path to config directory (uses DEFAULT_CONFIG_DIR if None)
include_wrapper: If False, returns just the Ul for OOB swaps
menu_section_title: Title to display for the menu section
plugin_registry: Optional plugin registry implementing PluginRegistryProtocol
Returns:
Sidebar menu component
"""
def create_oob_sidebar_menu(
schemas: Dict[str, Dict[str, Any]], # Dictionary of schemas
active_schema: str, # Active schema name
config_dir: Optional[Path] = None, # Config directory
menu_section_title: str = "Settings", # Menu section title
plugin_registry: Optional[Any] = None # Optional plugin registry
)
"""
Create sidebar menu with OOB swap attribute for HTMX.
This is useful for updating the sidebar menu without a full page reload.
"""
Utils (utils.ipynb)
Configuration loading, saving, and conversion utilities
Import
from cjm_fasthtml_settings.core.utils import (
load_config,
save_config,
get_default_values_from_schema,
get_config_with_defaults,
convert_form_data_to_config
)
Functions
def load_config(
schema_name: str, # Name of the schema/configuration to load
config_dir: Optional[Path] = None # Directory where config files are stored
) -> Dict[str, Any]: # Loaded configuration dictionary (empty dict if file doesn't exist)
"""
Load saved configuration for a schema.
Loads a JSON configuration file from the config directory.
If the file doesn't exist, returns an empty dictionary.
"""
def save_config(
schema_name: str, # Name of the schema/configuration to save
config: Dict[str, Any], # Configuration dictionary to save
config_dir: Optional[Path] = None # Directory where config files are stored
) -> bool: # True if save succeeded, False otherwise
"""
Save configuration for a schema.
Saves a configuration dictionary as a JSON file in the config directory.
Creates the config directory if it doesn't exist.
"""
def get_default_values_from_schema(
schema: Dict[str, Any] # JSON Schema dictionary
) -> Dict[str, Any]: # Dictionary of default values extracted from schema
"""
Extract default values from a JSON schema.
Iterates through the schema's properties and extracts any 'default' values.
"""
def get_config_with_defaults(
schema_name: str, # Name of the schema (or unique_id for grouped schemas)
schema: Dict[str, Any], # JSON Schema dictionary
config_dir: Optional[Path] = None # Directory where config files are stored
) -> Dict[str, Any]: # Merged configuration with defaults and saved values
"""
Get configuration with defaults merged with saved values.
Loads saved configuration and merges it with schema defaults.
Saved values take precedence over defaults.
For grouped schemas, uses the 'unique_id' field if present.
"""
def convert_form_data_to_config(
form_data: dict, # Raw form data from request
schema: Dict[str, Any] # JSON Schema for type conversion
) -> dict: # Converted configuration dictionary
"""
Convert form data to configuration dict based on schema.
Handles type conversions for:
- Boolean fields (checkboxes)
- Integer and number fields
- Array fields (comma-separated or Python list format)
"""
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_settings-0.0.2.tar.gz.
File metadata
- Download URL: cjm_fasthtml_settings-0.0.2.tar.gz
- Upload date:
- Size: 33.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
37105ae90a9ca746f826247437f9ab796084301fa32c9f89c19e33319ad6be21
|
|
| MD5 |
7541acfbcbadb4472cf5d162c647b697
|
|
| BLAKE2b-256 |
2a5914cd0db5b0974ab3166f4b5cbd5ce3bc284a1c7ed16d73fb024b76d67b98
|
File details
Details for the file cjm_fasthtml_settings-0.0.2-py3-none-any.whl.
File metadata
- Download URL: cjm_fasthtml_settings-0.0.2-py3-none-any.whl
- Upload date:
- Size: 32.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f2a9a87b8487835967b889025c8939566b2b93a50e50d8bb3f98bae030866729
|
|
| MD5 |
ae0cc0096da593810b8d1a1f148a6ba8
|
|
| BLAKE2b-256 |
0f04be70537ad21c59d452441983e5416ae5b4169da1f4f0bbde64f6892870dd
|