Add settings management infrastructure: models, API, views, and database loading
Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
"""
|
||||
Configuration loader that supports database-persisted settings with precedence.
|
||||
|
||||
This module provides functionality to:
|
||||
- Load settings from database after app initialization
|
||||
- Apply database settings over environment variables
|
||||
- Dynamically reload settings when changed
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Any, Optional
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.models import ApplicationSettings
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def load_settings_from_db(settings_obj, db_session: Session) -> None:
|
||||
"""
|
||||
Load settings from database and apply them to the settings object.
|
||||
|
||||
Database settings take precedence over environment variables and defaults.
|
||||
This function should be called after database initialization.
|
||||
|
||||
Args:
|
||||
settings_obj: The Settings instance to update
|
||||
db_session: Database session to use for loading settings
|
||||
"""
|
||||
try:
|
||||
db_settings = db_session.query(ApplicationSettings).all()
|
||||
|
||||
if not db_settings:
|
||||
logger.info("No database settings found, using environment/defaults")
|
||||
return
|
||||
|
||||
# Apply database settings to the settings object
|
||||
updated_count = 0
|
||||
for db_setting in db_settings:
|
||||
key = db_setting.key
|
||||
value = db_setting.value
|
||||
|
||||
# Check if the setting exists in the Settings class
|
||||
if hasattr(settings_obj, key):
|
||||
# Get the field info to determine the type
|
||||
field_info = settings_obj.__fields__.get(key)
|
||||
if field_info:
|
||||
# Convert value to the appropriate type
|
||||
converted_value = convert_setting_value(value, field_info.annotation)
|
||||
|
||||
# Set the attribute
|
||||
setattr(settings_obj, key, converted_value)
|
||||
updated_count += 1
|
||||
logger.debug(f"Applied database setting: {key}")
|
||||
|
||||
if updated_count > 0:
|
||||
logger.info(f"Loaded {updated_count} settings from database")
|
||||
else:
|
||||
logger.info("No applicable database settings found")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error loading settings from database: {e}")
|
||||
# Don't fail application startup if database settings can't be loaded
|
||||
logger.warning("Continuing with environment/default settings")
|
||||
|
||||
|
||||
def convert_setting_value(value: Optional[str], field_type: Any) -> Any:
|
||||
"""
|
||||
Convert a string value from database to the appropriate type.
|
||||
|
||||
Args:
|
||||
value: String value from database
|
||||
field_type: Target type from Pydantic field annotation
|
||||
|
||||
Returns:
|
||||
Converted value in the appropriate type
|
||||
"""
|
||||
if value is None:
|
||||
return None
|
||||
|
||||
# Handle Optional types
|
||||
origin = getattr(field_type, '__origin__', None)
|
||||
if origin is Union:
|
||||
# Get the non-None type from Union (for Optional)
|
||||
args = getattr(field_type, '__args__', ())
|
||||
field_type = next((arg for arg in args if arg is not type(None)), str)
|
||||
|
||||
# Convert based on type
|
||||
if field_type == bool:
|
||||
return value.lower() in ('true', '1', 'yes', 'y', 't')
|
||||
elif field_type == int:
|
||||
try:
|
||||
return int(value)
|
||||
except ValueError:
|
||||
logger.warning(f"Failed to convert '{value}' to int, returning 0")
|
||||
return 0
|
||||
elif field_type == float:
|
||||
try:
|
||||
return float(value)
|
||||
except ValueError:
|
||||
logger.warning(f"Failed to convert '{value}' to float, returning 0.0")
|
||||
return 0.0
|
||||
elif field_type == list or getattr(field_type, '__origin__', None) == list:
|
||||
# Handle list types - assume comma-separated values
|
||||
if isinstance(value, str):
|
||||
return [item.strip() for item in value.split(',') if item.strip()]
|
||||
return value
|
||||
else:
|
||||
# Default to string
|
||||
return str(value)
|
||||
|
||||
|
||||
def reload_settings_from_db(settings_obj) -> bool:
|
||||
"""
|
||||
Reload settings from database.
|
||||
|
||||
This is useful after settings have been updated through the UI.
|
||||
Note: Some settings require application restart to take effect.
|
||||
|
||||
Args:
|
||||
settings_obj: The Settings instance to update
|
||||
|
||||
Returns:
|
||||
True if reload was successful, False otherwise
|
||||
"""
|
||||
try:
|
||||
from app.database import SessionLocal
|
||||
|
||||
db = SessionLocal()
|
||||
try:
|
||||
load_settings_from_db(settings_obj, db)
|
||||
logger.info("Settings reloaded from database")
|
||||
return True
|
||||
finally:
|
||||
db.close()
|
||||
except Exception as e:
|
||||
logger.error(f"Error reloading settings from database: {e}")
|
||||
return False
|
||||
Reference in New Issue
Block a user