Coverage for nexusLIMS/tui/apps/config/app.py: 100%
24 statements
« prev ^ index » next coverage.py v7.11.3, created at 2026-03-24 05:23 +0000
« prev ^ index » next coverage.py v7.11.3, created at 2026-03-24 05:23 +0000
1"""
2NexusLIMS Configuration TUI Application.
4Provides an interactive terminal UI for editing the NexusLIMS ``.env``
5configuration file. Launched via ``nexuslims config edit``.
6"""
8from pathlib import Path
9from typing import ClassVar
11from nexusLIMS.tui.common.base_app import BaseNexusApp, HelpScreen
14class ConfiguratorApp(BaseNexusApp):
15 """
16 TUI application for interactively editing NexusLIMS configuration.
18 Reads the current ``.env`` file, presents all settings in a tabbed form,
19 validates input, and writes a new ``.env`` on save. No database
20 connection is required, so ``on_mount`` skips the DB session setup from
21 :class:`~nexusLIMS.tui.common.base_app.BaseNexusApp`.
23 Parameters
24 ----------
25 env_path : pathlib.Path | str
26 Path to the ``.env`` file to edit. The file is created (empty) if it
27 does not yet exist. Defaults to ``".env"`` in the current directory.
28 """
30 CSS_PATH: ClassVar = [
31 *BaseNexusApp.CSS_PATH,
32 ]
34 def __init__(self, env_path: Path | str = ".env", **kwargs):
35 """Initialize the configurator app."""
36 self._env_path = Path(env_path)
37 super().__init__(**kwargs)
38 # Prevent BaseNexusApp.on_mount from creating a DB session.
39 # Textual dispatches on_mount to every handler in the MRO; setting a
40 # non-None falsy sentinel makes the base-class guard (`if self.db_session
41 # is None`) evaluate to False while `on_unmount`'s `if self.db_session:`
42 # check also stays False, so no .close() is called on a non-Session.
43 self.db_session = False # type: ignore[assignment]
45 @property
46 def env_path(self) -> Path:
47 """Return the path to the .env file being edited."""
48 return self._env_path
50 def on_mount(self) -> None:
51 """Push the config screen (no database session needed)."""
52 # Intentionally skip super().on_mount() to avoid creating a DB session
53 self.title = f"NexusLIMS Configurator — {self._env_path}"
55 from nexusLIMS.tui.apps.config.screens import ConfigScreen # noqa: PLC0415
57 self.push_screen(ConfigScreen(self._env_path))
59 def get_app_name(self) -> str:
60 """Return application name for the help screen."""
61 return "NexusLIMS Configurator"
63 _HELP_DESCRIPTION = (
64 "The NexusLIMS Configurator reads and writes a `.env` file that controls "
65 "all NexusLIMS settings. On launch it loads the existing file (if present) "
66 "and pre-populates every field. Pressing **Save (Ctrl+S)** writes a new "
67 "`.env` at the same path, overwriting the previous contents. Pressing "
68 "**Cancel (Esc)** exits without saving — you will be warned if there are "
69 "unsaved changes.\n\n"
70 "Settings are grouped into tabs: **Core Paths**, **CDCS**, "
71 "**File Processing**, **NEMO Harvesters**, **eLabFTW**, **Email**, and "
72 "**SSL / Certs**. Focus any input field and press **F1** to read extended "
73 "documentation for that field."
74 )
76 def action_help(self) -> None:
77 """Show the configurator help screen with app description."""
78 self.push_screen(
79 HelpScreen(
80 app_name=self.get_app_name(),
81 keybindings=self.get_keybindings(),
82 description=self._HELP_DESCRIPTION,
83 )
84 )
86 def get_keybindings(self) -> list[tuple[str, str]]:
87 """Return app-specific keybindings for the help screen."""
88 app_bindings: list[tuple[str, str]] = [
89 ("f1", "Show extended help for the focused field"),
90 ("ctrl+s", "Save configuration to .env file"),
91 ("escape", "Cancel / go back"),
92 ("tab / shift+tab", "Move between fields"),
93 ("< / >", "Navigate to previous / next tab"),
94 ]
95 return app_bindings + super().get_keybindings()