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.
- fast_clean_architecture/__init__.py +3 -4
- fast_clean_architecture/analytics.py +260 -0
- fast_clean_architecture/cli.py +555 -43
- 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.0.dist-info}/METADATA +31 -21
- fast_clean_architecture-1.1.0.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.0.dist-info}/WHEEL +0 -0
- {fast_clean_architecture-1.0.0.dist-info → fast_clean_architecture-1.1.0.dist-info}/entry_points.txt +0 -0
- {fast_clean_architecture-1.0.0.dist-info → fast_clean_architecture-1.1.0.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
3
|
__version__ = "1.0.0"
|
4
|
-
__author__ = "Adegbenga
|
4
|
+
__author__ = "Agoro, Adegbenga. B (IAM)"
|
5
5
|
__email__ = "adegbenga@alden-technologies.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
|
-
|
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()
|