fast-clean-architecture 1.0.0__py3-none-any.whl → 1.1.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fast_clean_architecture/__init__.py +5 -6
- fast_clean_architecture/analytics.py +260 -0
- fast_clean_architecture/cli.py +563 -46
- fast_clean_architecture/config.py +47 -23
- fast_clean_architecture/error_tracking.py +201 -0
- fast_clean_architecture/exceptions.py +432 -12
- fast_clean_architecture/generators/__init__.py +11 -1
- fast_clean_architecture/generators/component_generator.py +407 -103
- fast_clean_architecture/generators/config_updater.py +186 -38
- fast_clean_architecture/generators/generator_factory.py +223 -0
- fast_clean_architecture/generators/package_generator.py +9 -7
- fast_clean_architecture/generators/template_validator.py +109 -9
- fast_clean_architecture/generators/validation_config.py +5 -3
- fast_clean_architecture/generators/validation_metrics.py +10 -6
- fast_clean_architecture/health.py +169 -0
- fast_clean_architecture/logging_config.py +52 -0
- fast_clean_architecture/metrics.py +108 -0
- fast_clean_architecture/protocols.py +406 -0
- fast_clean_architecture/templates/external.py.j2 +109 -32
- fast_clean_architecture/utils.py +50 -31
- fast_clean_architecture/validation.py +302 -0
- {fast_clean_architecture-1.0.0.dist-info → fast_clean_architecture-1.1.2.dist-info}/METADATA +131 -64
- fast_clean_architecture-1.1.2.dist-info/RECORD +38 -0
- fast_clean_architecture-1.0.0.dist-info/RECORD +0 -30
- {fast_clean_architecture-1.0.0.dist-info → fast_clean_architecture-1.1.2.dist-info}/WHEEL +0 -0
- {fast_clean_architecture-1.0.0.dist-info → fast_clean_architecture-1.1.2.dist-info}/entry_points.txt +0 -0
- {fast_clean_architecture-1.0.0.dist-info → fast_clean_architecture-1.1.2.dist-info}/licenses/LICENSE +0 -0
@@ -1,20 +1,21 @@
|
|
1
|
-
"""Configuration updater for managing
|
1
|
+
"""Configuration updater for managing fca_config.yaml updates."""
|
2
2
|
|
3
3
|
import shutil
|
4
4
|
from pathlib import Path
|
5
|
-
from typing import
|
5
|
+
from typing import Any, Dict, List, Optional, Union
|
6
6
|
|
7
7
|
from rich.console import Console
|
8
8
|
|
9
9
|
from ..config import Config
|
10
|
+
from ..exceptions import ConfigurationError, Result, SecurityError, ValidationError
|
10
11
|
from ..utils import generate_timestamp
|
11
|
-
from ..
|
12
|
+
from ..validation import Validator
|
12
13
|
|
13
14
|
|
14
15
|
class ConfigUpdater:
|
15
|
-
"""Handles updates to the
|
16
|
+
"""Handles updates to the fca_config.yaml file with proper timestamp management."""
|
16
17
|
|
17
|
-
def __init__(self, config_path: Path, console: Console = None):
|
18
|
+
def __init__(self, config_path: Path, console: Optional[Console] = None):
|
18
19
|
self.config_path = config_path
|
19
20
|
self.console = console or Console()
|
20
21
|
self._config: Optional[Config] = None
|
@@ -28,27 +29,95 @@ class ConfigUpdater:
|
|
28
29
|
|
29
30
|
def _load_config(self) -> Config:
|
30
31
|
"""Load configuration from file or create default."""
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
32
|
+
result = self._load_config_safe()
|
33
|
+
return result.unwrap()
|
34
|
+
|
35
|
+
def _load_config_safe(self) -> Result[Config, ConfigurationError]:
|
36
|
+
"""Safely load configuration with Result pattern."""
|
37
|
+
try:
|
38
|
+
if self.config_path.exists():
|
39
|
+
config = Config.load_from_file(self.config_path)
|
40
|
+
return Result.success(config)
|
41
|
+
else:
|
42
|
+
# Create default config if it doesn't exist
|
43
|
+
config = Config.create_default()
|
44
|
+
config.save_to_file(self.config_path)
|
45
|
+
return Result.success(config)
|
46
|
+
except Exception as e:
|
47
|
+
return Result.failure(
|
48
|
+
ConfigurationError(
|
49
|
+
f"Failed to load configuration from {self.config_path}",
|
50
|
+
context={"path": str(self.config_path), "error": str(e)},
|
51
|
+
)
|
52
|
+
)
|
38
53
|
|
39
54
|
def backup_config(self) -> Path:
|
40
55
|
"""Create a backup of the current configuration."""
|
41
|
-
|
42
|
-
|
43
|
-
|
56
|
+
result = self._backup_config_safe()
|
57
|
+
return result.unwrap()
|
58
|
+
|
59
|
+
def _backup_config_safe(self) -> Result[Path, ConfigurationError]:
|
60
|
+
"""Safely create a backup with Result pattern."""
|
61
|
+
try:
|
62
|
+
if not self.config_path.exists():
|
63
|
+
return Result.failure(
|
64
|
+
ConfigurationError(
|
65
|
+
f"Configuration file does not exist: {self.config_path}",
|
66
|
+
context={"path": str(self.config_path)},
|
67
|
+
)
|
68
|
+
)
|
69
|
+
|
70
|
+
timestamp = generate_timestamp().replace(":", "-").replace(".", "-")
|
71
|
+
# Create backup directory if it doesn't exist
|
72
|
+
backup_dir = self.config_path.parent / "fca_config_backups"
|
73
|
+
backup_dir.mkdir(exist_ok=True)
|
74
|
+
backup_path = (
|
75
|
+
backup_dir / f"{self.config_path.stem}.backup.{timestamp}.yaml"
|
44
76
|
)
|
45
77
|
|
46
|
-
|
47
|
-
|
78
|
+
shutil.copy2(self.config_path, backup_path)
|
79
|
+
self.console.print(f"📋 Config backed up to: {backup_path}")
|
80
|
+
|
81
|
+
# Clean up old backups (keep only last 5)
|
82
|
+
self._cleanup_old_backups()
|
48
83
|
|
49
|
-
|
50
|
-
|
51
|
-
|
84
|
+
return Result.success(backup_path)
|
85
|
+
except Exception as e:
|
86
|
+
return Result.failure(
|
87
|
+
ConfigurationError(
|
88
|
+
f"Failed to create backup: {e}",
|
89
|
+
context={"source": str(self.config_path), "error": str(e)},
|
90
|
+
)
|
91
|
+
)
|
92
|
+
|
93
|
+
def _cleanup_old_backups(self, keep_count: int = 5) -> None:
|
94
|
+
"""Clean up old backup files created by ConfigUpdater, keeping only the most recent ones."""
|
95
|
+
try:
|
96
|
+
backup_dir = self.config_path.parent / "fca_config_backups"
|
97
|
+
if not backup_dir.exists():
|
98
|
+
return
|
99
|
+
|
100
|
+
# Pattern for ConfigUpdater backups: filename.backup.timestamp.yaml
|
101
|
+
backup_pattern = f"{self.config_path.stem}.backup.*.yaml"
|
102
|
+
backup_files = list(backup_dir.glob(backup_pattern))
|
103
|
+
|
104
|
+
if len(backup_files) > keep_count:
|
105
|
+
# Sort by modification time (newest first)
|
106
|
+
backup_files.sort(key=lambda p: p.stat().st_mtime, reverse=True)
|
107
|
+
|
108
|
+
# Remove old backups
|
109
|
+
for old_backup in backup_files[keep_count:]:
|
110
|
+
try:
|
111
|
+
old_backup.unlink()
|
112
|
+
self.console.print(
|
113
|
+
f"🗑️ Removed old backup: {old_backup.name}", style="dim"
|
114
|
+
)
|
115
|
+
except OSError:
|
116
|
+
# Ignore errors when cleaning up old backups
|
117
|
+
pass
|
118
|
+
except OSError:
|
119
|
+
# Don't fail the main operation if backup cleanup fails
|
120
|
+
pass
|
52
121
|
|
53
122
|
def add_system(
|
54
123
|
self,
|
@@ -56,17 +125,33 @@ class ConfigUpdater:
|
|
56
125
|
description: Optional[str] = None,
|
57
126
|
backup: bool = True,
|
58
127
|
) -> None:
|
59
|
-
"""Add a new system to the configuration."""
|
128
|
+
"""Add a new system to the configuration with validation."""
|
129
|
+
# Validate system name
|
130
|
+
name_result = Validator.validate_system_name(system_name)
|
131
|
+
if name_result.is_failure:
|
132
|
+
name_result.unwrap() # This will raise the error
|
133
|
+
|
134
|
+
# Validate description if provided
|
135
|
+
if description:
|
136
|
+
desc_result = Validator.validate_description(description)
|
137
|
+
if desc_result.is_failure:
|
138
|
+
desc_result.unwrap() # This will raise the error
|
139
|
+
|
60
140
|
if backup and self.config_path.exists():
|
61
141
|
self.backup_config()
|
62
142
|
|
63
143
|
# Add system to config
|
64
|
-
|
144
|
+
validated_name = name_result.value
|
145
|
+
if validated_name is None:
|
146
|
+
raise ValidationError(
|
147
|
+
"Validated name should not be None after successful validation"
|
148
|
+
)
|
149
|
+
self.config.add_system(validated_name, description or "")
|
65
150
|
|
66
151
|
# Save updated config
|
67
152
|
self._save_config_atomically()
|
68
153
|
|
69
|
-
self.console.print(f"✅ Added system: {
|
154
|
+
self.console.print(f"✅ Added system: {name_result.value}")
|
70
155
|
|
71
156
|
def add_module(
|
72
157
|
self,
|
@@ -75,17 +160,45 @@ class ConfigUpdater:
|
|
75
160
|
description: Optional[str] = None,
|
76
161
|
backup: bool = True,
|
77
162
|
) -> None:
|
78
|
-
"""Add a new module to a system."""
|
163
|
+
"""Add a new module to a system with validation."""
|
164
|
+
# Validate system name
|
165
|
+
system_result = Validator.validate_system_name(system_name)
|
166
|
+
if system_result.is_failure:
|
167
|
+
system_result.unwrap() # This will raise the error
|
168
|
+
|
169
|
+
# Validate module name
|
170
|
+
module_result = Validator.validate_module_name(module_name)
|
171
|
+
if module_result.is_failure:
|
172
|
+
module_result.unwrap() # This will raise the error
|
173
|
+
|
174
|
+
# Validate description if provided
|
175
|
+
if description:
|
176
|
+
desc_result = Validator.validate_description(description)
|
177
|
+
if desc_result.is_failure:
|
178
|
+
desc_result.unwrap() # This will raise the error
|
179
|
+
|
79
180
|
if backup and self.config_path.exists():
|
80
181
|
self.backup_config()
|
81
182
|
|
82
183
|
# Add module to config
|
83
|
-
|
184
|
+
validated_system = system_result.value
|
185
|
+
validated_module = module_result.value
|
186
|
+
if validated_system is None:
|
187
|
+
raise ValidationError(
|
188
|
+
"Validated system should not be None after successful validation"
|
189
|
+
)
|
190
|
+
if validated_module is None:
|
191
|
+
raise ValidationError(
|
192
|
+
"Validated module should not be None after successful validation"
|
193
|
+
)
|
194
|
+
self.config.add_module(validated_system, validated_module, description or "")
|
84
195
|
|
85
196
|
# Save updated config
|
86
197
|
self._save_config_atomically()
|
87
198
|
|
88
|
-
self.console.print(
|
199
|
+
self.console.print(
|
200
|
+
f"✅ Added module: {module_result.value} to system: {system_result.value}"
|
201
|
+
)
|
89
202
|
|
90
203
|
def add_component(
|
91
204
|
self,
|
@@ -97,18 +210,39 @@ class ConfigUpdater:
|
|
97
210
|
file_path: Optional[Path] = None,
|
98
211
|
backup: bool = True,
|
99
212
|
) -> None:
|
100
|
-
"""Add a new component to a module."""
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
# Add component to config
|
105
|
-
self.config.add_component(
|
213
|
+
"""Add a new component to a module with comprehensive validation."""
|
214
|
+
# Validate all inputs
|
215
|
+
validation_result = Validator.validate_component_creation(
|
106
216
|
system_name=system_name,
|
107
217
|
module_name=module_name,
|
108
218
|
layer=layer,
|
109
219
|
component_type=component_type,
|
110
220
|
component_name=component_name,
|
111
|
-
file_path=
|
221
|
+
file_path=file_path,
|
222
|
+
)
|
223
|
+
|
224
|
+
if validation_result.is_failure:
|
225
|
+
validation_result.unwrap() # This will raise the error
|
226
|
+
|
227
|
+
if backup and self.config_path.exists():
|
228
|
+
self.backup_config()
|
229
|
+
|
230
|
+
# Add component to config using validated data
|
231
|
+
validated_data = validation_result.value
|
232
|
+
if validated_data is None:
|
233
|
+
raise ConfigurationError("Validation result is None")
|
234
|
+
|
235
|
+
self.config.add_component(
|
236
|
+
system_name=validated_data["system_name"],
|
237
|
+
module_name=validated_data["module_name"],
|
238
|
+
layer=validated_data["layer"],
|
239
|
+
component_type=validated_data["component_type"],
|
240
|
+
component_name=validated_data["component_name"],
|
241
|
+
file_path=(
|
242
|
+
str(validated_data["file_path"])
|
243
|
+
if "file_path" in validated_data and validated_data["file_path"]
|
244
|
+
else None
|
245
|
+
),
|
112
246
|
)
|
113
247
|
|
114
248
|
# Save updated config
|
@@ -266,7 +400,7 @@ class ConfigUpdater:
|
|
266
400
|
|
267
401
|
def get_config_summary(self) -> Dict[str, Any]:
|
268
402
|
"""Get a summary of the current configuration."""
|
269
|
-
summary = {
|
403
|
+
summary: Dict[str, Any] = {
|
270
404
|
"project": {
|
271
405
|
"name": self.config.project.name,
|
272
406
|
"version": self.config.project.version,
|
@@ -288,6 +422,12 @@ class ConfigUpdater:
|
|
288
422
|
|
289
423
|
def _save_config_atomically(self) -> None:
|
290
424
|
"""Save configuration atomically to prevent corruption."""
|
425
|
+
result = self._save_config_atomically_safe()
|
426
|
+
result.unwrap()
|
427
|
+
|
428
|
+
def _save_config_atomically_safe(self) -> Result[None, ConfigurationError]:
|
429
|
+
"""Safely save configuration with Result pattern."""
|
430
|
+
temp_path: Optional[Path] = None
|
291
431
|
try:
|
292
432
|
# Write to temporary file first
|
293
433
|
temp_path = self.config_path.with_suffix(".tmp")
|
@@ -295,13 +435,21 @@ class ConfigUpdater:
|
|
295
435
|
|
296
436
|
# Atomic move
|
297
437
|
temp_path.replace(self.config_path)
|
438
|
+
return Result.success(None)
|
298
439
|
|
299
440
|
except Exception as e:
|
300
441
|
# Clean up temp file if it exists
|
301
|
-
temp_path
|
302
|
-
|
303
|
-
|
304
|
-
|
442
|
+
if temp_path and temp_path.exists():
|
443
|
+
try:
|
444
|
+
temp_path.unlink()
|
445
|
+
except OSError:
|
446
|
+
pass # Ignore cleanup errors
|
447
|
+
return Result.failure(
|
448
|
+
ConfigurationError(
|
449
|
+
f"Failed to save configuration: {e}",
|
450
|
+
context={"config_path": str(self.config_path), "error": str(e)},
|
451
|
+
)
|
452
|
+
)
|
305
453
|
|
306
454
|
def reload_config(self) -> None:
|
307
455
|
"""Reload configuration from file."""
|
@@ -0,0 +1,223 @@
|
|
1
|
+
"""Factory pattern implementation for generators.
|
2
|
+
|
3
|
+
This module provides a factory for creating different types of generators
|
4
|
+
with proper dependency injection and type safety.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from abc import ABC, abstractmethod
|
8
|
+
from pathlib import Path
|
9
|
+
from typing import Any, Dict, Generic, Optional, Protocol, Type, TypeVar, Union
|
10
|
+
|
11
|
+
from rich.console import Console
|
12
|
+
|
13
|
+
from ..config import Config
|
14
|
+
from ..exceptions import ComponentError
|
15
|
+
from ..protocols import (
|
16
|
+
ComponentGeneratorProtocol,
|
17
|
+
SecurePathHandler,
|
18
|
+
TemplateValidatorProtocol,
|
19
|
+
)
|
20
|
+
from .component_generator import ComponentGenerator
|
21
|
+
from .config_updater import ConfigUpdater
|
22
|
+
from .package_generator import PackageGenerator
|
23
|
+
from .template_validator import SimpleTemplateValidator
|
24
|
+
from .validation_config import ValidationConfig
|
25
|
+
|
26
|
+
# Type variables for enhanced type safety
|
27
|
+
T = TypeVar("T")
|
28
|
+
GeneratorType = TypeVar("GeneratorType")
|
29
|
+
|
30
|
+
|
31
|
+
class GeneratorProtocol(Protocol):
|
32
|
+
"""Base protocol for all generators."""
|
33
|
+
|
34
|
+
pass
|
35
|
+
|
36
|
+
|
37
|
+
# ComponentGeneratorProtocol is imported from protocols module
|
38
|
+
|
39
|
+
|
40
|
+
class GeneratorFactoryProtocol(Protocol):
|
41
|
+
"""Protocol for generator factories."""
|
42
|
+
|
43
|
+
def create_generator(self, generator_type: str, **kwargs: Any) -> GeneratorProtocol:
|
44
|
+
"""Create a generator of the specified type.
|
45
|
+
|
46
|
+
Args:
|
47
|
+
generator_type: Type of generator to create
|
48
|
+
**kwargs: Additional arguments for generator creation
|
49
|
+
|
50
|
+
Returns:
|
51
|
+
Generator instance
|
52
|
+
|
53
|
+
Raises:
|
54
|
+
ValueError: If generator type is not supported
|
55
|
+
"""
|
56
|
+
...
|
57
|
+
|
58
|
+
|
59
|
+
class DependencyContainer:
|
60
|
+
"""Container for managing dependencies and their lifecycle."""
|
61
|
+
|
62
|
+
def __init__(self, config: Config, console: Optional[Console] = None):
|
63
|
+
self.config = config
|
64
|
+
self.console = console or Console()
|
65
|
+
self._template_validator: Optional[TemplateValidatorProtocol] = None
|
66
|
+
self._path_handler: Optional[SecurePathHandler[Union[str, Path]]] = None
|
67
|
+
|
68
|
+
@property
|
69
|
+
def template_validator(self) -> TemplateValidatorProtocol:
|
70
|
+
"""Get or create template validator instance."""
|
71
|
+
if self._template_validator is None:
|
72
|
+
# Create template environment for validator
|
73
|
+
import jinja2
|
74
|
+
from jinja2.sandbox import SandboxedEnvironment
|
75
|
+
|
76
|
+
from ..templates import TEMPLATES_DIR
|
77
|
+
|
78
|
+
template_env = SandboxedEnvironment(
|
79
|
+
loader=jinja2.FileSystemLoader(TEMPLATES_DIR),
|
80
|
+
trim_blocks=True,
|
81
|
+
lstrip_blocks=True,
|
82
|
+
)
|
83
|
+
|
84
|
+
validation_config = ValidationConfig(
|
85
|
+
sandbox_mode=True,
|
86
|
+
max_template_size_bytes=64 * 1024, # 64KB limit
|
87
|
+
render_timeout_seconds=10,
|
88
|
+
max_variable_nesting_depth=10,
|
89
|
+
)
|
90
|
+
|
91
|
+
self._template_validator = SimpleTemplateValidator(
|
92
|
+
template_env, validation_config
|
93
|
+
)
|
94
|
+
return self._template_validator
|
95
|
+
|
96
|
+
@property
|
97
|
+
def path_handler(self) -> SecurePathHandler[Union[str, Path]]:
|
98
|
+
"""Get or create path handler instance."""
|
99
|
+
if self._path_handler is None:
|
100
|
+
self._path_handler = SecurePathHandler[Union[str, Path]](
|
101
|
+
max_path_length=4096,
|
102
|
+
allowed_extensions=[".py", ".j2", ".yaml", ".yml", ".json"],
|
103
|
+
)
|
104
|
+
return self._path_handler
|
105
|
+
|
106
|
+
|
107
|
+
class GeneratorFactory(GeneratorFactoryProtocol):
|
108
|
+
"""Factory for creating generators with dependency injection.
|
109
|
+
|
110
|
+
This factory implements the factory pattern and provides dependency
|
111
|
+
injection for all generator types, ensuring loose coupling and
|
112
|
+
better testability.
|
113
|
+
"""
|
114
|
+
|
115
|
+
def __init__(self, dependency_container: DependencyContainer):
|
116
|
+
"""Initialize factory with dependency container.
|
117
|
+
|
118
|
+
Args:
|
119
|
+
dependency_container: Container with all required dependencies
|
120
|
+
"""
|
121
|
+
self.dependencies = dependency_container
|
122
|
+
|
123
|
+
# Registry of available generators
|
124
|
+
self._generators: Dict[str, Type[GeneratorProtocol]] = {
|
125
|
+
"component": ComponentGenerator,
|
126
|
+
"package": PackageGenerator,
|
127
|
+
"config": ConfigUpdater,
|
128
|
+
}
|
129
|
+
|
130
|
+
def create_generator(self, generator_type: str, **kwargs: Any) -> GeneratorProtocol:
|
131
|
+
"""Create a generator of the specified type with dependency injection.
|
132
|
+
|
133
|
+
Args:
|
134
|
+
generator_type: Type of generator ('component', 'package', 'config')
|
135
|
+
**kwargs: Additional arguments for specific generator types
|
136
|
+
|
137
|
+
Returns:
|
138
|
+
Generator instance with injected dependencies
|
139
|
+
|
140
|
+
Raises:
|
141
|
+
ValueError: If generator type is not supported
|
142
|
+
"""
|
143
|
+
if generator_type not in self._generators:
|
144
|
+
available_types = ", ".join(self._generators.keys())
|
145
|
+
raise ValueError(
|
146
|
+
f"Unsupported generator type: {generator_type}. "
|
147
|
+
f"Available types: {available_types}"
|
148
|
+
)
|
149
|
+
|
150
|
+
generator_class = self._generators[generator_type]
|
151
|
+
|
152
|
+
# Create generator with appropriate dependencies
|
153
|
+
if generator_type == "component":
|
154
|
+
return self._create_component_generator(**kwargs)
|
155
|
+
elif generator_type == "package":
|
156
|
+
return self._create_package_generator(**kwargs)
|
157
|
+
elif generator_type == "config":
|
158
|
+
return self._create_config_updater(**kwargs)
|
159
|
+
else:
|
160
|
+
# This should never happen due to the check above, but included for completeness
|
161
|
+
raise ValueError(f"Unknown generator type: {generator_type}")
|
162
|
+
|
163
|
+
def _create_component_generator(self, **kwargs: Any) -> ComponentGenerator:
|
164
|
+
"""Create ComponentGenerator with injected dependencies."""
|
165
|
+
return ComponentGenerator(
|
166
|
+
config=self.dependencies.config,
|
167
|
+
template_validator=self.dependencies.template_validator,
|
168
|
+
path_handler=self.dependencies.path_handler,
|
169
|
+
console=self.dependencies.console,
|
170
|
+
**kwargs,
|
171
|
+
)
|
172
|
+
|
173
|
+
def _create_package_generator(self, **kwargs: Any) -> PackageGenerator:
|
174
|
+
"""Create PackageGenerator with injected dependencies."""
|
175
|
+
return PackageGenerator(console=self.dependencies.console, **kwargs)
|
176
|
+
|
177
|
+
def _create_config_updater(
|
178
|
+
self, config_path: Optional[Path] = None, **kwargs: Any
|
179
|
+
) -> ConfigUpdater:
|
180
|
+
"""Create ConfigUpdater with injected dependencies."""
|
181
|
+
if config_path is None:
|
182
|
+
# Use default config path from dependencies
|
183
|
+
config_path = Path("fca_config.yaml")
|
184
|
+
|
185
|
+
return ConfigUpdater(
|
186
|
+
config_path=config_path, console=self.dependencies.console, **kwargs
|
187
|
+
)
|
188
|
+
|
189
|
+
def register_generator(
|
190
|
+
self, generator_type: str, generator_class: Type[GeneratorProtocol]
|
191
|
+
) -> None:
|
192
|
+
"""Register a new generator type.
|
193
|
+
|
194
|
+
Args:
|
195
|
+
generator_type: Name of the generator type
|
196
|
+
generator_class: Class implementing the generator
|
197
|
+
"""
|
198
|
+
self._generators[generator_type] = generator_class
|
199
|
+
|
200
|
+
def get_available_types(self) -> list[str]:
|
201
|
+
"""Get list of available generator types.
|
202
|
+
|
203
|
+
Returns:
|
204
|
+
List of available generator type names
|
205
|
+
"""
|
206
|
+
return list(self._generators.keys())
|
207
|
+
|
208
|
+
|
209
|
+
# Convenience function for creating a factory with default dependencies
|
210
|
+
def create_generator_factory(
|
211
|
+
config: Config, console: Optional[Console] = None
|
212
|
+
) -> GeneratorFactory:
|
213
|
+
"""Create a generator factory with default dependencies.
|
214
|
+
|
215
|
+
Args:
|
216
|
+
config: Configuration object
|
217
|
+
console: Optional console for output
|
218
|
+
|
219
|
+
Returns:
|
220
|
+
Configured generator factory
|
221
|
+
"""
|
222
|
+
dependency_container = DependencyContainer(config, console)
|
223
|
+
return GeneratorFactory(dependency_container)
|
@@ -1,14 +1,14 @@
|
|
1
1
|
"""Package generator for creating directory structures and __init__.py files."""
|
2
2
|
|
3
3
|
from pathlib import Path
|
4
|
-
from typing import
|
4
|
+
from typing import Any, Dict, List, Optional
|
5
5
|
|
6
6
|
import jinja2
|
7
7
|
from rich.console import Console
|
8
8
|
|
9
|
+
from ..exceptions import TemplateError
|
9
10
|
from ..templates import TEMPLATES_DIR
|
10
11
|
from ..utils import ensure_directory
|
11
|
-
from ..exceptions import TemplateError
|
12
12
|
|
13
13
|
|
14
14
|
class PackageGenerator:
|
@@ -20,6 +20,7 @@ class PackageGenerator:
|
|
20
20
|
loader=jinja2.FileSystemLoader(TEMPLATES_DIR),
|
21
21
|
trim_blocks=True,
|
22
22
|
lstrip_blocks=True,
|
23
|
+
autoescape=True,
|
23
24
|
)
|
24
25
|
|
25
26
|
def create_system_structure(
|
@@ -85,7 +86,7 @@ class PackageGenerator:
|
|
85
86
|
)
|
86
87
|
|
87
88
|
# Create layer directories
|
88
|
-
layers = {
|
89
|
+
layers: Dict[str, Dict[str, List[Any]]] = {
|
89
90
|
"domain": {
|
90
91
|
"entities": [],
|
91
92
|
"repositories": [],
|
@@ -100,7 +101,6 @@ class PackageGenerator:
|
|
100
101
|
"models": [],
|
101
102
|
"repositories": [],
|
102
103
|
"external": [],
|
103
|
-
"internal": [],
|
104
104
|
},
|
105
105
|
"presentation": {
|
106
106
|
"api": [],
|
@@ -134,9 +134,11 @@ class PackageGenerator:
|
|
134
134
|
components=[],
|
135
135
|
)
|
136
136
|
|
137
|
-
# Create module
|
137
|
+
# Create module API file
|
138
138
|
module_content = f'"""\nModule registration for {module_name}.\n"""\n\n# Module configuration and dependencies\n'
|
139
|
-
(module_path / "
|
139
|
+
(module_path / f"{module_name}_module_api.py").write_text(
|
140
|
+
module_content, encoding="utf-8"
|
141
|
+
)
|
140
142
|
|
141
143
|
self.console.print(f"✅ Created module structure: {module_path}")
|
142
144
|
|
@@ -160,7 +162,7 @@ class PackageGenerator:
|
|
160
162
|
|
161
163
|
self.console.print(f"📝 Updated {init_path}")
|
162
164
|
|
163
|
-
def _create_init_file(self, file_path: Path, **template_vars) -> None:
|
165
|
+
def _create_init_file(self, file_path: Path, **template_vars: Any) -> None:
|
164
166
|
"""Create __init__.py file from template."""
|
165
167
|
try:
|
166
168
|
template = self.template_env.get_template("__init__.py.j2")
|