Coverage for nexusLIMS/tui/common/base_app.py: 100%
74 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"""
2Base application class for NexusLIMS TUI applications.
4Provides common functionality including theme switching, database session
5management, help screens, and error notifications.
6"""
8import logging
9from pathlib import Path
10from typing import ClassVar
12from sqlmodel import Session
13from textual.app import App
14from textual.screen import Screen
15from textual.widgets import Footer, Header, Markdown
17from nexusLIMS.db.engine import get_engine
19_logger = logging.getLogger(__name__)
22class HelpScreen(Screen):
23 """Help screen showing keybindings and usage information."""
25 BINDINGS: ClassVar = [
26 ("escape", "dismiss", "Close Help"),
27 ("q", "dismiss", "Close Help"),
28 ]
30 def __init__(
31 self,
32 app_name: str,
33 keybindings: list[tuple[str, str]],
34 description: str = "",
35 **kwargs,
36 ):
37 """
38 Initialize help screen.
40 Parameters
41 ----------
42 app_name : str
43 Name of the application
44 keybindings : list[tuple[str, str]]
45 List of (key, description) tuples
46 description : str
47 Optional prose blurb shown between the title and keybindings.
48 **kwargs
49 Additional arguments passed to Screen
50 """
51 super().__init__(**kwargs)
52 self.app_name = app_name
53 self.keybindings = keybindings
54 self.description = description
56 def compose(self):
57 """Compose help screen layout."""
58 yield Header()
60 help_text = f"# {self.app_name}\n\n"
61 if self.description:
62 help_text += f"{self.description}\n\n"
63 help_text += "## Keybindings\n\n"
65 for key, description in self.keybindings:
66 help_text += f"- **{key}**: {description}\n"
68 help_text += "\n## Theme Switching\n\n"
69 help_text += "- **Ctrl+T**: Toggle between dark and light themes\n"
70 help_text += "\n## Navigation\n\n"
71 help_text += "- **Arrow keys**: Navigate menus and forms\n"
72 help_text += "- **Tab**: Move between form fields\n"
73 help_text += "- **Enter**: Select/Submit\n"
74 help_text += "- **Escape**: Cancel/Go back\n"
76 yield Markdown(help_text, id="help-content")
77 yield Footer()
79 def action_dismiss(self):
80 """Dismiss the help screen."""
81 self.app.pop_screen()
84class BaseNexusApp(App):
85 """
86 Base application class for NexusLIMS TUI apps.
88 Provides:
89 - Theme switching (dark/light) using Textual's built-in themes
90 - Database session management
91 - Help screen
92 - Error notifications
93 - Common keybindings
95 Subclasses should:
96 - Define their own screens
97 - Override get_app_name() to return app name
98 - Override get_keybindings() to add app-specific bindings
99 """
101 CSS_PATH: ClassVar = [Path(__file__).parent.parent / "styles" / "base_app.tcss"]
103 BINDINGS: ClassVar = [
104 ("ctrl+t", "toggle_theme", "Toggle Theme"),
105 ("ctrl+q", "quit", "Quit"),
106 ("?", "help", "Help"),
107 ]
109 def __init__(self, **kwargs):
110 """Initialize the base application with dark mode by default."""
111 # Will be set in on_mount
112 self.db_session: Session | None = None
114 super().__init__(**kwargs)
116 def on_mount(self) -> None:
117 """Set up database connection."""
118 # Create database session (if not already set by subclass)
119 if self.db_session is None:
120 try:
121 self.db_session = Session(get_engine())
122 _logger.debug("Database session created")
123 except Exception as e:
124 _logger.exception("Failed to create database session")
125 self.show_error(f"Database connection failed: {e}")
127 def on_unmount(self) -> None:
128 """Clean up database connection."""
129 if self.db_session:
130 self.db_session.close()
131 _logger.debug("Database session closed")
133 def action_toggle_theme(self) -> None:
134 """Toggle between dark and light themes using Textual's built-in system."""
135 # Use Textual's built-in action to toggle between textual-dark and textual-light
136 self.action_toggle_dark()
138 # Show notification
139 theme_name = "light" if self.current_theme.name == "textual-light" else "dark"
140 self.notify(f"Switched to {theme_name} mode", timeout=2)
141 _logger.debug("Theme toggled to: %s", self.current_theme.name)
143 def action_help(self) -> None:
144 """Show help screen."""
145 help_screen = HelpScreen(
146 app_name=self.get_app_name(),
147 keybindings=self.get_keybindings(),
148 )
149 self.push_screen(help_screen)
151 def get_app_name(self) -> str:
152 """
153 Get application name for help screen.
155 Returns
156 -------
157 str
158 Application name (override in subclass)
159 """
160 return "NexusLIMS TUI"
162 def get_keybindings(self) -> list[tuple[str, str]]:
163 """
164 Get app-specific keybindings for help screen.
166 Returns
167 -------
168 list[tuple[str, str]]
169 List of (key, description) tuples (override in subclass)
170 """
171 return [
172 ("ctrl+q", "Quit application"),
173 ("ctrl+t", "Toggle theme (dark/light)"),
174 ("?", "Show this help"),
175 ]
177 def show_error(self, message: str) -> None:
178 """
179 Display an error notification.
181 Parameters
182 ----------
183 message : str
184 Error message to display
185 """
186 # Use Textual's notify system
187 self.notify(message, severity="error", timeout=5)
188 _logger.error(message)
190 def show_success(self, message: str) -> None:
191 """
192 Display a success notification.
194 Parameters
195 ----------
196 message : str
197 Success message to display
198 """
199 self.notify(message, severity="information", timeout=3)
200 _logger.info(message)
202 def show_warning(self, message: str) -> None:
203 """
204 Display a warning notification.
206 Parameters
207 ----------
208 message : str
209 Warning message to display
210 """
211 self.notify(message, severity="warning", timeout=4)
212 _logger.warning(message)