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.
Files changed (27) hide show
  1. fast_clean_architecture/__init__.py +5 -6
  2. fast_clean_architecture/analytics.py +260 -0
  3. fast_clean_architecture/cli.py +563 -46
  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.2.dist-info}/METADATA +131 -64
  23. fast_clean_architecture-1.1.2.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.2.dist-info}/WHEEL +0 -0
  26. {fast_clean_architecture-1.0.0.dist-info → fast_clean_architecture-1.1.2.dist-info}/entry_points.txt +0 -0
  27. {fast_clean_architecture-1.0.0.dist-info → fast_clean_architecture-1.1.2.dist-info}/licenses/LICENSE +0 -0
@@ -1,17 +1,16 @@
1
1
  """Fast Clean Architecture - CLI tool for scaffolding clean architecture in FastAPI projects."""
2
2
 
3
- __version__ = "1.0.0"
4
- __author__ = "Adegbenga Agoro"
5
- __email__ = "adegbenga@alden-technologies.com"
3
+ __version__ = "1.1.2"
4
+ __author__ = "Agoro, Adegbenga. B (IAM)"
5
+ __email__ = "opensource@aldentechnologies.com"
6
6
 
7
7
  from .cli import app
8
8
  from .config import Config
9
-
10
9
  from .exceptions import (
11
- FastCleanArchitectureError,
12
10
  ConfigurationError,
13
- ValidationError,
11
+ FastCleanArchitectureError,
14
12
  FileConflictError,
13
+ ValidationError,
15
14
  )
16
15
 
17
16
  __all__ = [
@@ -0,0 +1,260 @@
1
+ """Usage analytics for fast-clean-architecture."""
2
+
3
+ import time
4
+ from collections import Counter, defaultdict
5
+ from datetime import datetime, timedelta
6
+ from pathlib import Path
7
+ from typing import Any, Dict, List, Optional
8
+
9
+ from .logging_config import get_logger
10
+
11
+ # Set up logger
12
+ logger = get_logger(__name__)
13
+
14
+
15
+ class UsageAnalytics:
16
+ """Track and analyze usage patterns of the tool."""
17
+
18
+ def __init__(self) -> None:
19
+ """Initialize usage analytics."""
20
+ self.session_start = time.time()
21
+ self.command_counts: Counter[str] = Counter()
22
+ self.component_types: Counter[str] = Counter()
23
+ self.layer_usage: Counter[str] = Counter()
24
+ self.system_usage: Counter[str] = Counter()
25
+ self.execution_times: Dict[str, List[float]] = defaultdict(list)
26
+ self.daily_usage: Dict[str, int] = defaultdict(int)
27
+
28
+ def track_command(
29
+ self, command: str, execution_time: Optional[float] = None, **context: Any
30
+ ) -> None:
31
+ """Track command usage.
32
+
33
+ Args:
34
+ command: The command that was executed
35
+ execution_time: Time taken to execute the command
36
+ context: Additional context about the command
37
+ """
38
+ # Update counters
39
+ self.command_counts[command] += 1
40
+
41
+ # Track execution time if provided
42
+ if execution_time is not None:
43
+ self.execution_times[command].append(execution_time)
44
+
45
+ # Track daily usage
46
+ today = datetime.now().strftime("%Y-%m-%d")
47
+ self.daily_usage[today] += 1
48
+
49
+ # Extract specific analytics from context
50
+ if "component_type" in context:
51
+ self.component_types[context["component_type"]] += 1
52
+
53
+ if "layer" in context:
54
+ self.layer_usage[context["layer"]] += 1
55
+
56
+ if "system_name" in context:
57
+ self.system_usage[context["system_name"]] += 1
58
+
59
+ # Log the usage
60
+ logger.info(
61
+ "Command usage tracked",
62
+ operation="usage_tracking",
63
+ command=command,
64
+ execution_time=execution_time,
65
+ session_duration=time.time() - self.session_start,
66
+ **context,
67
+ )
68
+
69
+ def track_component_creation(
70
+ self,
71
+ system_name: str,
72
+ module_name: str,
73
+ layer: str,
74
+ component_type: str,
75
+ component_name: str,
76
+ execution_time: Optional[float] = None,
77
+ ) -> None:
78
+ """Track component creation specifically.
79
+
80
+ Args:
81
+ system_name: Name of the system
82
+ module_name: Name of the module
83
+ layer: Layer where component was created
84
+ component_type: Type of component
85
+ component_name: Name of the component
86
+ execution_time: Time taken to create the component
87
+ """
88
+ self.track_command(
89
+ command="create_component",
90
+ execution_time=execution_time,
91
+ system_name=system_name,
92
+ module_name=module_name,
93
+ layer=layer,
94
+ component_type=component_type,
95
+ component_name=component_name,
96
+ )
97
+
98
+ def get_usage_summary(self) -> Dict[str, Any]:
99
+ """Get comprehensive usage summary.
100
+
101
+ Returns:
102
+ Dictionary containing usage statistics
103
+ """
104
+ session_duration = time.time() - self.session_start
105
+ total_commands = sum(self.command_counts.values())
106
+
107
+ # Calculate average execution times
108
+ avg_execution_times = {}
109
+ for command, times in self.execution_times.items():
110
+ if times:
111
+ avg_execution_times[command] = {
112
+ "average_ms": round(sum(times) / len(times) * 1000, 2),
113
+ "min_ms": round(min(times) * 1000, 2),
114
+ "max_ms": round(max(times) * 1000, 2),
115
+ "count": len(times),
116
+ }
117
+
118
+ summary = {
119
+ "session": {
120
+ "duration_seconds": round(session_duration, 2),
121
+ "total_commands": total_commands,
122
+ "commands_per_minute": (
123
+ round(total_commands / (session_duration / 60), 2)
124
+ if session_duration > 0
125
+ else 0
126
+ ),
127
+ },
128
+ "commands": dict(self.command_counts.most_common()),
129
+ "component_types": dict(self.component_types.most_common()),
130
+ "layers": dict(self.layer_usage.most_common()),
131
+ "systems": dict(self.system_usage.most_common()),
132
+ "performance": avg_execution_times,
133
+ "daily_usage": dict(self.daily_usage),
134
+ "timestamp": datetime.utcnow().isoformat(),
135
+ }
136
+
137
+ return summary
138
+
139
+ def get_productivity_metrics(self) -> Dict[str, Any]:
140
+ """Get productivity-focused metrics.
141
+
142
+ Returns:
143
+ Dictionary containing productivity metrics
144
+ """
145
+ session_duration = time.time() - self.session_start
146
+ total_components = self.command_counts.get("create_component", 0)
147
+
148
+ # Calculate components per hour
149
+ components_per_hour = (
150
+ (total_components / (session_duration / 3600))
151
+ if session_duration > 0
152
+ else 0
153
+ )
154
+
155
+ # Most productive layer/type combinations
156
+ layer_type_combinations: Counter[str] = Counter()
157
+ for layer in self.layer_usage:
158
+ for comp_type in self.component_types:
159
+ # This is a simplified combination - in practice you'd track actual pairs
160
+ layer_type_combinations[f"{layer}/{comp_type}"] = min(
161
+ self.layer_usage[layer], self.component_types[comp_type]
162
+ )
163
+
164
+ metrics = {
165
+ "components_created": total_components,
166
+ "components_per_hour": round(components_per_hour, 2),
167
+ "average_time_per_component": (
168
+ round(session_duration / total_components, 2)
169
+ if total_components > 0
170
+ else 0
171
+ ),
172
+ "most_used_layer": (
173
+ self.layer_usage.most_common(1)[0] if self.layer_usage else None
174
+ ),
175
+ "most_used_component_type": (
176
+ self.component_types.most_common(1)[0] if self.component_types else None
177
+ ),
178
+ "layer_type_combinations": dict(layer_type_combinations.most_common(5)),
179
+ "session_duration_minutes": round(session_duration / 60, 2),
180
+ }
181
+
182
+ return metrics
183
+
184
+ def log_usage_summary(self) -> None:
185
+ """Log current usage summary."""
186
+ summary = self.get_usage_summary()
187
+
188
+ logger.info("Usage analytics summary", operation="usage_summary", **summary)
189
+
190
+ def log_productivity_metrics(self) -> None:
191
+ """Log productivity metrics."""
192
+ metrics = self.get_productivity_metrics()
193
+
194
+ logger.info("Productivity metrics", operation="productivity_metrics", **metrics)
195
+
196
+
197
+ # Global analytics instance
198
+ _analytics: Optional[UsageAnalytics] = None
199
+
200
+
201
+ def get_analytics() -> UsageAnalytics:
202
+ """Get the global analytics instance.
203
+
204
+ Returns:
205
+ Global UsageAnalytics instance
206
+ """
207
+ global _analytics
208
+ if _analytics is None:
209
+ _analytics = UsageAnalytics()
210
+ return _analytics
211
+
212
+
213
+ def track_command_usage(
214
+ command: str, execution_time: Optional[float] = None, **context: Any
215
+ ) -> None:
216
+ """Track command usage using the global analytics instance.
217
+
218
+ Args:
219
+ command: The command that was executed
220
+ execution_time: Time taken to execute the command
221
+ context: Additional context about the command
222
+ """
223
+ analytics = get_analytics()
224
+ analytics.track_command(command, execution_time, **context)
225
+
226
+
227
+ def track_component_creation(
228
+ system_name: str,
229
+ module_name: str,
230
+ layer: str,
231
+ component_type: str,
232
+ component_name: str,
233
+ execution_time: Optional[float] = None,
234
+ ) -> None:
235
+ """Track component creation using the global analytics instance.
236
+
237
+ Args:
238
+ system_name: Name of the system
239
+ module_name: Name of the module
240
+ layer: Layer where component was created
241
+ component_type: Type of component
242
+ component_name: Name of the component
243
+ execution_time: Time taken to create the component
244
+ """
245
+ analytics = get_analytics()
246
+ analytics.track_component_creation(
247
+ system_name, module_name, layer, component_type, component_name, execution_time
248
+ )
249
+
250
+
251
+ def log_usage_summary() -> None:
252
+ """Log usage summary using the global analytics instance."""
253
+ analytics = get_analytics()
254
+ analytics.log_usage_summary()
255
+
256
+
257
+ def log_productivity_metrics() -> None:
258
+ """Log productivity metrics using the global analytics instance."""
259
+ analytics = get_analytics()
260
+ analytics.log_productivity_metrics()