mdb-engine 0.1.6__py3-none-any.whl → 0.4.12__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 (92) hide show
  1. mdb_engine/__init__.py +116 -11
  2. mdb_engine/auth/ARCHITECTURE.md +112 -0
  3. mdb_engine/auth/README.md +654 -11
  4. mdb_engine/auth/__init__.py +136 -29
  5. mdb_engine/auth/audit.py +592 -0
  6. mdb_engine/auth/base.py +252 -0
  7. mdb_engine/auth/casbin_factory.py +265 -70
  8. mdb_engine/auth/config_defaults.py +5 -5
  9. mdb_engine/auth/config_helpers.py +19 -18
  10. mdb_engine/auth/cookie_utils.py +12 -16
  11. mdb_engine/auth/csrf.py +483 -0
  12. mdb_engine/auth/decorators.py +10 -16
  13. mdb_engine/auth/dependencies.py +69 -71
  14. mdb_engine/auth/helpers.py +3 -3
  15. mdb_engine/auth/integration.py +61 -88
  16. mdb_engine/auth/jwt.py +11 -15
  17. mdb_engine/auth/middleware.py +79 -35
  18. mdb_engine/auth/oso_factory.py +21 -41
  19. mdb_engine/auth/provider.py +270 -171
  20. mdb_engine/auth/rate_limiter.py +505 -0
  21. mdb_engine/auth/restrictions.py +21 -36
  22. mdb_engine/auth/session_manager.py +24 -41
  23. mdb_engine/auth/shared_middleware.py +977 -0
  24. mdb_engine/auth/shared_users.py +775 -0
  25. mdb_engine/auth/token_lifecycle.py +10 -12
  26. mdb_engine/auth/token_store.py +17 -32
  27. mdb_engine/auth/users.py +99 -159
  28. mdb_engine/auth/utils.py +236 -42
  29. mdb_engine/cli/commands/generate.py +546 -10
  30. mdb_engine/cli/commands/validate.py +3 -7
  31. mdb_engine/cli/utils.py +7 -7
  32. mdb_engine/config.py +13 -28
  33. mdb_engine/constants.py +65 -0
  34. mdb_engine/core/README.md +117 -6
  35. mdb_engine/core/__init__.py +39 -7
  36. mdb_engine/core/app_registration.py +31 -50
  37. mdb_engine/core/app_secrets.py +289 -0
  38. mdb_engine/core/connection.py +20 -12
  39. mdb_engine/core/encryption.py +222 -0
  40. mdb_engine/core/engine.py +2862 -115
  41. mdb_engine/core/index_management.py +12 -16
  42. mdb_engine/core/manifest.py +628 -204
  43. mdb_engine/core/ray_integration.py +436 -0
  44. mdb_engine/core/seeding.py +13 -21
  45. mdb_engine/core/service_initialization.py +20 -30
  46. mdb_engine/core/types.py +40 -43
  47. mdb_engine/database/README.md +140 -17
  48. mdb_engine/database/__init__.py +17 -6
  49. mdb_engine/database/abstraction.py +37 -50
  50. mdb_engine/database/connection.py +51 -30
  51. mdb_engine/database/query_validator.py +367 -0
  52. mdb_engine/database/resource_limiter.py +204 -0
  53. mdb_engine/database/scoped_wrapper.py +747 -237
  54. mdb_engine/dependencies.py +427 -0
  55. mdb_engine/di/__init__.py +34 -0
  56. mdb_engine/di/container.py +247 -0
  57. mdb_engine/di/providers.py +206 -0
  58. mdb_engine/di/scopes.py +139 -0
  59. mdb_engine/embeddings/README.md +54 -24
  60. mdb_engine/embeddings/__init__.py +31 -24
  61. mdb_engine/embeddings/dependencies.py +38 -155
  62. mdb_engine/embeddings/service.py +78 -75
  63. mdb_engine/exceptions.py +104 -12
  64. mdb_engine/indexes/README.md +30 -13
  65. mdb_engine/indexes/__init__.py +1 -0
  66. mdb_engine/indexes/helpers.py +11 -11
  67. mdb_engine/indexes/manager.py +59 -123
  68. mdb_engine/memory/README.md +95 -4
  69. mdb_engine/memory/__init__.py +1 -2
  70. mdb_engine/memory/service.py +363 -1168
  71. mdb_engine/observability/README.md +4 -2
  72. mdb_engine/observability/__init__.py +26 -9
  73. mdb_engine/observability/health.py +17 -17
  74. mdb_engine/observability/logging.py +10 -10
  75. mdb_engine/observability/metrics.py +40 -19
  76. mdb_engine/repositories/__init__.py +34 -0
  77. mdb_engine/repositories/base.py +325 -0
  78. mdb_engine/repositories/mongo.py +233 -0
  79. mdb_engine/repositories/unit_of_work.py +166 -0
  80. mdb_engine/routing/README.md +1 -1
  81. mdb_engine/routing/__init__.py +1 -3
  82. mdb_engine/routing/websockets.py +41 -75
  83. mdb_engine/utils/__init__.py +3 -1
  84. mdb_engine/utils/mongo.py +117 -0
  85. mdb_engine-0.4.12.dist-info/METADATA +492 -0
  86. mdb_engine-0.4.12.dist-info/RECORD +97 -0
  87. {mdb_engine-0.1.6.dist-info → mdb_engine-0.4.12.dist-info}/WHEEL +1 -1
  88. mdb_engine-0.1.6.dist-info/METADATA +0 -213
  89. mdb_engine-0.1.6.dist-info/RECORD +0 -75
  90. {mdb_engine-0.1.6.dist-info → mdb_engine-0.4.12.dist-info}/entry_points.txt +0 -0
  91. {mdb_engine-0.1.6.dist-info → mdb_engine-0.4.12.dist-info}/licenses/LICENSE +0 -0
  92. {mdb_engine-0.1.6.dist-info → mdb_engine-0.4.12.dist-info}/top_level.txt +0 -0
@@ -99,9 +99,11 @@ record_operation(
99
99
  )
100
100
 
101
101
  # Record failed operation
102
+ from pymongo.errors import PyMongoError
103
+
102
104
  try:
103
105
  await db.collection.insert_one(document)
104
- except Exception as e:
106
+ except PyMongoError as e:
105
107
  record_operation(
106
108
  operation_name="database.insert_one",
107
109
  duration_ms=0,
@@ -368,7 +370,7 @@ async def check_custom_service():
368
370
  message="Service responded with error",
369
371
  details={"status_code": response.status_code}
370
372
  )
371
- except Exception as e:
373
+ except (httpx.HTTPError, ConnectionError, TimeoutError) as e:
372
374
  return HealthCheckResult(
373
375
  name="custom_service",
374
376
  status=HealthStatus.UNHEALTHY,
@@ -5,15 +5,32 @@ Provides structured logging, metrics collection, request tracing,
5
5
  and health check capabilities.
6
6
  """
7
7
 
8
- from .health import (HealthChecker, HealthCheckResult, HealthStatus,
9
- check_engine_health, check_mongodb_health,
10
- check_pool_health)
11
- from .logging import (ContextualLoggerAdapter, clear_app_context,
12
- clear_correlation_id, get_correlation_id, get_logger,
13
- get_logging_context, log_operation, set_app_context,
14
- set_correlation_id)
15
- from .metrics import (MetricsCollector, OperationMetrics,
16
- get_metrics_collector, record_operation, timed_operation)
8
+ from .health import (
9
+ HealthChecker,
10
+ HealthCheckResult,
11
+ HealthStatus,
12
+ check_engine_health,
13
+ check_mongodb_health,
14
+ check_pool_health,
15
+ )
16
+ from .logging import (
17
+ ContextualLoggerAdapter,
18
+ clear_app_context,
19
+ clear_correlation_id,
20
+ get_correlation_id,
21
+ get_logger,
22
+ get_logging_context,
23
+ log_operation,
24
+ set_app_context,
25
+ set_correlation_id,
26
+ )
27
+ from .metrics import (
28
+ MetricsCollector,
29
+ OperationMetrics,
30
+ get_metrics_collector,
31
+ record_operation,
32
+ timed_operation,
33
+ )
17
34
 
18
35
  __all__ = [
19
36
  # Metrics
@@ -6,13 +6,17 @@ Provides health check functions for monitoring system status.
6
6
 
7
7
  import asyncio
8
8
  import logging
9
+ from collections.abc import Callable
9
10
  from dataclasses import dataclass, field
10
11
  from datetime import datetime
11
12
  from enum import Enum
12
- from typing import Any, Callable, Dict, List, Optional
13
+ from typing import Any
13
14
 
14
- from pymongo.errors import (ConnectionFailure, OperationFailure,
15
- ServerSelectionTimeoutError)
15
+ from pymongo.errors import (
16
+ ConnectionFailure,
17
+ OperationFailure,
18
+ ServerSelectionTimeoutError,
19
+ )
16
20
 
17
21
  logger = logging.getLogger(__name__)
18
22
 
@@ -33,10 +37,10 @@ class HealthCheckResult:
33
37
  name: str
34
38
  status: HealthStatus
35
39
  message: str
36
- details: Optional[Dict[str, Any]] = None
40
+ details: dict[str, Any] | None = None
37
41
  timestamp: datetime = field(default_factory=datetime.now)
38
42
 
39
- def to_dict(self) -> Dict[str, Any]:
43
+ def to_dict(self) -> dict[str, Any]:
40
44
  """Convert to dictionary."""
41
45
  return {
42
46
  "name": self.name,
@@ -54,7 +58,7 @@ class HealthChecker:
54
58
 
55
59
  def __init__(self):
56
60
  """Initialize the health checker."""
57
- self._checks: List[callable] = []
61
+ self._checks: list[callable] = []
58
62
 
59
63
  def register_check(self, check_func: Callable) -> None:
60
64
  """
@@ -65,14 +69,14 @@ class HealthChecker:
65
69
  """
66
70
  self._checks.append(check_func)
67
71
 
68
- async def check_all(self) -> Dict[str, Any]:
72
+ async def check_all(self) -> dict[str, Any]:
69
73
  """
70
74
  Run all registered health checks.
71
75
 
72
76
  Returns:
73
77
  Dictionary with overall status and individual check results
74
78
  """
75
- results: List[HealthCheckResult] = []
79
+ results: list[HealthCheckResult] = []
76
80
 
77
81
  for check_func in self._checks:
78
82
  try:
@@ -86,9 +90,7 @@ class HealthChecker:
86
90
  ConnectionError,
87
91
  OSError,
88
92
  ) as e:
89
- logger.error(
90
- f"Health check {check_func.__name__} failed: {e}", exc_info=True
91
- )
93
+ logger.error(f"Health check {check_func.__name__} failed: {e}", exc_info=True)
92
94
  results.append(
93
95
  HealthCheckResult(
94
96
  name=check_func.__name__,
@@ -116,7 +118,7 @@ class HealthChecker:
116
118
 
117
119
 
118
120
  async def check_mongodb_health(
119
- mongo_client: Optional[Any], timeout_seconds: float = 5.0
121
+ mongo_client: Any | None, timeout_seconds: float = 5.0
120
122
  ) -> HealthCheckResult:
121
123
  """
122
124
  Check MongoDB connection health.
@@ -137,9 +139,7 @@ async def check_mongodb_health(
137
139
 
138
140
  try:
139
141
  # Try to ping MongoDB with timeout
140
- await asyncio.wait_for(
141
- mongo_client.admin.command("ping"), timeout=timeout_seconds
142
- )
142
+ await asyncio.wait_for(mongo_client.admin.command("ping"), timeout=timeout_seconds)
143
143
 
144
144
  return HealthCheckResult(
145
145
  name="mongodb",
@@ -167,7 +167,7 @@ async def check_mongodb_health(
167
167
  )
168
168
 
169
169
 
170
- async def check_engine_health(engine: Optional[Any]) -> HealthCheckResult:
170
+ async def check_engine_health(engine: Any | None) -> HealthCheckResult:
171
171
  """
172
172
  Check MongoDB Engine health.
173
173
 
@@ -206,7 +206,7 @@ async def check_engine_health(engine: Optional[Any]) -> HealthCheckResult:
206
206
 
207
207
 
208
208
  async def check_pool_health(
209
- get_pool_metrics_func: Optional[Callable[[], Any]] = None
209
+ get_pool_metrics_func: Callable[[], Any] | None = None,
210
210
  ) -> HealthCheckResult:
211
211
  """
212
212
  Check connection pool health.
@@ -8,25 +8,25 @@ import contextvars
8
8
  import logging
9
9
  import uuid
10
10
  from datetime import datetime
11
- from typing import Any, Dict, Optional
11
+ from typing import Any
12
12
 
13
13
  # Context variable for correlation ID
14
- _correlation_id: contextvars.ContextVar[Optional[str]] = contextvars.ContextVar(
14
+ _correlation_id: contextvars.ContextVar[str | None] = contextvars.ContextVar(
15
15
  "correlation_id", default=None
16
16
  )
17
17
 
18
18
  # Context variable for app context
19
- _app_context: contextvars.ContextVar[Optional[Dict[str, Any]]] = contextvars.ContextVar(
19
+ _app_context: contextvars.ContextVar[dict[str, Any] | None] = contextvars.ContextVar(
20
20
  "app_context", default=None
21
21
  )
22
22
 
23
23
 
24
- def get_correlation_id() -> Optional[str]:
24
+ def get_correlation_id() -> str | None:
25
25
  """Get the current correlation ID from context."""
26
26
  return _correlation_id.get()
27
27
 
28
28
 
29
- def set_correlation_id(correlation_id: Optional[str] = None) -> str:
29
+ def set_correlation_id(correlation_id: str | None = None) -> str:
30
30
  """
31
31
  Set a correlation ID in the current context.
32
32
 
@@ -47,7 +47,7 @@ def clear_correlation_id() -> None:
47
47
  _correlation_id.set(None)
48
48
 
49
49
 
50
- def set_app_context(app_slug: Optional[str] = None, **kwargs: Any) -> None:
50
+ def set_app_context(app_slug: str | None = None, **kwargs: Any) -> None:
51
51
  """
52
52
  Set app context for logging.
53
53
 
@@ -64,14 +64,14 @@ def clear_app_context() -> None:
64
64
  _app_context.set(None)
65
65
 
66
66
 
67
- def get_logging_context() -> Dict[str, Any]:
67
+ def get_logging_context() -> dict[str, Any]:
68
68
  """
69
69
  Get current logging context (correlation ID and app context).
70
70
 
71
71
  Returns:
72
72
  Dictionary with context information
73
73
  """
74
- context: Dict[str, Any] = {
74
+ context: dict[str, Any] = {
75
75
  "timestamp": datetime.now().isoformat(),
76
76
  }
77
77
 
@@ -91,7 +91,7 @@ class ContextualLoggerAdapter(logging.LoggerAdapter):
91
91
  Logger adapter that automatically adds context to log records.
92
92
  """
93
93
 
94
- def process(self, msg: str, kwargs: Dict[str, Any]) -> tuple[str, Dict[str, Any]]:
94
+ def process(self, msg: str, kwargs: dict[str, Any]) -> tuple[str, dict[str, Any]]:
95
95
  """Add context to log records."""
96
96
  # Get base context
97
97
  context = get_logging_context()
@@ -124,7 +124,7 @@ def log_operation(
124
124
  operation: str,
125
125
  level: int = logging.INFO,
126
126
  success: bool = True,
127
- duration_ms: Optional[float] = None,
127
+ duration_ms: float | None = None,
128
128
  **context: Any,
129
129
  ) -> None:
130
130
  """
@@ -9,9 +9,10 @@ import logging
9
9
  import threading
10
10
  import time
11
11
  from collections import OrderedDict
12
+ from collections.abc import Callable
12
13
  from dataclasses import dataclass
13
14
  from datetime import datetime
14
- from typing import Any, Callable, Dict, Optional
15
+ from typing import Any
15
16
 
16
17
  logger = logging.getLogger(__name__)
17
18
 
@@ -26,7 +27,7 @@ class OperationMetrics:
26
27
  min_duration_ms: float = float("inf")
27
28
  max_duration_ms: float = 0.0
28
29
  error_count: int = 0
29
- last_execution: Optional[datetime] = None
30
+ last_execution: datetime | None = None
30
31
 
31
32
  @property
32
33
  def avg_duration_ms(self) -> float:
@@ -48,23 +49,19 @@ class OperationMetrics:
48
49
  self.error_count += 1
49
50
  self.last_execution = datetime.now()
50
51
 
51
- def to_dict(self) -> Dict[str, Any]:
52
+ def to_dict(self) -> dict[str, Any]:
52
53
  """Convert metrics to dictionary."""
53
54
  return {
54
55
  "operation": self.operation_name,
55
56
  "count": self.count,
56
57
  "avg_duration_ms": round(self.avg_duration_ms, 2),
57
58
  "min_duration_ms": (
58
- round(self.min_duration_ms, 2)
59
- if self.min_duration_ms != float("inf")
60
- else 0.0
59
+ round(self.min_duration_ms, 2) if self.min_duration_ms != float("inf") else 0.0
61
60
  ),
62
61
  "max_duration_ms": round(self.max_duration_ms, 2),
63
62
  "error_count": self.error_count,
64
63
  "error_rate_percent": round(self.error_rate, 2),
65
- "last_execution": (
66
- self.last_execution.isoformat() if self.last_execution else None
67
- ),
64
+ "last_execution": (self.last_execution.isoformat() if self.last_execution else None),
68
65
  }
69
66
 
70
67
 
@@ -125,7 +122,7 @@ class MetricsCollector:
125
122
 
126
123
  self._metrics[key].record(duration_ms, success)
127
124
 
128
- def get_metrics(self, operation_name: Optional[str] = None) -> Dict[str, Any]:
125
+ def get_metrics(self, operation_name: str | None = None) -> dict[str, Any]:
129
126
  """
130
127
  Get metrics for operations.
131
128
 
@@ -138,9 +135,7 @@ class MetricsCollector:
138
135
  with self._lock:
139
136
  if operation_name:
140
137
  metrics = {
141
- k: v.to_dict()
142
- for k, v in self._metrics.items()
143
- if k.startswith(operation_name)
138
+ k: v.to_dict() for k, v in self._metrics.items() if k.startswith(operation_name)
144
139
  }
145
140
  # Move accessed metrics to end (LRU)
146
141
  for key in list(self._metrics.keys()):
@@ -160,7 +155,7 @@ class MetricsCollector:
160
155
  "total_operations": total_operations,
161
156
  }
162
157
 
163
- def get_summary(self) -> Dict[str, Any]:
158
+ def get_summary(self) -> dict[str, Any]:
164
159
  """
165
160
  Get a summary of all metrics.
166
161
 
@@ -176,9 +171,9 @@ class MetricsCollector:
176
171
  }
177
172
 
178
173
  # Aggregate by base operation name (without tags)
179
- aggregated: Dict[str, OperationMetrics] = {}
174
+ aggregated: dict[str, OperationMetrics] = {}
180
175
 
181
- for key, metric in self._metrics.items():
176
+ for _key, metric in self._metrics.items():
182
177
  base_name = metric.operation_name
183
178
  if base_name not in aggregated:
184
179
  aggregated[base_name] = OperationMetrics(operation_name=base_name)
@@ -223,7 +218,7 @@ class MetricsCollector:
223
218
 
224
219
 
225
220
  # Global metrics collector instance
226
- _metrics_collector: Optional[MetricsCollector] = None
221
+ _metrics_collector: MetricsCollector | None = None
227
222
 
228
223
 
229
224
  def get_metrics_collector() -> MetricsCollector:
@@ -260,6 +255,32 @@ def timed_operation(operation_name: str, **tags: Any):
260
255
  ...
261
256
  """
262
257
 
258
+ # Common exceptions that indicate operation failure (not system-level exits)
259
+ # This is comprehensive to handle any decorated function's failures
260
+ _OPERATION_FAILURES = (
261
+ RuntimeError,
262
+ ValueError,
263
+ TypeError,
264
+ KeyError,
265
+ AttributeError,
266
+ IndexError,
267
+ LookupError,
268
+ IOError,
269
+ OSError,
270
+ ConnectionError,
271
+ TimeoutError,
272
+ PermissionError,
273
+ FileNotFoundError,
274
+ AssertionError,
275
+ ArithmeticError,
276
+ BufferError,
277
+ ImportError,
278
+ MemoryError,
279
+ StopIteration,
280
+ StopAsyncIteration,
281
+ UnicodeError,
282
+ )
283
+
263
284
  def decorator(func: Callable) -> Callable:
264
285
  if hasattr(func, "__code__") and func.__code__.co_flags & 0x80: # CO_COROUTINE
265
286
  # Async function
@@ -269,7 +290,7 @@ def timed_operation(operation_name: str, **tags: Any):
269
290
  try:
270
291
  result = await func(*args, **kwargs)
271
292
  return result
272
- except Exception:
293
+ except _OPERATION_FAILURES:
273
294
  success = False
274
295
  raise
275
296
  finally:
@@ -285,7 +306,7 @@ def timed_operation(operation_name: str, **tags: Any):
285
306
  try:
286
307
  result = func(*args, **kwargs)
287
308
  return result
288
- except Exception:
309
+ except _OPERATION_FAILURES:
289
310
  success = False
290
311
  raise
291
312
  finally:
@@ -0,0 +1,34 @@
1
+ """
2
+ MDB Engine Repository Pattern
3
+
4
+ Provides abstract repository interfaces and MongoDB implementations
5
+ for clean data access patterns.
6
+
7
+ Usage:
8
+ from mdb_engine.repositories import Repository, MongoRepository
9
+
10
+ # In domain services
11
+ class UserService:
12
+ def __init__(self, users: Repository[User]):
13
+ self._users = users
14
+
15
+ async def get_user(self, id: str) -> User:
16
+ return await self._users.get(id)
17
+
18
+ # In FastAPI routes using UnitOfWork
19
+ @app.get("/users/{user_id}")
20
+ async def get_user(user_id: str, ctx: RequestContext = Depends()):
21
+ user = await ctx.uow.users.get(user_id)
22
+ return user
23
+ """
24
+
25
+ from .base import Entity, Repository
26
+ from .mongo import MongoRepository
27
+ from .unit_of_work import UnitOfWork
28
+
29
+ __all__ = [
30
+ "Repository",
31
+ "Entity",
32
+ "MongoRepository",
33
+ "UnitOfWork",
34
+ ]