Coverage for src / marimushka / config.py: 100%

39 statements  

« prev     ^ index     » next       coverage.py v7.13.4, created at 2026-02-28 17:41 +0000

1"""Configuration management for marimushka. 

2 

3This module provides configuration loading and validation from TOML files. 

4""" 

5 

6import tomllib 

7from pathlib import Path 

8from typing import Any 

9 

10 

11class MarimushkaConfig: 

12 """Configuration for marimushka. 

13 

14 This class encapsulates all configuration options with sensible defaults. 

15 

16 Attributes: 

17 output: Output directory for exported files. 

18 template: Path to Jinja2 template file. 

19 notebooks: Directory containing static notebooks. 

20 apps: Directory containing app notebooks. 

21 notebooks_wasm: Directory containing interactive notebooks. 

22 sandbox: Whether to run exports in sandbox mode. 

23 parallel: Whether to export notebooks in parallel. 

24 max_workers: Maximum number of parallel workers. 

25 timeout: Timeout in seconds for each export. 

26 audit_log: Optional path to audit log file. 

27 audit_enabled: Whether audit logging is enabled. 

28 max_file_size_mb: Maximum file size in MB for templates/notebooks. 

29 file_permissions: Default file permissions (octal). 

30 

31 """ 

32 

33 def __init__( 

34 self, 

35 output: str = "_site", 

36 template: str | None = None, 

37 notebooks: str = "notebooks", 

38 apps: str = "apps", 

39 notebooks_wasm: str = "notebooks_wasm", 

40 sandbox: bool = True, 

41 parallel: bool = True, 

42 max_workers: int = 4, 

43 timeout: int = 300, 

44 audit_log: str | None = None, 

45 audit_enabled: bool = True, 

46 max_file_size_mb: int = 10, 

47 file_permissions: int = 0o644, 

48 ) -> None: 

49 """Initialize configuration with defaults. 

50 

51 Args: 

52 output: Output directory. Defaults to "_site". 

53 template: Template path. Defaults to None (uses built-in). 

54 notebooks: Notebooks directory. Defaults to "notebooks". 

55 apps: Apps directory. Defaults to "apps". 

56 notebooks_wasm: Interactive notebooks directory. Defaults to "notebooks_wasm". 

57 sandbox: Use sandbox mode. Defaults to True. 

58 parallel: Use parallel export. Defaults to True. 

59 max_workers: Max parallel workers. Defaults to 4. 

60 timeout: Export timeout. Defaults to 300. 

61 audit_log: Audit log file path. Defaults to None. 

62 audit_enabled: Enable audit logging. Defaults to True. 

63 max_file_size_mb: Max file size in MB. Defaults to 10. 

64 file_permissions: File permissions. Defaults to 0o644. 

65 

66 """ 

67 self.output = output 

68 self.template = template 

69 self.notebooks = notebooks 

70 self.apps = apps 

71 self.notebooks_wasm = notebooks_wasm 

72 self.sandbox = sandbox 

73 self.parallel = parallel 

74 self.max_workers = max_workers 

75 self.timeout = timeout 

76 self.audit_log = audit_log 

77 self.audit_enabled = audit_enabled 

78 self.max_file_size_mb = max_file_size_mb 

79 self.file_permissions = file_permissions 

80 

81 @classmethod 

82 def from_file(cls, config_path: Path) -> "MarimushkaConfig": 

83 """Load configuration from a TOML file. 

84 

85 Args: 

86 config_path: Path to the configuration file. 

87 

88 Returns: 

89 A MarimushkaConfig instance with loaded settings. 

90 

91 Raises: 

92 FileNotFoundError: If config file doesn't exist. 

93 ValueError: If config file is invalid. 

94 

95 """ 

96 if not config_path.exists(): 

97 raise FileNotFoundError(f"Config file not found: {config_path}") # noqa: TRY003 

98 

99 try: 

100 with config_path.open("rb") as f: 

101 config_data = tomllib.load(f) 

102 except Exception as e: 

103 raise ValueError(f"Failed to parse config file: {e}") from e # noqa: TRY003 

104 

105 # Extract marimushka section 

106 marimushka_config = config_data.get("marimushka", {}) 

107 

108 # Handle security subsection 

109 security_config = marimushka_config.get("security", {}) 

110 

111 return cls( 

112 output=marimushka_config.get("output", "_site"), 

113 template=marimushka_config.get("template"), 

114 notebooks=marimushka_config.get("notebooks", "notebooks"), 

115 apps=marimushka_config.get("apps", "apps"), 

116 notebooks_wasm=marimushka_config.get("notebooks_wasm", "notebooks_wasm"), 

117 sandbox=marimushka_config.get("sandbox", True), 

118 parallel=marimushka_config.get("parallel", True), 

119 max_workers=marimushka_config.get("max_workers", 4), 

120 timeout=marimushka_config.get("timeout", 300), 

121 audit_log=security_config.get("audit_log"), 

122 audit_enabled=security_config.get("audit_enabled", True), 

123 max_file_size_mb=security_config.get("max_file_size_mb", 10), 

124 file_permissions=int(str(security_config.get("file_permissions", "0o644")), 8), 

125 ) 

126 

127 @classmethod 

128 def from_file_or_defaults(cls, config_path: Path | None = None) -> "MarimushkaConfig": 

129 """Load configuration from file if it exists, otherwise use defaults. 

130 

131 Args: 

132 config_path: Optional path to config file. If None, looks for 

133 .marimushka.toml in current directory. 

134 

135 Returns: 

136 A MarimushkaConfig instance. 

137 

138 """ 

139 if config_path is None: 

140 config_path = Path(".marimushka.toml") 

141 

142 if config_path.exists(): 

143 return cls.from_file(config_path) 

144 

145 return cls() 

146 

147 def to_dict(self) -> dict[str, Any]: 

148 """Convert configuration to dictionary. 

149 

150 Returns: 

151 Dictionary representation of configuration. 

152 

153 """ 

154 return { 

155 "output": self.output, 

156 "template": self.template, 

157 "notebooks": self.notebooks, 

158 "apps": self.apps, 

159 "notebooks_wasm": self.notebooks_wasm, 

160 "sandbox": self.sandbox, 

161 "parallel": self.parallel, 

162 "max_workers": self.max_workers, 

163 "timeout": self.timeout, 

164 "security": { 

165 "audit_log": self.audit_log, 

166 "audit_enabled": self.audit_enabled, 

167 "max_file_size_mb": self.max_file_size_mb, 

168 "file_permissions": oct(self.file_permissions), 

169 }, 

170 }