Skip to content

Configuration and Runtime

Configuration and runtime state services for project setup and UI behavior.


ConfigService

ConfigService

ConfigService()

Bases: ConfigServiceProtocol

Modular configuration service for EzQt_App.

Source code in src/ezqt_app/services/config/config_service.py
def __init__(self):
    self._config_cache: dict[str, Any] = {}
    self._config_files: dict[str, Path] = {}
    self._project_root: Path | None = None

set_project_root

set_project_root(project_root: Path | str) -> None

Set the active project root directory.

Source code in src/ezqt_app/services/config/config_service.py
def set_project_root(self, project_root: Path | str) -> None:
    """Set the active project root directory."""
    self._project_root = (
        project_root if isinstance(project_root, Path) else Path(project_root)
    )
    from ...utils.runtime_paths import _sync_bin_path_from_root

    _sync_bin_path_from_root(self._project_root / "bin")

load_config

load_config(config_name: str, force_reload: bool = False) -> dict[str, Any]

Load a named configuration from the first matching path.

Parameters

config_name: Configuration file name (without extension, e.g. "app"). force_reload: Bypass cache and reload from disk.

Returns

dict[str, Any] Loaded configuration data, or empty dict on failure.

Source code in src/ezqt_app/services/config/config_service.py
def load_config(
    self, config_name: str, force_reload: bool = False
) -> dict[str, Any]:
    """Load a named configuration from the first matching path.

    Parameters
    ----------
    config_name:
        Configuration file name (without extension, e.g. ``"app"``).
    force_reload:
        Bypass cache and reload from disk.

    Returns
    -------
    dict[str, Any]
        Loaded configuration data, or empty dict on failure.
    """
    if not force_reload and config_name in self._config_cache:
        return self._config_cache[config_name]

    config_file = self._resolve_config_file(config_name)

    if not config_file:
        warn_user(
            code="config.service.missing_file",
            user_message=f"No configuration file found for '{config_name}'",
            log_message=f"No configuration file found for '{config_name}'",
        )
        get_printer().verbose_msg(
            f"Searched paths: {self.get_config_paths(config_name)}"
        )
        return {}

    try:
        with open(config_file, encoding="utf-8") as f:
            config_data = yaml.safe_load(f)

        self._config_cache[config_name] = config_data
        self._config_files[config_name] = config_file

        get_printer().verbose_msg(
            f"Configuration '{config_name}' loaded from: {config_file}"
        )
        return config_data

    except Exception as e:
        get_printer().error(f"Error loading '{config_name}': {e}")
        return {}

get_config_value

get_config_value(config_name: str, key_path: str, default: Any = None) -> Any

Read a specific value from a configuration using dot-notation key path.

Parameters

config_name: Configuration file name. key_path: Dot-separated path (e.g. "app.name" or "palette.dark"). default: Value returned when key is absent.

Source code in src/ezqt_app/services/config/config_service.py
def get_config_value(
    self, config_name: str, key_path: str, default: Any = None
) -> Any:
    """Read a specific value from a configuration using dot-notation key path.

    Parameters
    ----------
    config_name:
        Configuration file name.
    key_path:
        Dot-separated path (e.g. ``"app.name"`` or ``"palette.dark"``).
    default:
        Value returned when key is absent.
    """
    config = self.load_config(config_name)
    keys = key_path.split(".")
    current = config

    for key in keys:
        if isinstance(current, dict) and key in current:
            current = current[key]
        else:
            return default

    return current

save_config

save_config(config_name: str, config_data: dict[str, Any]) -> bool

Persist a named configuration to the project directory.

Parameters

config_name: Configuration file name. config_data: Data to serialise as YAML.

Returns

bool True if the write succeeded.

Source code in src/ezqt_app/services/config/config_service.py
def save_config(self, config_name: str, config_data: dict[str, Any]) -> bool:
    """Persist a named configuration to the project directory.

    Parameters
    ----------
    config_name:
        Configuration file name.
    config_data:
        Data to serialise as YAML.

    Returns
    -------
    bool
        ``True`` if the write succeeded.
    """
    config_file: Path
    if config_name in self._config_files:
        # Keep writes consistent with the file originally loaded.
        config_file = self._config_files[config_name]
        config_file.parent.mkdir(parents=True, exist_ok=True)
    else:
        config_dir = get_bin_path() / "config"
        config_dir.mkdir(parents=True, exist_ok=True)
        config_file = config_dir / f"{config_name}.config.yaml"

    try:
        with open(config_file, "w", encoding="utf-8") as f:
            yaml.dump(config_data, f, default_flow_style=False, allow_unicode=True)

        self._config_cache[config_name] = config_data
        self._config_files[config_name] = config_file

        get_printer().verbose_msg(
            f"Configuration '{config_name}' saved: {config_file}"
        )
        return True

    except Exception as e:
        get_printer().error(f"Error saving '{config_name}': {e}")
        return False

get_config_paths

get_config_paths(config_name: str) -> list[Path]

Return candidate paths for config_name in priority order.

Parameters

config_name: Logical configuration name (e.g. "app", "languages", "theme"). Files are resolved as <name>.config.yaml.

Source code in src/ezqt_app/services/config/config_service.py
def get_config_paths(self, config_name: str) -> list[Path]:
    """Return candidate paths for *config_name* in priority order.

    Parameters
    ----------
    config_name:
        Logical configuration name (e.g. ``"app"``, ``"languages"``,
        ``"theme"``). Files are resolved as ``<name>.config.yaml``.
    """
    config_file = f"{config_name}.config.yaml"
    paths: list[Path] = []

    # 1. Configured bin directory
    paths.append(get_bin_path() / "config" / config_file)

    # 3. Package resources — relative to cwd
    paths.append(Path.cwd() / "ezqt_app" / "resources" / "config" / config_file)

    # 4. Package resources — relative to APP_PATH
    paths.append(APP_PATH / "resources" / "config" / config_file)

    return paths

copy_package_configs_to_project

copy_package_configs_to_project() -> bool

Copy package configuration files into the child project.

Returns

bool True if the operation succeeded.

Source code in src/ezqt_app/services/config/config_service.py
def copy_package_configs_to_project(self) -> bool:
    """Copy package configuration files into the child project.

    Returns
    -------
    bool
        ``True`` if the operation succeeded.
    """
    if not self._project_root:
        get_printer().error("[ConfigService] No project root defined")
        return False

    package_config_dir = self._find_package_config_dir()

    if not package_config_dir:
        get_printer().error(
            f"[ConfigService] EzQt_App package not found. Tested paths: "
            f"{[str(p) for p in [Path.cwd(), APP_PATH]]}"
        )
        return False

    project_config_dir = get_bin_path() / "config"
    project_config_dir.mkdir(parents=True, exist_ok=True)

    copied_files: list[str] = []

    try:
        for config_file in package_config_dir.glob("*.yaml"):
            target_file = project_config_dir / config_file.name

            if target_file.exists():
                get_printer().verbose_msg(f"Existing file, ignored: {target_file}")
                continue

            shutil.copy2(config_file, target_file)
            copied_files.append(config_file.name)
            get_printer().action(
                f"[ConfigService] Configuration copied: {config_file.name}"
            )

        if copied_files:
            get_printer().action(
                "[ConfigService] "
                f"{len(copied_files)} configurations copied to project"
            )

        return True

    except Exception as e:
        get_printer().error(f"Error copying configurations: {e}")
        return False

clear_cache

clear_cache() -> None

Invalidate the in-memory configuration cache.

Source code in src/ezqt_app/services/config/config_service.py
def clear_cache(self) -> None:
    """Invalidate the in-memory configuration cache."""
    self._config_cache.clear()
    self._config_files.clear()
    get_printer().verbose_msg("Configuration cache cleared")

get_loaded_configs

get_loaded_configs() -> dict[str, Path]

Return a snapshot of currently cached configuration paths.

Source code in src/ezqt_app/services/config/config_service.py
def get_loaded_configs(self) -> dict[str, Path]:
    """Return a snapshot of currently cached configuration paths."""
    return self._config_files.copy()

SettingsService

SettingsService

SettingsService()

Bases: SettingsServiceProtocol

Service managing mutable application settings state.

Initialize settings state container.

Source code in src/ezqt_app/services/settings/settings_service.py
def __init__(self) -> None:
    """Initialize settings state container."""
    self._state = SettingsStateModel()

app property

app: AppSettingsModel

Return mutable application settings.

gui property

gui: GuiSettingsModel

Return mutable GUI settings.

set_app_name

set_app_name(name: str) -> None

Set application name.

Source code in src/ezqt_app/services/settings/settings_service.py
def set_app_name(self, name: str) -> None:
    """Set application name."""
    self.app.NAME = name

set_app_description

set_app_description(description: str) -> None

Set application description.

Source code in src/ezqt_app/services/settings/settings_service.py
def set_app_description(self, description: str) -> None:
    """Set application description."""
    self.app.DESCRIPTION = description

set_custom_title_bar_enabled

set_custom_title_bar_enabled(enabled: bool) -> None

Enable or disable custom title bar.

Source code in src/ezqt_app/services/settings/settings_service.py
def set_custom_title_bar_enabled(self, enabled: bool) -> None:
    """Enable or disable custom title bar."""
    self.app.ENABLE_CUSTOM_TITLE_BAR = enabled

set_app_min_size

set_app_min_size(width: int, height: int) -> None

Set minimum window size.

Source code in src/ezqt_app/services/settings/settings_service.py
def set_app_min_size(self, width: int, height: int) -> None:
    """Set minimum window size."""
    self.app.APP_MIN_SIZE = (width, height)

set_app_min_size_qsize

set_app_min_size_qsize(size: QSize) -> None

Set minimum window size from QSize (convenience, not part of the port).

Source code in src/ezqt_app/services/settings/settings_service.py
def set_app_min_size_qsize(self, size: QSize) -> None:
    """Set minimum window size from QSize (convenience, not part of the port)."""
    self.app.APP_MIN_SIZE = (size.width(), size.height())

set_app_dimensions

set_app_dimensions(width: int, height: int) -> None

Set default window dimensions.

Source code in src/ezqt_app/services/settings/settings_service.py
def set_app_dimensions(self, width: int, height: int) -> None:
    """Set default window dimensions."""
    self.app.APP_WIDTH = width
    self.app.APP_HEIGHT = height

set_debug_enabled

set_debug_enabled(enabled: bool) -> None

Enable or disable debug console output.

Source code in src/ezqt_app/services/settings/settings_service.py
def set_debug_enabled(self, enabled: bool) -> None:
    """Enable or disable debug console output."""
    self.app.DEBUG = enabled

set_theme

set_theme(theme: str) -> None

Set active theme.

Accepts either a 'preset:variant' string (e.g. 'blue_gray:dark') or a bare variant (e.g. 'dark', 'light') for backward compat. A bare variant keeps the current THEME_PRESET unchanged.

Source code in src/ezqt_app/services/settings/settings_service.py
def set_theme(self, theme: str) -> None:
    """Set active theme.

    Accepts either a ``'preset:variant'`` string (e.g. ``'blue_gray:dark'``)
    or a bare variant (e.g. ``'dark'``, ``'light'``) for backward compat.
    A bare variant keeps the current ``THEME_PRESET`` unchanged.
    """
    value = theme.lower().strip()
    if ":" in value:
        preset, _, variant = value.partition(":")
        self.gui.THEME_PRESET = preset.strip()
        self.gui.THEME = variant.strip()
    else:
        self.gui.THEME = value

set_menu_widths

set_menu_widths(shrinked: int, extended: int) -> None

Set menu panel widths.

Source code in src/ezqt_app/services/settings/settings_service.py
def set_menu_widths(self, shrinked: int, extended: int) -> None:
    """Set menu panel widths."""
    self.gui.MENU_PANEL_SHRINKED_WIDTH = shrinked
    self.gui.MENU_PANEL_EXTENDED_WIDTH = extended

set_settings_panel_width

set_settings_panel_width(width: int) -> None

Set settings panel width.

Source code in src/ezqt_app/services/settings/settings_service.py
def set_settings_panel_width(self, width: int) -> None:
    """Set settings panel width."""
    self.gui.SETTINGS_PANEL_WIDTH = width

set_time_animation

set_time_animation(duration: int) -> None

Set animation duration in milliseconds.

Source code in src/ezqt_app/services/settings/settings_service.py
def set_time_animation(self, duration: int) -> None:
    """Set animation duration in milliseconds."""
    self.gui.TIME_ANIMATION = duration

RuntimeStateService

RuntimeStateService

RuntimeStateService()

Bases: RuntimeStateServiceProtocol

Service managing runtime flags and legacy global UI state.

Initialize runtime state.

Source code in src/ezqt_app/services/runtime/runtime_service.py
def __init__(self) -> None:
    """Initialize runtime state."""
    self._state = RuntimeStateModel()

  • ezqt_app.domain.ports.config_service.ConfigServiceProtocol
  • ezqt_app.domain.ports.settings_service.SettingsServiceProtocol
  • ezqt_app.domain.ports.runtime_state_service.RuntimeStateServiceProtocol