Coverage for src/pytribeam/GUI/common/config_manager.py: 0%

52 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2026-06-16 18:30 +0000

1"""Application configuration management. 

2 

3This module provides centralized management of application-wide settings 

4and ensures necessary directories exist. 

5""" 

6 

7import os 

8from dataclasses import dataclass, field 

9from pathlib import Path 

10from typing import Optional, List 

11 

12 

13@dataclass 

14class AppConfig: 

15 """Application-wide configuration settings. 

16 

17 This dataclass holds all application-level configuration that persists 

18 across sessions and is not specific to individual experiments. 

19 

20 Attributes: 

21 data_dir: Directory for application data 

22 log_dir: Directory for log files 

23 default_theme: Default UI theme ('dark' or 'light') 

24 auto_save_interval: Seconds between auto-saves (0 to disable) 

25 recent_configs: List of recently opened config files 

26 max_recent_files: Maximum number of recent files to track 

27 """ 

28 

29 data_dir: Path 

30 log_dir: Path 

31 default_theme: str = "dark" 

32 auto_save_interval: int = 300 # seconds 

33 recent_configs: List[str] = field(default_factory=list) 

34 max_recent_files: int = 10 

35 

36 @classmethod 

37 def from_env(cls, app_name: str = "pytribeam") -> "AppConfig": 

38 """Create configuration from environment variables. 

39 

40 Uses LOCALAPPDATA on Windows or ~/.local/share on Unix-like systems. 

41 

42 Args: 

43 app_name: Application name for directory structure 

44 

45 Returns: 

46 AppConfig instance with paths set from environment 

47 """ 

48 if os.name == "nt": 

49 # Windows 

50 base_dir = Path(os.getenv("LOCALAPPDATA", os.path.expanduser("~"))) 

51 else: 

52 # Unix-like 

53 base_dir = Path( 

54 os.getenv("XDG_DATA_HOME", os.path.expanduser("~/.local/share")) 

55 ) 

56 

57 app_dir = base_dir / app_name 

58 

59 return cls( 

60 data_dir=app_dir / "data", 

61 log_dir=app_dir / "logs", 

62 ) 

63 

64 @classmethod 

65 def from_file(cls, config_file: Path) -> "AppConfig": 

66 """Load configuration from JSON file. 

67 

68 Args: 

69 config_file: Path to configuration JSON file 

70 

71 Returns: 

72 AppConfig instance loaded from file 

73 

74 Raises: 

75 FileNotFoundError: If config file doesn't exist 

76 ValueError: If config file is invalid 

77 """ 

78 import json 

79 

80 if not config_file.exists(): 

81 raise FileNotFoundError(f"Config file not found: {config_file}") 

82 

83 with open(config_file, "r") as f: 

84 data = json.load(f) 

85 

86 return cls( 

87 data_dir=Path(data["data_dir"]), 

88 log_dir=Path(data["log_dir"]), 

89 default_theme=data.get("default_theme", "dark"), 

90 auto_save_interval=data.get("auto_save_interval", 300), 

91 recent_configs=data.get("recent_configs", []), 

92 max_recent_files=data.get("max_recent_files", 10), 

93 ) 

94 

95 def save_to_file(self, config_file: Path): 

96 """Save configuration to JSON file. 

97 

98 Args: 

99 config_file: Path where config should be saved 

100 """ 

101 import json 

102 

103 config_file.parent.mkdir(parents=True, exist_ok=True) 

104 

105 data = { 

106 "data_dir": str(self.data_dir), 

107 "log_dir": str(self.log_dir), 

108 "default_theme": self.default_theme, 

109 "auto_save_interval": self.auto_save_interval, 

110 "recent_configs": self.recent_configs, 

111 "max_recent_files": self.max_recent_files, 

112 } 

113 

114 with open(config_file, "w") as f: 

115 json.dump(data, f, indent=2) 

116 

117 def ensure_directories(self): 

118 """Create necessary directories if they don't exist. 

119 

120 This should be called during application startup to ensure 

121 all required directories are present. 

122 """ 

123 self.data_dir.mkdir(parents=True, exist_ok=True) 

124 self.log_dir.mkdir(parents=True, exist_ok=True) 

125 

126 def add_recent_config(self, config_path: Path): 

127 """Add configuration file to recent files list. 

128 

129 Args: 

130 config_path: Path to configuration file to add 

131 """ 

132 config_str = str(config_path.absolute()) 

133 

134 # Remove if already in list 

135 if config_str in self.recent_configs: 

136 self.recent_configs.remove(config_str) 

137 

138 # Add to front of list 

139 self.recent_configs.insert(0, config_str) 

140 

141 # Trim to max length 

142 self.recent_configs = self.recent_configs[: self.max_recent_files] 

143 

144 def get_recent_configs(self) -> List[Path]: 

145 """Get list of recent configuration files that still exist. 

146 

147 Returns: 

148 List of Path objects for existing recent configs 

149 """ 

150 return [Path(p) for p in self.recent_configs if Path(p).exists()] 

151 

152 def get_terminal_log_path(self) -> Path: 

153 """Get path for new terminal log file. 

154 

155 Returns: 

156 Path for terminal log file with timestamp 

157 """ 

158 import time 

159 

160 timestamp = time.strftime("%Y%m%d-%H%M%S") 

161 return self.log_dir / f"{timestamp}_terminal.txt" 

162 

163 def get_error_log_path(self) -> Path: 

164 """Get path for new error traceback file. 

165 

166 Returns: 

167 Path for error log file with timestamp 

168 """ 

169 import time 

170 

171 timestamp = time.strftime("%Y%m%d-%H%M%S") 

172 return self.log_dir / f"{timestamp}_error_traceback.txt"