mdb-engine 0.1.6__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 (75) hide show
  1. mdb_engine/README.md +144 -0
  2. mdb_engine/__init__.py +37 -0
  3. mdb_engine/auth/README.md +631 -0
  4. mdb_engine/auth/__init__.py +128 -0
  5. mdb_engine/auth/casbin_factory.py +199 -0
  6. mdb_engine/auth/casbin_models.py +46 -0
  7. mdb_engine/auth/config_defaults.py +71 -0
  8. mdb_engine/auth/config_helpers.py +213 -0
  9. mdb_engine/auth/cookie_utils.py +158 -0
  10. mdb_engine/auth/decorators.py +350 -0
  11. mdb_engine/auth/dependencies.py +747 -0
  12. mdb_engine/auth/helpers.py +64 -0
  13. mdb_engine/auth/integration.py +578 -0
  14. mdb_engine/auth/jwt.py +225 -0
  15. mdb_engine/auth/middleware.py +241 -0
  16. mdb_engine/auth/oso_factory.py +323 -0
  17. mdb_engine/auth/provider.py +570 -0
  18. mdb_engine/auth/restrictions.py +271 -0
  19. mdb_engine/auth/session_manager.py +477 -0
  20. mdb_engine/auth/token_lifecycle.py +213 -0
  21. mdb_engine/auth/token_store.py +289 -0
  22. mdb_engine/auth/users.py +1516 -0
  23. mdb_engine/auth/utils.py +614 -0
  24. mdb_engine/cli/__init__.py +13 -0
  25. mdb_engine/cli/commands/__init__.py +7 -0
  26. mdb_engine/cli/commands/generate.py +105 -0
  27. mdb_engine/cli/commands/migrate.py +83 -0
  28. mdb_engine/cli/commands/show.py +70 -0
  29. mdb_engine/cli/commands/validate.py +63 -0
  30. mdb_engine/cli/main.py +41 -0
  31. mdb_engine/cli/utils.py +92 -0
  32. mdb_engine/config.py +217 -0
  33. mdb_engine/constants.py +160 -0
  34. mdb_engine/core/README.md +542 -0
  35. mdb_engine/core/__init__.py +42 -0
  36. mdb_engine/core/app_registration.py +392 -0
  37. mdb_engine/core/connection.py +243 -0
  38. mdb_engine/core/engine.py +749 -0
  39. mdb_engine/core/index_management.py +162 -0
  40. mdb_engine/core/manifest.py +2793 -0
  41. mdb_engine/core/seeding.py +179 -0
  42. mdb_engine/core/service_initialization.py +355 -0
  43. mdb_engine/core/types.py +413 -0
  44. mdb_engine/database/README.md +522 -0
  45. mdb_engine/database/__init__.py +31 -0
  46. mdb_engine/database/abstraction.py +635 -0
  47. mdb_engine/database/connection.py +387 -0
  48. mdb_engine/database/scoped_wrapper.py +1721 -0
  49. mdb_engine/embeddings/README.md +184 -0
  50. mdb_engine/embeddings/__init__.py +62 -0
  51. mdb_engine/embeddings/dependencies.py +193 -0
  52. mdb_engine/embeddings/service.py +759 -0
  53. mdb_engine/exceptions.py +167 -0
  54. mdb_engine/indexes/README.md +651 -0
  55. mdb_engine/indexes/__init__.py +21 -0
  56. mdb_engine/indexes/helpers.py +145 -0
  57. mdb_engine/indexes/manager.py +895 -0
  58. mdb_engine/memory/README.md +451 -0
  59. mdb_engine/memory/__init__.py +30 -0
  60. mdb_engine/memory/service.py +1285 -0
  61. mdb_engine/observability/README.md +515 -0
  62. mdb_engine/observability/__init__.py +42 -0
  63. mdb_engine/observability/health.py +296 -0
  64. mdb_engine/observability/logging.py +161 -0
  65. mdb_engine/observability/metrics.py +297 -0
  66. mdb_engine/routing/README.md +462 -0
  67. mdb_engine/routing/__init__.py +73 -0
  68. mdb_engine/routing/websockets.py +813 -0
  69. mdb_engine/utils/__init__.py +7 -0
  70. mdb_engine-0.1.6.dist-info/METADATA +213 -0
  71. mdb_engine-0.1.6.dist-info/RECORD +75 -0
  72. mdb_engine-0.1.6.dist-info/WHEEL +5 -0
  73. mdb_engine-0.1.6.dist-info/entry_points.txt +2 -0
  74. mdb_engine-0.1.6.dist-info/licenses/LICENSE +661 -0
  75. mdb_engine-0.1.6.dist-info/top_level.txt +1 -0
@@ -0,0 +1,297 @@
1
+ """
2
+ Metrics collection for MDB_ENGINE.
3
+
4
+ This module provides metrics collection for monitoring engine performance,
5
+ operations, and health.
6
+ """
7
+
8
+ import logging
9
+ import threading
10
+ import time
11
+ from collections import OrderedDict
12
+ from dataclasses import dataclass
13
+ from datetime import datetime
14
+ from typing import Any, Callable, Dict, Optional
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ @dataclass
20
+ class OperationMetrics:
21
+ """Metrics for a single operation."""
22
+
23
+ operation_name: str
24
+ count: int = 0
25
+ total_duration_ms: float = 0.0
26
+ min_duration_ms: float = float("inf")
27
+ max_duration_ms: float = 0.0
28
+ error_count: int = 0
29
+ last_execution: Optional[datetime] = None
30
+
31
+ @property
32
+ def avg_duration_ms(self) -> float:
33
+ """Calculate average duration in milliseconds."""
34
+ return self.total_duration_ms / self.count if self.count > 0 else 0.0
35
+
36
+ @property
37
+ def error_rate(self) -> float:
38
+ """Calculate error rate as percentage."""
39
+ return (self.error_count / self.count * 100) if self.count > 0 else 0.0
40
+
41
+ def record(self, duration_ms: float, success: bool = True) -> None:
42
+ """Record a single operation execution."""
43
+ self.count += 1
44
+ self.total_duration_ms += duration_ms
45
+ self.min_duration_ms = min(self.min_duration_ms, duration_ms)
46
+ self.max_duration_ms = max(self.max_duration_ms, duration_ms)
47
+ if not success:
48
+ self.error_count += 1
49
+ self.last_execution = datetime.now()
50
+
51
+ def to_dict(self) -> Dict[str, Any]:
52
+ """Convert metrics to dictionary."""
53
+ return {
54
+ "operation": self.operation_name,
55
+ "count": self.count,
56
+ "avg_duration_ms": round(self.avg_duration_ms, 2),
57
+ "min_duration_ms": (
58
+ round(self.min_duration_ms, 2)
59
+ if self.min_duration_ms != float("inf")
60
+ else 0.0
61
+ ),
62
+ "max_duration_ms": round(self.max_duration_ms, 2),
63
+ "error_count": self.error_count,
64
+ "error_rate_percent": round(self.error_rate, 2),
65
+ "last_execution": (
66
+ self.last_execution.isoformat() if self.last_execution else None
67
+ ),
68
+ }
69
+
70
+
71
+ class MetricsCollector:
72
+ """
73
+ Centralized metrics collector for MDB_ENGINE.
74
+
75
+ Collects and aggregates metrics for:
76
+ - Engine operations (initialization, app registration)
77
+ - Database operations (queries, writes)
78
+ - Index operations (creation, updates)
79
+ - Performance metrics (latency, throughput)
80
+ """
81
+
82
+ def __init__(self, max_metrics: int = 10000):
83
+ """
84
+ Initialize the metrics collector.
85
+
86
+ Args:
87
+ max_metrics: Maximum number of metrics to store before evicting oldest (LRU).
88
+ Defaults to 10000.
89
+ """
90
+ self._metrics: OrderedDict[str, OperationMetrics] = OrderedDict()
91
+ self._lock = threading.Lock()
92
+ self._max_metrics = max_metrics
93
+
94
+ def record_operation(
95
+ self, operation_name: str, duration_ms: float, success: bool = True, **tags: Any
96
+ ) -> None:
97
+ """
98
+ Record an operation execution.
99
+
100
+ Args:
101
+ operation_name: Name of the operation (e.g., "engine.initialize")
102
+ duration_ms: Duration in milliseconds
103
+ success: Whether the operation succeeded
104
+ **tags: Additional tags for filtering (app_slug, collection_name, etc.)
105
+ """
106
+ # Create a key that includes tags for more granular metrics
107
+ key = operation_name
108
+ if tags:
109
+ tag_str = "_".join(f"{k}={v}" for k, v in sorted(tags.items()))
110
+ key = f"{operation_name}[{tag_str}]"
111
+
112
+ with self._lock:
113
+ # Check if this is a new metric
114
+ is_new = key not in self._metrics
115
+
116
+ # Evict oldest if at capacity and adding new metric
117
+ if is_new and len(self._metrics) >= self._max_metrics:
118
+ self._metrics.popitem(last=False) # Remove oldest (LRU)
119
+
120
+ if is_new:
121
+ self._metrics[key] = OperationMetrics(operation_name=operation_name)
122
+ else:
123
+ # Move to end (most recently used)
124
+ self._metrics.move_to_end(key)
125
+
126
+ self._metrics[key].record(duration_ms, success)
127
+
128
+ def get_metrics(self, operation_name: Optional[str] = None) -> Dict[str, Any]:
129
+ """
130
+ Get metrics for operations.
131
+
132
+ Args:
133
+ operation_name: Optional operation name to filter by
134
+
135
+ Returns:
136
+ Dictionary of metrics
137
+ """
138
+ with self._lock:
139
+ if operation_name:
140
+ metrics = {
141
+ k: v.to_dict()
142
+ for k, v in self._metrics.items()
143
+ if k.startswith(operation_name)
144
+ }
145
+ # Move accessed metrics to end (LRU)
146
+ for key in list(self._metrics.keys()):
147
+ if key.startswith(operation_name):
148
+ self._metrics.move_to_end(key)
149
+ else:
150
+ metrics = {k: v.to_dict() for k, v in self._metrics.items()}
151
+ # Move all accessed metrics to end (LRU)
152
+ for key in list(self._metrics.keys()):
153
+ self._metrics.move_to_end(key)
154
+
155
+ total_operations = len(self._metrics)
156
+
157
+ return {
158
+ "timestamp": datetime.now().isoformat(),
159
+ "metrics": metrics,
160
+ "total_operations": total_operations,
161
+ }
162
+
163
+ def get_summary(self) -> Dict[str, Any]:
164
+ """
165
+ Get a summary of all metrics.
166
+
167
+ Returns:
168
+ Summary dictionary with aggregated statistics
169
+ """
170
+ with self._lock:
171
+ if not self._metrics:
172
+ return {
173
+ "timestamp": datetime.now().isoformat(),
174
+ "total_operations": 0,
175
+ "summary": {},
176
+ }
177
+
178
+ # Aggregate by base operation name (without tags)
179
+ aggregated: Dict[str, OperationMetrics] = {}
180
+
181
+ for key, metric in self._metrics.items():
182
+ base_name = metric.operation_name
183
+ if base_name not in aggregated:
184
+ aggregated[base_name] = OperationMetrics(operation_name=base_name)
185
+
186
+ agg = aggregated[base_name]
187
+ agg.count += metric.count
188
+ agg.total_duration_ms += metric.total_duration_ms
189
+ agg.min_duration_ms = min(agg.min_duration_ms, metric.min_duration_ms)
190
+ agg.max_duration_ms = max(agg.max_duration_ms, metric.max_duration_ms)
191
+ agg.error_count += metric.error_count
192
+ if metric.last_execution and (
193
+ not agg.last_execution or metric.last_execution > agg.last_execution
194
+ ):
195
+ agg.last_execution = metric.last_execution
196
+
197
+ # Move all accessed metrics to end (LRU)
198
+ for key in list(self._metrics.keys()):
199
+ self._metrics.move_to_end(key)
200
+
201
+ total_operations = len(self._metrics)
202
+ summary = {name: m.to_dict() for name, m in aggregated.items()}
203
+
204
+ return {
205
+ "timestamp": datetime.now().isoformat(),
206
+ "total_operations": total_operations,
207
+ "summary": summary,
208
+ }
209
+
210
+ def reset(self) -> None:
211
+ """Reset all metrics."""
212
+ with self._lock:
213
+ self._metrics.clear()
214
+
215
+ def get_operation_count(self, operation_name: str) -> int:
216
+ """Get the count of executions for an operation."""
217
+ with self._lock:
218
+ total = 0
219
+ for metric in self._metrics.values():
220
+ if metric.operation_name == operation_name:
221
+ total += metric.count
222
+ return total
223
+
224
+
225
+ # Global metrics collector instance
226
+ _metrics_collector: Optional[MetricsCollector] = None
227
+
228
+
229
+ def get_metrics_collector() -> MetricsCollector:
230
+ """Get or create the global metrics collector."""
231
+ global _metrics_collector
232
+ if _metrics_collector is None:
233
+ _metrics_collector = MetricsCollector()
234
+ return _metrics_collector
235
+
236
+
237
+ def record_operation(
238
+ operation_name: str, duration_ms: float, success: bool = True, **tags: Any
239
+ ) -> None:
240
+ """
241
+ Record an operation in the global metrics collector.
242
+
243
+ Args:
244
+ operation_name: Name of the operation
245
+ duration_ms: Duration in milliseconds
246
+ success: Whether the operation succeeded
247
+ **tags: Additional tags
248
+ """
249
+ collector = get_metrics_collector()
250
+ collector.record_operation(operation_name, duration_ms, success, **tags)
251
+
252
+
253
+ def timed_operation(operation_name: str, **tags: Any):
254
+ """
255
+ Decorator to automatically time and record an operation.
256
+
257
+ Usage:
258
+ @timed_operation("engine.initialize")
259
+ async def initialize(self):
260
+ ...
261
+ """
262
+
263
+ def decorator(func: Callable) -> Callable:
264
+ if hasattr(func, "__code__") and func.__code__.co_flags & 0x80: # CO_COROUTINE
265
+ # Async function
266
+ async def async_wrapper(*args, **kwargs):
267
+ start_time = time.time()
268
+ success = True
269
+ try:
270
+ result = await func(*args, **kwargs)
271
+ return result
272
+ except Exception:
273
+ success = False
274
+ raise
275
+ finally:
276
+ duration_ms = (time.time() - start_time) * 1000
277
+ record_operation(operation_name, duration_ms, success, **tags)
278
+
279
+ return async_wrapper
280
+ else:
281
+ # Sync function
282
+ def sync_wrapper(*args, **kwargs):
283
+ start_time = time.time()
284
+ success = True
285
+ try:
286
+ result = func(*args, **kwargs)
287
+ return result
288
+ except Exception:
289
+ success = False
290
+ raise
291
+ finally:
292
+ duration_ms = (time.time() - start_time) * 1000
293
+ record_operation(operation_name, duration_ms, success, **tags)
294
+
295
+ return sync_wrapper
296
+
297
+ return decorator