fast-clean-architecture 1.0.0__py3-none-any.whl → 1.1.0__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.
Files changed (27) hide show
  1. fast_clean_architecture/__init__.py +3 -4
  2. fast_clean_architecture/analytics.py +260 -0
  3. fast_clean_architecture/cli.py +555 -43
  4. fast_clean_architecture/config.py +47 -23
  5. fast_clean_architecture/error_tracking.py +201 -0
  6. fast_clean_architecture/exceptions.py +432 -12
  7. fast_clean_architecture/generators/__init__.py +11 -1
  8. fast_clean_architecture/generators/component_generator.py +407 -103
  9. fast_clean_architecture/generators/config_updater.py +186 -38
  10. fast_clean_architecture/generators/generator_factory.py +223 -0
  11. fast_clean_architecture/generators/package_generator.py +9 -7
  12. fast_clean_architecture/generators/template_validator.py +109 -9
  13. fast_clean_architecture/generators/validation_config.py +5 -3
  14. fast_clean_architecture/generators/validation_metrics.py +10 -6
  15. fast_clean_architecture/health.py +169 -0
  16. fast_clean_architecture/logging_config.py +52 -0
  17. fast_clean_architecture/metrics.py +108 -0
  18. fast_clean_architecture/protocols.py +406 -0
  19. fast_clean_architecture/templates/external.py.j2 +109 -32
  20. fast_clean_architecture/utils.py +50 -31
  21. fast_clean_architecture/validation.py +302 -0
  22. {fast_clean_architecture-1.0.0.dist-info → fast_clean_architecture-1.1.0.dist-info}/METADATA +31 -21
  23. fast_clean_architecture-1.1.0.dist-info/RECORD +38 -0
  24. fast_clean_architecture-1.0.0.dist-info/RECORD +0 -30
  25. {fast_clean_architecture-1.0.0.dist-info → fast_clean_architecture-1.1.0.dist-info}/WHEEL +0 -0
  26. {fast_clean_architecture-1.0.0.dist-info → fast_clean_architecture-1.1.0.dist-info}/entry_points.txt +0 -0
  27. {fast_clean_architecture-1.0.0.dist-info → fast_clean_architecture-1.1.0.dist-info}/licenses/LICENSE +0 -0
@@ -2,14 +2,19 @@
2
2
 
3
3
  import keyword
4
4
  import re
5
- import urllib.parse
6
- import unicodedata
7
5
  import threading
8
- import fcntl
9
- import os
6
+ import unicodedata
7
+ import urllib.parse
10
8
  from datetime import datetime, timezone
11
9
  from pathlib import Path
12
- from typing import Any, Dict, List, Optional, Union
10
+ from typing import Any, Callable, Dict, List, Optional, Union
11
+
12
+ from .exceptions import (
13
+ SecurityError,
14
+ ValidationError,
15
+ create_secure_error,
16
+ create_validation_error,
17
+ )
13
18
 
14
19
 
15
20
  def generate_timestamp() -> str:
@@ -42,7 +47,12 @@ def get_file_lock(file_path: Union[str, Path]) -> threading.Lock:
42
47
  return _file_locks[file_path_str]
43
48
 
44
49
 
45
- def secure_file_operation(file_path: Union[str, Path], operation_func, *args, **kwargs):
50
+ def secure_file_operation(
51
+ file_path: Union[str, Path],
52
+ operation_func: Callable[..., Any],
53
+ *args: Any,
54
+ **kwargs: Any,
55
+ ) -> Any:
46
56
  """Execute file operation with proper locking."""
47
57
  lock = get_file_lock(file_path)
48
58
  with lock:
@@ -61,7 +71,7 @@ def sanitize_error_message(
61
71
  r"/Users/[^/\s]+", # User home directories
62
72
  r"/home/[^/\s]+", # Linux home directories
63
73
  r"C:\\Users\\[^\\\s]+", # Windows user directories
64
- r"/tmp/[^/\s]+", # Temporary directories
74
+ r"/tmp/[^/\s]+", # Temporary directories # nosec B108
65
75
  r"/var/[^/\s]+", # System directories
66
76
  r"\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b", # IP addresses
67
77
  ]
@@ -76,18 +86,20 @@ def sanitize_error_message(
76
86
  return sanitized_msg
77
87
 
78
88
 
79
- def create_secure_error(error_type: str, operation: str, details: Optional[str] = None):
89
+ def create_secure_error_message(
90
+ error_type: str, operation: str, details: Optional[str] = None
91
+ ) -> str:
80
92
  """Create a secure error message without exposing sensitive information."""
81
- from fast_clean_architecture.exceptions import ValidationError
93
+ import os
82
94
 
83
95
  base_msg = f"Failed to {operation}"
84
-
85
96
  if details:
86
- # Sanitize details before including
87
- safe_details = sanitize_error_message(details)
88
- return ValidationError(f"{base_msg}: {safe_details}")
89
-
90
- return ValidationError(base_msg)
97
+ # Sanitize details to prevent information leakage
98
+ safe_details = details.replace(os.path.expanduser("~"), "<HOME>")
99
+ safe_details = re.sub(r"/Users/[^/]+", "/Users/<USER>", safe_details)
100
+ safe_details = re.sub(r"\\Users\\[^\\]+", "\\Users\\<USER>", safe_details)
101
+ return f"{base_msg}: {safe_details}"
102
+ return base_msg
91
103
 
92
104
 
93
105
  def to_snake_case(name: str) -> str:
@@ -220,18 +232,20 @@ def validate_name(name: str) -> None:
220
232
  decoded_name = urllib.parse.unquote(name)
221
233
  # Apply Unicode normalization to handle Unicode attacks
222
234
  normalized_name = unicodedata.normalize("NFKC", decoded_name)
223
- except Exception:
235
+ except (ValueError, UnicodeDecodeError, UnicodeError):
224
236
  # If decoding fails, treat as suspicious
225
- raise ValidationError(
226
- f"Invalid component name: suspicious encoding detected in '{name}'"
237
+ raise create_secure_error(
238
+ "encoding_attack",
239
+ "name validation",
240
+ f"Suspicious encoding detected in component name: {name[:50]}",
227
241
  )
228
242
 
229
243
  # Check for path traversal in original, decoded, and normalized forms
230
244
  names_to_check = [name, decoded_name, normalized_name]
231
245
  for check_name in names_to_check:
232
246
  if ".." in check_name or "/" in check_name or "\\" in check_name:
233
- raise ValidationError(
234
- f"Invalid component name: path traversal detected in '{name}'"
247
+ raise create_secure_error(
248
+ "path_traversal", "name validation", "Path traversal pattern detected"
235
249
  )
236
250
 
237
251
  # Check for encoded path traversal sequences
@@ -252,8 +266,10 @@ def validate_name(name: str) -> None:
252
266
  name_lower = name.lower()
253
267
  for pattern in encoded_patterns:
254
268
  if pattern in name_lower:
255
- raise ValidationError(
256
- f"Invalid component name: encoded path traversal detected in '{name}'"
269
+ raise create_secure_error(
270
+ "encoded_path_traversal",
271
+ "name validation",
272
+ "Encoded path traversal sequence detected",
257
273
  )
258
274
 
259
275
  # Check for Unicode path traversal variants
@@ -264,14 +280,18 @@ def validate_name(name: str) -> None:
264
280
  for dot in unicode_dots:
265
281
  for dot2 in unicode_dots:
266
282
  if dot + dot2 in name:
267
- raise ValidationError(
268
- f"Invalid component name: Unicode path traversal detected in '{name}'"
283
+ raise create_secure_error(
284
+ "unicode_path_traversal",
285
+ "name validation",
286
+ "Unicode path traversal pattern detected",
269
287
  )
270
288
 
271
289
  for slash in unicode_slashes + unicode_backslashes:
272
290
  if slash in name:
273
- raise ValidationError(
274
- f"Invalid component name: Unicode path separator detected in '{name}'"
291
+ raise create_secure_error(
292
+ "unicode_path_separator",
293
+ "name validation",
294
+ "Unicode path separator detected",
275
295
  )
276
296
 
277
297
  # Check for shell injection attempts
@@ -347,8 +367,8 @@ def get_template_variables(
347
367
  module_name: str,
348
368
  component_name: str,
349
369
  component_type: str,
350
- **kwargs,
351
- ) -> dict:
370
+ **kwargs: Any,
371
+ ) -> Dict[str, Any]:
352
372
  """Generate template variables for rendering."""
353
373
  snake_name = to_snake_case(component_name)
354
374
  pascal_name = to_pascal_case(component_name)
@@ -489,7 +509,6 @@ def get_component_type_from_path(path: str) -> Optional[str]:
489
509
  "queries", # application
490
510
  "models",
491
511
  "external",
492
- "internal", # infrastructure
493
512
  "api",
494
513
  "schemas", # presentation
495
514
  ]
@@ -503,7 +522,7 @@ def get_component_type_from_path(path: str) -> Optional[str]:
503
522
  return None
504
523
 
505
524
 
506
- def parse_location_path(location: str) -> dict[str, str]:
525
+ def parse_location_path(location: str) -> Dict[str, str]:
507
526
  """Parse location path to extract system, module, layer, and component type.
508
527
 
509
528
  Args:
@@ -535,7 +554,7 @@ def parse_location_path(location: str) -> dict[str, str]:
535
554
  layer_components = {
536
555
  "domain": ["entities", "repositories", "value_objects"],
537
556
  "application": ["services", "commands", "queries"],
538
- "infrastructure": ["models", "repositories", "external", "internal"],
557
+ "infrastructure": ["models", "repositories", "external"],
539
558
  "presentation": ["api", "schemas"],
540
559
  }
541
560
 
@@ -0,0 +1,302 @@
1
+ """Validation utilities for Fast Clean Architecture."""
2
+
3
+ import re
4
+ from pathlib import Path
5
+ from typing import Any, Dict, List, Optional, Pattern, Set, Union
6
+
7
+ from .exceptions import Result, SecurityError, ValidationError
8
+
9
+
10
+ class ValidationRules:
11
+ """Centralized validation rules and patterns."""
12
+
13
+ # Component naming patterns
14
+ COMPONENT_NAME_PATTERN: Pattern[str] = re.compile(r"^[a-zA-Z][a-zA-Z0-9_]*$")
15
+ SYSTEM_NAME_PATTERN: Pattern[str] = re.compile(r"^[a-zA-Z][a-zA-Z0-9_-]*$")
16
+ MODULE_NAME_PATTERN: Pattern[str] = re.compile(r"^[a-zA-Z][a-zA-Z0-9_]*$")
17
+
18
+ # Security patterns
19
+ DANGEROUS_PATH_PATTERNS: List[Pattern[str]] = [
20
+ re.compile(r"\.\."), # Path traversal
21
+ re.compile(r"^/etc/|^/root/|^/proc/|^/sys/"), # Dangerous system paths only
22
+ re.compile(r"^~"), # Home directory
23
+ re.compile(r"\$\{"), # Variable expansion
24
+ re.compile(r"\$\("), # Command substitution
25
+ ]
26
+
27
+ # Valid component types and layers
28
+ VALID_LAYERS: Set[str] = {"domain", "application", "infrastructure", "presentation"}
29
+
30
+ VALID_COMPONENT_TYPES: Set[str] = {
31
+ "entities",
32
+ "repositories",
33
+ "services",
34
+ "controllers",
35
+ "models",
36
+ "views",
37
+ "adapters",
38
+ "gateways",
39
+ "external",
40
+ }
41
+
42
+ # File extension validation
43
+ ALLOWED_EXTENSIONS: Set[str] = {
44
+ ".py",
45
+ ".yaml",
46
+ ".yml",
47
+ ".json",
48
+ ".md",
49
+ ".txt",
50
+ ".toml",
51
+ }
52
+
53
+
54
+ class Validator:
55
+ """Main validation class with comprehensive checks."""
56
+
57
+ @staticmethod
58
+ def validate_component_name(name: str) -> Result[str, ValidationError]:
59
+ """Validate component name format."""
60
+ if not name:
61
+ return Result.failure(
62
+ ValidationError(
63
+ "Component name cannot be empty", context={"name": name}
64
+ )
65
+ )
66
+
67
+ if not ValidationRules.COMPONENT_NAME_PATTERN.match(name):
68
+ return Result.failure(
69
+ ValidationError(
70
+ f"Invalid component name format: {name}. Must start with letter and contain only letters, numbers, and underscores.",
71
+ context={
72
+ "name": name,
73
+ "pattern": ValidationRules.COMPONENT_NAME_PATTERN.pattern,
74
+ },
75
+ )
76
+ )
77
+
78
+ if len(name) > 50:
79
+ return Result.failure(
80
+ ValidationError(
81
+ f"Component name too long: {len(name)} characters. Maximum 50 allowed.",
82
+ context={"name": name, "length": len(name)},
83
+ )
84
+ )
85
+
86
+ return Result.success(name)
87
+
88
+ @staticmethod
89
+ def validate_system_name(name: str) -> Result[str, ValidationError]:
90
+ """Validate system name format."""
91
+ if not name:
92
+ return Result.failure(
93
+ ValidationError("System name cannot be empty", context={"name": name})
94
+ )
95
+
96
+ if not ValidationRules.SYSTEM_NAME_PATTERN.match(name):
97
+ return Result.failure(
98
+ ValidationError(
99
+ f"Invalid system name format: {name}. Must start with letter and contain only letters, numbers, underscores, and hyphens.",
100
+ context={
101
+ "name": name,
102
+ "pattern": ValidationRules.SYSTEM_NAME_PATTERN.pattern,
103
+ },
104
+ )
105
+ )
106
+
107
+ return Result.success(name)
108
+
109
+ @staticmethod
110
+ def validate_module_name(name: str) -> Result[str, ValidationError]:
111
+ """Validate module name format."""
112
+ if not name:
113
+ return Result.failure(
114
+ ValidationError("Module name cannot be empty", context={"name": name})
115
+ )
116
+
117
+ if not ValidationRules.MODULE_NAME_PATTERN.match(name):
118
+ return Result.failure(
119
+ ValidationError(
120
+ f"Invalid module name format: {name}. Must start with letter and contain only letters, numbers, and underscores.",
121
+ context={
122
+ "name": name,
123
+ "pattern": ValidationRules.MODULE_NAME_PATTERN.pattern,
124
+ },
125
+ )
126
+ )
127
+
128
+ return Result.success(name)
129
+
130
+ @staticmethod
131
+ def validate_layer(layer: str) -> Result[str, ValidationError]:
132
+ """Validate layer name."""
133
+ if layer not in ValidationRules.VALID_LAYERS:
134
+ return Result.failure(
135
+ ValidationError(
136
+ f"Invalid layer: {layer}. Must be one of: {', '.join(sorted(ValidationRules.VALID_LAYERS))}",
137
+ context={
138
+ "layer": layer,
139
+ "valid_layers": list(ValidationRules.VALID_LAYERS),
140
+ },
141
+ )
142
+ )
143
+
144
+ return Result.success(layer)
145
+
146
+ @staticmethod
147
+ def validate_component_type(component_type: str) -> Result[str, ValidationError]:
148
+ """Validate component type."""
149
+ if component_type not in ValidationRules.VALID_COMPONENT_TYPES:
150
+ return Result.failure(
151
+ ValidationError(
152
+ f"Invalid component type: {component_type}. Must be one of: {', '.join(sorted(ValidationRules.VALID_COMPONENT_TYPES))}",
153
+ context={
154
+ "component_type": component_type,
155
+ "valid_types": list(ValidationRules.VALID_COMPONENT_TYPES),
156
+ },
157
+ )
158
+ )
159
+
160
+ return Result.success(component_type)
161
+
162
+ @staticmethod
163
+ def validate_file_path(file_path: Union[str, Path]) -> Result[Path, SecurityError]:
164
+ """Validate file path for security issues."""
165
+ path_str = str(file_path)
166
+
167
+ # Check for dangerous patterns
168
+ for pattern in ValidationRules.DANGEROUS_PATH_PATTERNS:
169
+ if pattern.search(path_str):
170
+ return Result.failure(
171
+ SecurityError(
172
+ f"Potentially dangerous path pattern detected: {path_str}",
173
+ context={"path": path_str, "pattern": pattern.pattern},
174
+ )
175
+ )
176
+
177
+ # Convert to Path object
178
+ try:
179
+ path_obj = Path(path_str)
180
+ except Exception as e:
181
+ return Result.failure(
182
+ SecurityError(
183
+ f"Invalid path format: {path_str}",
184
+ context={"path": path_str, "error": str(e)},
185
+ )
186
+ )
187
+
188
+ # Check file extension
189
+ if (
190
+ path_obj.suffix
191
+ and path_obj.suffix not in ValidationRules.ALLOWED_EXTENSIONS
192
+ ):
193
+ return Result.failure(
194
+ SecurityError(
195
+ f"File extension not allowed: {path_obj.suffix}",
196
+ context={
197
+ "path": path_str,
198
+ "extension": path_obj.suffix,
199
+ "allowed": list(ValidationRules.ALLOWED_EXTENSIONS),
200
+ },
201
+ )
202
+ )
203
+
204
+ return Result.success(path_obj)
205
+
206
+ @staticmethod
207
+ def validate_description(
208
+ description: str, max_length: int = 500
209
+ ) -> Result[str, Union[ValidationError, SecurityError]]:
210
+ """Validate description text."""
211
+ if len(description) > max_length:
212
+ return Result.failure(
213
+ ValidationError(
214
+ f"Description too long: {len(description)} characters. Maximum {max_length} allowed.",
215
+ context={
216
+ "description": description[:100] + "...",
217
+ "length": len(description),
218
+ "max_length": max_length,
219
+ },
220
+ )
221
+ )
222
+
223
+ # Check for potentially dangerous content
224
+ dangerous_patterns = ["<script", "${", "$(", "javascript:"]
225
+ for pattern in dangerous_patterns:
226
+ if pattern.lower() in description.lower():
227
+ return Result.failure(
228
+ SecurityError(
229
+ f"Potentially dangerous content in description: {pattern}",
230
+ context={
231
+ "description": description[:100] + "...",
232
+ "pattern": pattern,
233
+ },
234
+ )
235
+ )
236
+
237
+ return Result.success(description)
238
+
239
+ @classmethod
240
+ def validate_component_creation(
241
+ cls,
242
+ system_name: str,
243
+ module_name: str,
244
+ layer: str,
245
+ component_type: str,
246
+ component_name: str,
247
+ file_path: Optional[Union[str, Path]] = None,
248
+ description: Optional[str] = None,
249
+ ) -> Result[Dict[str, Any], Union[ValidationError, SecurityError]]:
250
+ """Comprehensive validation for component creation."""
251
+
252
+ # Validate all required fields
253
+ system_validation = cls.validate_system_name(system_name)
254
+ if system_validation.is_failure:
255
+ return system_validation # type: ignore
256
+
257
+ module_validation = cls.validate_module_name(module_name)
258
+ if module_validation.is_failure:
259
+ return module_validation # type: ignore
260
+
261
+ layer_validation = cls.validate_layer(layer)
262
+ if layer_validation.is_failure:
263
+ return layer_validation # type: ignore
264
+
265
+ component_type_validation = cls.validate_component_type(component_type)
266
+ if component_type_validation.is_failure:
267
+ return component_type_validation # type: ignore
268
+
269
+ component_name_validation = cls.validate_component_name(component_name)
270
+ if component_name_validation.is_failure:
271
+ return component_name_validation # type: ignore
272
+
273
+ # Validate optional fields
274
+ validated_file_path = None
275
+ if file_path is not None:
276
+ file_validation = cls.validate_file_path(file_path)
277
+ if file_validation.is_failure:
278
+ return file_validation # type: ignore
279
+ else:
280
+ validated_file_path = file_validation.value
281
+
282
+ if description is not None:
283
+ description_validation = cls.validate_description(description)
284
+ if description_validation.is_failure:
285
+ return description_validation # type: ignore
286
+
287
+ # Return validated data
288
+ validated_data = {
289
+ "system_name": system_name,
290
+ "module_name": module_name,
291
+ "layer": layer,
292
+ "component_type": component_type,
293
+ "component_name": component_name,
294
+ }
295
+
296
+ if validated_file_path is not None:
297
+ validated_data["file_path"] = str(validated_file_path)
298
+
299
+ if description is not None:
300
+ validated_data["description"] = description
301
+
302
+ return Result.success(validated_data)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fast-clean-architecture
3
- Version: 1.0.0
3
+ Version: 1.1.0
4
4
  Summary: CLI tool for scaffolding clean architecture in FastAPI projects
5
5
  Project-URL: Homepage, https://github.com/alden-technologies/fast-clean-architecture
6
6
  Project-URL: Repository, https://github.com/alden-technologies/fast-clean-architecture
@@ -17,28 +17,22 @@ Classifier: Intended Audience :: Developers
17
17
  Classifier: License :: OSI Approved :: MIT License
18
18
  Classifier: Operating System :: OS Independent
19
19
  Classifier: Programming Language :: Python :: 3
20
- Classifier: Programming Language :: Python :: 3.8
21
20
  Classifier: Programming Language :: Python :: 3.9
22
21
  Classifier: Programming Language :: Python :: 3.10
23
22
  Classifier: Programming Language :: Python :: 3.11
24
23
  Classifier: Programming Language :: Python :: 3.12
25
24
  Classifier: Topic :: Software Development :: Code Generators
26
25
  Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
27
- Requires-Python: >=3.8
26
+ Requires-Python: >=3.9
28
27
  Requires-Dist: jinja2<4.0.0,>=3.1.0
29
28
  Requires-Dist: pathlib-mate<2.0.0,>=1.0.0
29
+ Requires-Dist: portalocker<3.0.0,>=2.0.0
30
+ Requires-Dist: psutil>=5.9.0
30
31
  Requires-Dist: pydantic<3.0.0,>=2.0.0
31
32
  Requires-Dist: pyyaml<7.0.0,>=6.0
32
33
  Requires-Dist: rich<14.0.0,>=13.0.0
34
+ Requires-Dist: structlog<24.0.0,>=23.0.0
33
35
  Requires-Dist: typer[all]<1.0.0,>=0.9.0
34
- Provides-Extra: dev
35
- Requires-Dist: bandit<2.0.0,>=1.7.0; extra == 'dev'
36
- Requires-Dist: black<24.0.0,>=23.0.0; extra == 'dev'
37
- Requires-Dist: isort<6.0.0,>=5.0.0; extra == 'dev'
38
- Requires-Dist: mypy<2.0.0,>=1.0.0; extra == 'dev'
39
- Requires-Dist: pytest-cov<5.0.0,>=4.0.0; extra == 'dev'
40
- Requires-Dist: pytest<8.0.0,>=7.0.0; extra == 'dev'
41
- Requires-Dist: safety<3.0.0,>=2.0.0; extra == 'dev'
42
36
  Description-Content-Type: text/markdown
43
37
 
44
38
  # Fast Clean Architecture
@@ -76,36 +70,52 @@ poetry add fast-clean-architecture
76
70
 
77
71
  ### From Source
78
72
 
79
- #### Using pip
73
+ #### Using Poetry (Recommended)
80
74
  ```bash
81
75
  git clone https://github.com/alden-technologies/fast-clean-architecture.git
82
76
  cd fast-clean-architecture
83
- pip install -e .
77
+ poetry install
84
78
  ```
85
79
 
86
- #### Using Poetry
80
+ #### Using pip with requirements.txt
87
81
  ```bash
88
82
  git clone https://github.com/alden-technologies/fast-clean-architecture.git
89
83
  cd fast-clean-architecture
90
- poetry install
84
+ pip install -r requirements.txt
85
+ ```
86
+
87
+ #### Using pip (editable install)
88
+ ```bash
89
+ git clone https://github.com/alden-technologies/fast-clean-architecture.git
90
+ cd fast-clean-architecture
91
+ pip install -e .
91
92
  ```
92
93
 
93
94
  ### Development Installation
94
95
 
95
- #### Using pip
96
+ #### Using Poetry (Recommended)
96
97
  ```bash
97
98
  git clone https://github.com/alden-technologies/fast-clean-architecture.git
98
99
  cd fast-clean-architecture
99
- pip install -e ".[dev]"
100
+ poetry install --extras dev
100
101
  ```
101
102
 
102
- #### Using Poetry
103
+ #### Using pip with dev requirements
104
+ ```bash
105
+ git clone https://github.com/alden-technologies/fast-clean-architecture.git
106
+ cd fast-clean-architecture
107
+ pip install -r requirements-dev.txt
108
+ ```
109
+
110
+ #### Using pip (editable install with dev dependencies)
103
111
  ```bash
104
112
  git clone https://github.com/alden-technologies/fast-clean-architecture.git
105
113
  cd fast-clean-architecture
106
- poetry install --with dev
114
+ pip install -e ".[dev]"
107
115
  ```
108
116
 
117
+ **Note**: This project uses Poetry for dependency management. The `requirements.txt` and `requirements-dev.txt` files are provided for convenience and compatibility with pip-based workflows.
118
+
109
119
  ## 🏗️ Architecture Overview
110
120
 
111
121
  Fast Clean Architecture follows the clean architecture pattern with these layers:
@@ -130,7 +140,7 @@ Fast Clean Architecture follows the clean architecture pattern with these layers
130
140
  │ └── 📁 presentation/ # API layer
131
141
  │ ├── 📁 api/ # FastAPI routers
132
142
  │ └── 📁 schemas/ # Pydantic schemas
133
- └── 📄 fca-config.yaml # Project configuration
143
+ └── 📄 fca_config.yaml # Project configuration
134
144
  ```
135
145
 
136
146
  ## 📋 Prerequisites
@@ -323,7 +333,7 @@ fca-scaffold create-component --help
323
333
 
324
334
  ### Configuration File
325
335
 
326
- The `fca-config.yaml` file tracks your project structure:
336
+ The `fca_config.yaml` file tracks your project structure:
327
337
 
328
338
  ```yaml
329
339
  project:
@@ -0,0 +1,38 @@
1
+ fast_clean_architecture/__init__.py,sha256=VOCrdvUXRx1IOKghlxhkzuKY4KFcU7s_RlLtjL10rFE,533
2
+ fast_clean_architecture/analytics.py,sha256=2EC8iF1WERhoJm_XCATkUnkBvLIm8lxp9-Ukl4sf4dU,8733
3
+ fast_clean_architecture/cli.py,sha256=S_GViX0gYFQJmmorqSk2nPunHRGL-8VpAmannJMFS-c,37263
4
+ fast_clean_architecture/config.py,sha256=UAyzEifdqrbpZDDirVR_E2yq5rlTJyv19Iixd01ufiI,19969
5
+ fast_clean_architecture/error_tracking.py,sha256=uyOEg6IZLNd-1u57bCCLpTzeBwP02pjVxRivXy1GyVM,6034
6
+ fast_clean_architecture/exceptions.py,sha256=0CTkgDjG-Ilqc9Vex02ofgPCSBfmNNceK6ZGwCIF8n0,16578
7
+ fast_clean_architecture/health.py,sha256=yvvGAOW_VXiuX7J4Od-IN7yazSA9fw8ugvfyb-hKzeU,5294
8
+ fast_clean_architecture/logging_config.py,sha256=6I82AWCFGMdP5KFeDiL_wtXk2RLMcDL0_-u9Ka8TW4M,1580
9
+ fast_clean_architecture/metrics.py,sha256=-7ln9E8UTSTBXWr7QhPRwpkGl_H1gKjmOMq79m6ASa0,3219
10
+ fast_clean_architecture/protocols.py,sha256=45IQhACDJj8KvlHLAETlSpf_F5BTkivl52MgUJwizhs,12547
11
+ fast_clean_architecture/utils.py,sha256=TbD7evQsKf_2KAr4RJ8iVhnpgBLA1FY6VaRd0CJSwEU,18837
12
+ fast_clean_architecture/validation.py,sha256=Iu9KyaBZX1VznM_bMb_4Jvu8o4XtAQoUxaWcA1UhNkI,10873
13
+ fast_clean_architecture/generators/__init__.py,sha256=bpq_b3lMiOBcn8bJhC3UCVLsSxIj9bGaSteGgN-Lgkc,560
14
+ fast_clean_architecture/generators/component_generator.py,sha256=xlYnqeK55Y5pjYOuz4OaRenQkng0hYe6Kx1MICr9lSs,50128
15
+ fast_clean_architecture/generators/config_updater.py,sha256=faSjwPX_sFWl8-qq4rl0HfOMUoZNKA1HwX3uP0BVCT4,16711
16
+ fast_clean_architecture/generators/generator_factory.py,sha256=YB_qlGcdNWJsu1ZFP46ik3eK7fjZFCCFo4hfSkwQYMc,7746
17
+ fast_clean_architecture/generators/package_generator.py,sha256=S234nCTHezdGMor0IaxbyWUyO3T68kAx_pJuHVKJHog,5978
18
+ fast_clean_architecture/generators/template_validator.py,sha256=7N1_Sn3BqPIWnv0oKZvSvGgi3y64FKShSIvkFlrlbVA,23651
19
+ fast_clean_architecture/generators/validation_config.py,sha256=ryqlEnE0tGIG5Ste9QsQccr_5HCS1Bl-gn422faDXug,2392
20
+ fast_clean_architecture/generators/validation_metrics.py,sha256=mQ5pUVH2zg-gfaTHm0lf7-n6X7evTEGPeN__OPeaqS0,6644
21
+ fast_clean_architecture/templates/__init__.py,sha256=uweRUmrNZosbwDlnM10Nfu9rKzPcEqXEr6BchsSAr58,146
22
+ fast_clean_architecture/templates/__init__.py.j2,sha256=ogycgpBmYv-O1yoyQK9-Ki_x4FlYgb58mvhPrhiQNpA,618
23
+ fast_clean_architecture/templates/api.py.j2,sha256=jPt1i84GuGEyeftJ8IythTrcTzac1GGyd4lQedl7_FA,2362
24
+ fast_clean_architecture/templates/command.py.j2,sha256=jjmteEF-UqXdlhM487SSUHzeaz2l8vHq6BzOykw0gJM,612
25
+ fast_clean_architecture/templates/entity.py.j2,sha256=5AIP6LwuusVNHiiAhph0bpFfnVzPhfnBdDMwZi_gx_s,1458
26
+ fast_clean_architecture/templates/external.py.j2,sha256=dXqm9Y_EsoFXErF0ukdAzOwQbZy667gjQtU7ZD5iWnw,5418
27
+ fast_clean_architecture/templates/infrastructure_repository.py.j2,sha256=nxKe7zpSRnpZkqpBrlhOkKLu2vERAiFhrt9iWU3K6aU,2543
28
+ fast_clean_architecture/templates/model.py.j2,sha256=f3toWIDu5nX5wkHKPbk0-XXow3RLP7D5VknhLPNwSHo,1113
29
+ fast_clean_architecture/templates/query.py.j2,sha256=7AwqC8BpzGq6L4T-g3Wo8Xj_SPOK0L1ke1RkJ4DQsVU,594
30
+ fast_clean_architecture/templates/repository.py.j2,sha256=YsjPpVakZR2A-JmsK-VYE6bYDhmeRBfNtID9SdhsGj8,1694
31
+ fast_clean_architecture/templates/schemas.py.j2,sha256=iwZT42LxqgNL4ms_-uQkWKWQWaiMDflWzdlWMBke_3M,755
32
+ fast_clean_architecture/templates/service.py.j2,sha256=utNp_E7bywlMfjY9SHzzrH4isr-WeXpnRIb15XkZrj4,3926
33
+ fast_clean_architecture/templates/value_object.py.j2,sha256=IVI69cuYTa_7g1s9T5guigIqeSl1lXFapWFu-XKzYjE,919
34
+ fast_clean_architecture-1.1.0.dist-info/METADATA,sha256=mqX8xt3q2b-miLJHwU5S0el4gaiXfXhPQ38K4JrZwhQ,17096
35
+ fast_clean_architecture-1.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
36
+ fast_clean_architecture-1.1.0.dist-info/entry_points.txt,sha256=5R_7kBX0dYEq6n8lyTmv7Sn4Rs3pjpLqGX-mB_3FL1w,65
37
+ fast_clean_architecture-1.1.0.dist-info/licenses/LICENSE,sha256=wpM1B1Znw_YrC9L_R0jiXPHWz5iM_tNW4UOvBtl026s,1075
38
+ fast_clean_architecture-1.1.0.dist-info/RECORD,,
@@ -1,30 +0,0 @@
1
- fast_clean_architecture/__init__.py,sha256=1B-oli8nUGzjXVe62dN_uAV6fTQlt_wNZmlbzLZs1C8,524
2
- fast_clean_architecture/cli.py,sha256=bug84CZx87zlAw7eCRGMs6-wVtFDER9snAvWPv-GxEs,15447
3
- fast_clean_architecture/config.py,sha256=W-H4r3sPHbx4CSp8_wC3VylnRRYngzw2OosC2__V0RE,18742
4
- fast_clean_architecture/exceptions.py,sha256=cFgIcHGlY7EqIeQvt95_D57AWZxCJja9XVt_MQo4gFE,1667
5
- fast_clean_architecture/utils.py,sha256=rrdiYUyj-xQ24nCaK5fspFvHIsjpspJAhxc64jzWgrk,18295
6
- fast_clean_architecture/generators/__init__.py,sha256=xgd1_9vo1DZ5DgKZainvK-rqoRVKz8Nvo7X34uDOdIs,280
7
- fast_clean_architecture/generators/component_generator.py,sha256=zPBkkTq0gTRPQGeaatCmqTeJt5QLmgN2k3kNZwzXfao,39089
8
- fast_clean_architecture/generators/config_updater.py,sha256=5SNeOszx_9urd8_NBM3aDDqw_Pu4cyWW9gLE_IMwo3c,10518
9
- fast_clean_architecture/generators/package_generator.py,sha256=pMi-SNMe2mUuwmE7YrJnfJNJrYmMk6jgTp7hJV_bozE,5896
10
- fast_clean_architecture/generators/template_validator.py,sha256=xOBRxvCLaR6cE0q3CgVdMjN5pHud8tJEWIOEMh4kRBA,20399
11
- fast_clean_architecture/generators/validation_config.py,sha256=OU46Ye2ik9AH51009faWIoe-GPzg-b8A4D2JaoFZSDY,2313
12
- fast_clean_architecture/generators/validation_metrics.py,sha256=owUYjqbDIttg4ZLXKvUkjqRQqxsrYe2f009oTrzbXXU,6493
13
- fast_clean_architecture/templates/__init__.py,sha256=uweRUmrNZosbwDlnM10Nfu9rKzPcEqXEr6BchsSAr58,146
14
- fast_clean_architecture/templates/__init__.py.j2,sha256=ogycgpBmYv-O1yoyQK9-Ki_x4FlYgb58mvhPrhiQNpA,618
15
- fast_clean_architecture/templates/api.py.j2,sha256=jPt1i84GuGEyeftJ8IythTrcTzac1GGyd4lQedl7_FA,2362
16
- fast_clean_architecture/templates/command.py.j2,sha256=jjmteEF-UqXdlhM487SSUHzeaz2l8vHq6BzOykw0gJM,612
17
- fast_clean_architecture/templates/entity.py.j2,sha256=5AIP6LwuusVNHiiAhph0bpFfnVzPhfnBdDMwZi_gx_s,1458
18
- fast_clean_architecture/templates/external.py.j2,sha256=SbZ8GEp9Q02bQTI59k0wDE5lDWVJWa7wI6VOBpsoAd4,1844
19
- fast_clean_architecture/templates/infrastructure_repository.py.j2,sha256=nxKe7zpSRnpZkqpBrlhOkKLu2vERAiFhrt9iWU3K6aU,2543
20
- fast_clean_architecture/templates/model.py.j2,sha256=f3toWIDu5nX5wkHKPbk0-XXow3RLP7D5VknhLPNwSHo,1113
21
- fast_clean_architecture/templates/query.py.j2,sha256=7AwqC8BpzGq6L4T-g3Wo8Xj_SPOK0L1ke1RkJ4DQsVU,594
22
- fast_clean_architecture/templates/repository.py.j2,sha256=YsjPpVakZR2A-JmsK-VYE6bYDhmeRBfNtID9SdhsGj8,1694
23
- fast_clean_architecture/templates/schemas.py.j2,sha256=iwZT42LxqgNL4ms_-uQkWKWQWaiMDflWzdlWMBke_3M,755
24
- fast_clean_architecture/templates/service.py.j2,sha256=utNp_E7bywlMfjY9SHzzrH4isr-WeXpnRIb15XkZrj4,3926
25
- fast_clean_architecture/templates/value_object.py.j2,sha256=IVI69cuYTa_7g1s9T5guigIqeSl1lXFapWFu-XKzYjE,919
26
- fast_clean_architecture-1.0.0.dist-info/METADATA,sha256=UAswG12fAvPtXkdOGttTx-o3Dh10jIUAtdx9jYoHFeg,16765
27
- fast_clean_architecture-1.0.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
28
- fast_clean_architecture-1.0.0.dist-info/entry_points.txt,sha256=5R_7kBX0dYEq6n8lyTmv7Sn4Rs3pjpLqGX-mB_3FL1w,65
29
- fast_clean_architecture-1.0.0.dist-info/licenses/LICENSE,sha256=wpM1B1Znw_YrC9L_R0jiXPHWz5iM_tNW4UOvBtl026s,1075
30
- fast_clean_architecture-1.0.0.dist-info/RECORD,,