Source code for research_client.settings.eel

"""Exposes app configuration to Python Eel as Settings."""
import eel
import logging
from dataclasses import is_dataclass
from functools import wraps
from pathlib import Path
from typing import Optional, Union, Callable, Any, TypeVar, cast
from .. import booteel
from ..config import config
from ..datavalidator.exceptions import DataValidationError
# from .dataschema import Response

logger = logging.getLogger(__name__)

# TypeVar for function wrappers
F = TypeVar("F", bound=Callable[..., Any])

# Function to be called to handle exceptions, or None to not handle exceptions
exceptionhandler: Optional[Callable[..., None]] = None


[docs]def _handleexception(exc: Exception) -> None: """Passes exception to exceptionhandler if defined, otherwise continues raising.""" logger.exception(exc) if exceptionhandler is not None: exceptionhandler(exc) else: raise exc
[docs]def _expose(func: F) -> F: """Wraps, renames and exposes a function to eel.""" @wraps(func) def api_wrapper( *args: list[Any], **kwargs: dict[str, Any] ) -> Optional[Union[F, bool]]: try: return func(*args, **kwargs) except Exception as exc: if isinstance(exc, DataValidationError): booteel.modal( "Data Validation Error", exc.validator.tohtml(errorsonly=True) ) _handleexception(exc) else: booteel.displayexception(exc) _handleexception(exc) return False eel._expose("_settings_" + func.__name__, api_wrapper) # type: ignore return cast(F, api_wrapper)
[docs]@_expose def load() -> dict[str, dict[str, Any]]: """Return current settings and config documentation.""" logger.info("Fetching app settings..") dataclasses, fields = config.getdocs() settings: dict[str, dict[str, Any]] = {} # Add general settings fields settings["root"] = { "name": "root", "label": "General", "help": "General app settings.", "fields": fields, } for dataclass in dataclasses: settings[dataclass["name"]] = dataclass # Remove irrelevant/redundant attributes for key in ("value", "type", "default"): if key in settings[dataclass["name"]]: del settings[dataclass["name"]][key] # Remove sub-dataclasses from fields attribute settings[dataclass["name"]]["fields"] = settings[dataclass["name"]]["fields"][1] # For Path type arguments, convert default and value to string for fieldindex in range(0, len(settings[dataclass["name"]]["fields"])): if isinstance(settings[dataclass["name"]]["fields"][fieldindex]["default"], Path): settings[dataclass["name"]]["fields"][fieldindex]["default"] = str(settings[dataclass["name"]]["fields"][fieldindex]["default"]) if isinstance(settings[dataclass["name"]]["fields"][fieldindex]["value"], Path): settings[dataclass["name"]]["fields"][fieldindex]["value"] = str(settings[dataclass["name"]]["fields"][fieldindex]["value"]) logger.debug(f" Settings: {settings}") return settings
[docs]@_expose def store(settings: dict[str, dict[str, Any]]) -> bool: # noqa: C901 """Validate settings, store in config, and save..""" from pprint import pprint pprint(config.asdict()) logger.info("Updating app settings...") logger.debug(f"Data received: {settings}") for key, value in settings.items(): if not key.startswith("settings-"): continue key = key.removeprefix("settings-") if "-" not in key: continue section, property = key.split("-") target = None if section == "root": target = config elif hasattr(config, section) and is_dataclass(getattr(config, section)): target = getattr(config, section) if target is None: continue if hasattr(target, property): print(f"Setting {property} on {target}") target_type = type(getattr(target, property)) if type(value) is not target_type: print(f"VALUE IS TYPE {type(value)} - target is type {target_type}") try: value = target_type(value) print("CONVERSION SUCCESSFUL:", value) except ValueError: print("CONVERSION NOT SUCCESSFUL :->") raise ValueError( f"Could not convert input {value!r} to required type {target_type} " f"for field config.{section}.{property}" ) setattr(target, property, value) config.save() logger.info(f"Settings successfully updated to {config.asdict()}.") pprint(config.asdict()) eel._settings_notify_successful_update() # type: ignore return True