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,387 @@
1
+ """
2
+ Shared MongoDB Connection Pool Manager
3
+
4
+ Provides a singleton MongoDB connection pool manager for sharing database
5
+ connections efficiently across multiple components in the same process.
6
+ This prevents each component from creating its own connection pool, reducing
7
+ total connections and improving resource utilization.
8
+
9
+ This module implements a thread-safe singleton pattern that ensures all
10
+ components in the same process share a single MongoDB client instance with
11
+ a reasonable connection pool size.
12
+
13
+ This module is part of MDB_ENGINE - MongoDB Engine.
14
+
15
+ Usage:
16
+ from mdb_engine.database import get_shared_mongo_client, get_pool_metrics
17
+
18
+ client = get_shared_mongo_client(mongo_uri)
19
+ db = client[db_name]
20
+ metrics = await get_pool_metrics() # Monitor pool usage
21
+ """
22
+
23
+ import logging
24
+ import os
25
+ import threading
26
+ from typing import Any, Dict, Optional
27
+
28
+ from motor.motor_asyncio import AsyncIOMotorClient
29
+ from pymongo.errors import (ConnectionFailure, InvalidOperation,
30
+ OperationFailure, ServerSelectionTimeoutError)
31
+
32
+ logger = logging.getLogger(__name__)
33
+
34
+ # Global singleton instance
35
+ _shared_client: Optional[AsyncIOMotorClient] = None
36
+ # Use threading.Lock for cross-thread safety in multi-threaded environments
37
+ # asyncio.Lock isn't sufficient for thread-safe initialization
38
+ _init_lock = threading.Lock()
39
+
40
+
41
+ def get_shared_mongo_client(
42
+ mongo_uri: str,
43
+ max_pool_size: Optional[int] = None,
44
+ min_pool_size: Optional[int] = None,
45
+ server_selection_timeout_ms: int = 5000,
46
+ max_idle_time_ms: int = 45000,
47
+ retry_writes: bool = True,
48
+ retry_reads: bool = True,
49
+ ) -> AsyncIOMotorClient:
50
+ """
51
+ Gets or creates a shared MongoDB client instance.
52
+
53
+ This function implements a singleton pattern to ensure all components
54
+ in the same process share a single MongoDB client connection pool.
55
+
56
+ Args:
57
+ mongo_uri: MongoDB connection URI
58
+ max_pool_size: Maximum connection pool size (default: from env or 10)
59
+ min_pool_size: Minimum connection pool size (default: from env or 1)
60
+ server_selection_timeout_ms: Server selection timeout in milliseconds
61
+ max_idle_time_ms: Maximum idle time before closing connections
62
+ retry_writes: Enable automatic retry for write operations
63
+ retry_reads: Enable automatic retry for read operations
64
+
65
+ Returns:
66
+ Shared AsyncIOMotorClient instance
67
+
68
+ Example:
69
+ client = get_shared_mongo_client("mongodb://mongo:27017/")
70
+ db = client["my_database"]
71
+ """
72
+ global _shared_client
73
+
74
+ # Get pool sizes from environment or use defaults
75
+ if max_pool_size is None:
76
+ max_pool_size = int(os.getenv("MONGO_ACTOR_MAX_POOL_SIZE", "10"))
77
+ if min_pool_size is None:
78
+ min_pool_size = int(os.getenv("MONGO_ACTOR_MIN_POOL_SIZE", "1"))
79
+
80
+ logger.info(
81
+ f"MongoDB Shared Pool Configuration: max_pool_size={max_pool_size}, "
82
+ f"min_pool_size={min_pool_size} (from env or defaults)"
83
+ )
84
+
85
+ # Fast path: return existing client if already initialized
86
+ if _shared_client is not None:
87
+ # Verify client is still connected
88
+ try:
89
+ # Non-blocking check - if client was closed, it will be None or invalid
90
+ if (
91
+ hasattr(_shared_client, "_topology")
92
+ and _shared_client._topology is not None
93
+ ):
94
+ return _shared_client
95
+ except (AttributeError, RuntimeError):
96
+ # Client was closed or invalid, reset and recreate
97
+ # Type 2: Recoverable - reset client and continue
98
+ _shared_client = None
99
+
100
+ # Thread-safe initialization with lock
101
+ # Use threading lock for multi-threaded environments
102
+ with _init_lock:
103
+ # Double-check pattern: another thread may have initialized while we waited
104
+ if _shared_client is not None:
105
+ try:
106
+ if (
107
+ hasattr(_shared_client, "_topology")
108
+ and _shared_client._topology is not None
109
+ ):
110
+ return _shared_client
111
+ except (AttributeError, RuntimeError):
112
+ # Client was closed or invalid, reset and recreate
113
+ # Type 2: Recoverable - reset client and continue
114
+ _shared_client = None
115
+
116
+ logger.info(
117
+ f"Creating shared MongoDB client with pool_size={max_pool_size}, "
118
+ f"min_pool_size={min_pool_size} (singleton for all components in this process)"
119
+ )
120
+
121
+ try:
122
+ _shared_client = AsyncIOMotorClient(
123
+ mongo_uri,
124
+ serverSelectionTimeoutMS=server_selection_timeout_ms,
125
+ appname="MDB_ENGINE_Shared",
126
+ maxPoolSize=max_pool_size,
127
+ minPoolSize=min_pool_size,
128
+ maxIdleTimeMS=max_idle_time_ms,
129
+ retryWrites=retry_writes,
130
+ retryReads=retry_reads,
131
+ )
132
+
133
+ logger.info(
134
+ f"Shared MongoDB client created successfully "
135
+ f"(max_pool_size={max_pool_size}, min_pool_size={min_pool_size})"
136
+ )
137
+ logger.info(
138
+ f"Connection pool limits: max={max_pool_size}, min={min_pool_size}. "
139
+ f"Monitor pool usage with get_pool_metrics() to prevent exhaustion."
140
+ )
141
+
142
+ # Note: Ping verification happens asynchronously on first use
143
+ # This avoids blocking during synchronous initialization
144
+
145
+ return _shared_client
146
+ except (
147
+ ConnectionFailure,
148
+ ServerSelectionTimeoutError,
149
+ ValueError,
150
+ TypeError,
151
+ ) as e:
152
+ logger.error(f"Failed to create shared MongoDB client: {e}", exc_info=True)
153
+ _shared_client = None
154
+ raise
155
+
156
+ return _shared_client
157
+
158
+
159
+ async def verify_shared_client() -> bool:
160
+ """
161
+ Verifies that the shared MongoDB client is connected.
162
+ Should be called from async context after initialization.
163
+
164
+ Returns:
165
+ True if client is connected and responsive, False otherwise
166
+ """
167
+ global _shared_client
168
+
169
+ if _shared_client is None:
170
+ logger.warning("Shared MongoDB client is None - cannot verify")
171
+ return False
172
+
173
+ try:
174
+ await _shared_client.admin.command("ping")
175
+ logger.debug("Shared MongoDB client verification successful")
176
+ return True
177
+ except (
178
+ ConnectionFailure,
179
+ ServerSelectionTimeoutError,
180
+ OperationFailure,
181
+ InvalidOperation,
182
+ ) as e:
183
+ logger.error(f"Shared MongoDB client verification failed: {e}")
184
+ return False
185
+
186
+
187
+ # Registry for tracking all MongoDB clients (for monitoring)
188
+ _registered_clients: list = []
189
+
190
+
191
+ def register_client_for_metrics(client: AsyncIOMotorClient) -> None:
192
+ """
193
+ Register a MongoDB client for pool metrics monitoring.
194
+
195
+ This allows MongoDBEngine and other components to register their clients
196
+ so pool metrics can be tracked even if they don't use the shared client.
197
+
198
+ Args:
199
+ client: AsyncIOMotorClient instance to register
200
+ """
201
+ global _registered_clients
202
+ if client not in _registered_clients:
203
+ _registered_clients.append(client)
204
+ logger.debug("Registered MongoDB client for pool metrics")
205
+
206
+
207
+ async def get_pool_metrics(
208
+ client: Optional[AsyncIOMotorClient] = None,
209
+ ) -> Dict[str, Any]:
210
+ """
211
+ Gets connection pool metrics for monitoring.
212
+ Returns information about pool size, active connections, etc.
213
+
214
+ Args:
215
+ client: Optional specific client to check. If None, checks shared client first,
216
+ then any registered clients (e.g., MongoDBEngine's client).
217
+
218
+ Returns:
219
+ Dictionary with pool metrics including:
220
+ - max_pool_size: Maximum pool size
221
+ - min_pool_size: Minimum pool size
222
+ - active_connections: Current active connections (if available)
223
+ - pool_usage_percent: Estimated pool usage percentage
224
+ """
225
+ global _shared_client, _registered_clients
226
+
227
+ # If specific client provided, use it
228
+ if client is not None:
229
+ return await _get_client_pool_metrics(client)
230
+
231
+ # Try shared client first
232
+ if _shared_client is not None:
233
+ return await _get_client_pool_metrics(_shared_client)
234
+
235
+ # Try registered clients (e.g., MongoDBEngine's client)
236
+ for registered_client in _registered_clients:
237
+ try:
238
+ # Verify client is still valid
239
+ if (
240
+ hasattr(registered_client, "_topology")
241
+ and registered_client._topology is not None
242
+ ):
243
+ return await _get_client_pool_metrics(registered_client)
244
+ except (AttributeError, RuntimeError):
245
+ # Type 2: Recoverable - if this client is invalid, try next one
246
+ continue
247
+
248
+ # No client available
249
+ return {
250
+ "status": "no_client",
251
+ "error": "No MongoDB client available for metrics (shared or registered)",
252
+ }
253
+
254
+
255
+ async def _get_client_pool_metrics(client: AsyncIOMotorClient) -> Dict[str, Any]:
256
+ """
257
+ Internal helper to get pool metrics from a specific client.
258
+
259
+ Uses MongoDB serverStatus for accurate connection information.
260
+
261
+ Args:
262
+ client: AsyncIOMotorClient instance
263
+
264
+ Returns:
265
+ Dictionary with pool metrics
266
+ """
267
+
268
+ try:
269
+ # Verify client is valid
270
+ if client is None:
271
+ return {"status": "error", "error": "Client is None"}
272
+
273
+ # Try to get pool configuration from client options
274
+ max_pool_size = None
275
+ min_pool_size = None
276
+
277
+ # Try multiple ways to get pool size (Motor version compatibility)
278
+ try:
279
+ # Method 1: From client options (most reliable)
280
+ if hasattr(client, "options") and client.options:
281
+ max_pool_size = getattr(client.options, "maxPoolSize", None) or getattr(
282
+ client.options, "max_pool_size", None
283
+ )
284
+ min_pool_size = getattr(client.options, "minPoolSize", None) or getattr(
285
+ client.options, "min_pool_size", None
286
+ )
287
+
288
+ # Method 2: From client attributes directly
289
+ if max_pool_size is None:
290
+ max_pool_size = getattr(client, "maxPoolSize", None) or getattr(
291
+ client, "max_pool_size", None
292
+ )
293
+ if min_pool_size is None:
294
+ min_pool_size = getattr(client, "minPoolSize", None) or getattr(
295
+ client, "min_pool_size", None
296
+ )
297
+ except (AttributeError, TypeError, KeyError) as e:
298
+ logger.debug(f"Could not get pool size from client options: {e}")
299
+
300
+ # Get connection info from MongoDB serverStatus (most accurate)
301
+ current_connections = None
302
+ available_connections = None
303
+ total_created = None
304
+
305
+ try:
306
+ server_status = await client.admin.command("serverStatus")
307
+ connections = server_status.get("connections", {})
308
+ current_connections = connections.get("current", 0)
309
+ available_connections = connections.get("available", 0)
310
+ total_created = connections.get("totalCreated", 0)
311
+ except (
312
+ OperationFailure,
313
+ ConnectionFailure,
314
+ ServerSelectionTimeoutError,
315
+ InvalidOperation,
316
+ ) as e:
317
+ logger.debug(f"Could not get server status for connections: {e}")
318
+
319
+ # Build metrics
320
+ metrics = {
321
+ "status": "connected",
322
+ }
323
+
324
+ if max_pool_size is not None:
325
+ metrics["max_pool_size"] = max_pool_size
326
+ if min_pool_size is not None:
327
+ metrics["min_pool_size"] = min_pool_size
328
+
329
+ # Add connection info from serverStatus
330
+ if current_connections is not None:
331
+ metrics["current_connections"] = current_connections
332
+ if available_connections is not None:
333
+ metrics["available_connections"] = available_connections
334
+ if total_created is not None:
335
+ metrics["total_connections_created"] = total_created
336
+
337
+ # Calculate pool usage if we have max_pool_size and current connections
338
+ if max_pool_size and current_connections is not None:
339
+ usage_percent = (current_connections / max_pool_size) * 100
340
+ metrics["pool_usage_percent"] = round(usage_percent, 2)
341
+ metrics["active_connections"] = (
342
+ current_connections # Alias for compatibility
343
+ )
344
+
345
+ # Warn if pool usage is high
346
+ if usage_percent > 80:
347
+ logger.warning(
348
+ f"MongoDB connection pool usage is HIGH: {usage_percent:.1f}% "
349
+ f"({current_connections}/{max_pool_size}). Consider increasing max_pool_size."
350
+ )
351
+ elif current_connections is not None:
352
+ # We have connection count but not max pool size - still useful info
353
+ metrics["active_connections"] = current_connections
354
+
355
+ return metrics
356
+ except (
357
+ OperationFailure,
358
+ ConnectionFailure,
359
+ ServerSelectionTimeoutError,
360
+ InvalidOperation,
361
+ AttributeError,
362
+ KeyError,
363
+ ) as e:
364
+ logger.debug(f"Error getting detailed pool metrics (non-critical): {e}")
365
+ # Return minimal metrics - client exists and is being used
366
+ return {
367
+ "status": "connected",
368
+ "note": "Detailed metrics unavailable, but client is operational",
369
+ "error": str(e),
370
+ }
371
+
372
+
373
+ def close_shared_client():
374
+ """
375
+ Closes the shared MongoDB client.
376
+ Should be called during application shutdown.
377
+ """
378
+ global _shared_client
379
+
380
+ if _shared_client is not None:
381
+ try:
382
+ _shared_client.close()
383
+ logger.info("Shared MongoDB client closed")
384
+ except (InvalidOperation, AttributeError, RuntimeError) as e:
385
+ logger.warning(f"Error closing shared MongoDB client: {e}")
386
+ finally:
387
+ _shared_client = None