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

1""" 

2NexusLIMS Configuration TUI Application. 

3 

4Provides an interactive terminal UI for editing the NexusLIMS ``.env`` 

5configuration file. Launched via ``nexuslims config edit``. 

6""" 

7 

8from pathlib import Path 

9from typing import ClassVar 

10 

11from nexusLIMS.tui.common.base_app import BaseNexusApp, HelpScreen 

12 

13 

14class ConfiguratorApp(BaseNexusApp): 

15 """ 

16 TUI application for interactively editing NexusLIMS configuration. 

17 

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`. 

22 

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 """ 

29 

30 CSS_PATH: ClassVar = [ 

31 *BaseNexusApp.CSS_PATH, 

32 ] 

33 

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] 

44 

45 @property 

46 def env_path(self) -> Path: 

47 """Return the path to the .env file being edited.""" 

48 return self._env_path 

49 

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}" 

54 

55 from nexusLIMS.tui.apps.config.screens import ConfigScreen # noqa: PLC0415 

56 

57 self.push_screen(ConfigScreen(self._env_path)) 

58 

59 def get_app_name(self) -> str: 

60 """Return application name for the help screen.""" 

61 return "NexusLIMS Configurator" 

62 

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 ) 

75 

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 ) 

85 

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()