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.
- mdb_engine/README.md +144 -0
- mdb_engine/__init__.py +37 -0
- mdb_engine/auth/README.md +631 -0
- mdb_engine/auth/__init__.py +128 -0
- mdb_engine/auth/casbin_factory.py +199 -0
- mdb_engine/auth/casbin_models.py +46 -0
- mdb_engine/auth/config_defaults.py +71 -0
- mdb_engine/auth/config_helpers.py +213 -0
- mdb_engine/auth/cookie_utils.py +158 -0
- mdb_engine/auth/decorators.py +350 -0
- mdb_engine/auth/dependencies.py +747 -0
- mdb_engine/auth/helpers.py +64 -0
- mdb_engine/auth/integration.py +578 -0
- mdb_engine/auth/jwt.py +225 -0
- mdb_engine/auth/middleware.py +241 -0
- mdb_engine/auth/oso_factory.py +323 -0
- mdb_engine/auth/provider.py +570 -0
- mdb_engine/auth/restrictions.py +271 -0
- mdb_engine/auth/session_manager.py +477 -0
- mdb_engine/auth/token_lifecycle.py +213 -0
- mdb_engine/auth/token_store.py +289 -0
- mdb_engine/auth/users.py +1516 -0
- mdb_engine/auth/utils.py +614 -0
- mdb_engine/cli/__init__.py +13 -0
- mdb_engine/cli/commands/__init__.py +7 -0
- mdb_engine/cli/commands/generate.py +105 -0
- mdb_engine/cli/commands/migrate.py +83 -0
- mdb_engine/cli/commands/show.py +70 -0
- mdb_engine/cli/commands/validate.py +63 -0
- mdb_engine/cli/main.py +41 -0
- mdb_engine/cli/utils.py +92 -0
- mdb_engine/config.py +217 -0
- mdb_engine/constants.py +160 -0
- mdb_engine/core/README.md +542 -0
- mdb_engine/core/__init__.py +42 -0
- mdb_engine/core/app_registration.py +392 -0
- mdb_engine/core/connection.py +243 -0
- mdb_engine/core/engine.py +749 -0
- mdb_engine/core/index_management.py +162 -0
- mdb_engine/core/manifest.py +2793 -0
- mdb_engine/core/seeding.py +179 -0
- mdb_engine/core/service_initialization.py +355 -0
- mdb_engine/core/types.py +413 -0
- mdb_engine/database/README.md +522 -0
- mdb_engine/database/__init__.py +31 -0
- mdb_engine/database/abstraction.py +635 -0
- mdb_engine/database/connection.py +387 -0
- mdb_engine/database/scoped_wrapper.py +1721 -0
- mdb_engine/embeddings/README.md +184 -0
- mdb_engine/embeddings/__init__.py +62 -0
- mdb_engine/embeddings/dependencies.py +193 -0
- mdb_engine/embeddings/service.py +759 -0
- mdb_engine/exceptions.py +167 -0
- mdb_engine/indexes/README.md +651 -0
- mdb_engine/indexes/__init__.py +21 -0
- mdb_engine/indexes/helpers.py +145 -0
- mdb_engine/indexes/manager.py +895 -0
- mdb_engine/memory/README.md +451 -0
- mdb_engine/memory/__init__.py +30 -0
- mdb_engine/memory/service.py +1285 -0
- mdb_engine/observability/README.md +515 -0
- mdb_engine/observability/__init__.py +42 -0
- mdb_engine/observability/health.py +296 -0
- mdb_engine/observability/logging.py +161 -0
- mdb_engine/observability/metrics.py +297 -0
- mdb_engine/routing/README.md +462 -0
- mdb_engine/routing/__init__.py +73 -0
- mdb_engine/routing/websockets.py +813 -0
- mdb_engine/utils/__init__.py +7 -0
- mdb_engine-0.1.6.dist-info/METADATA +213 -0
- mdb_engine-0.1.6.dist-info/RECORD +75 -0
- mdb_engine-0.1.6.dist-info/WHEEL +5 -0
- mdb_engine-0.1.6.dist-info/entry_points.txt +2 -0
- mdb_engine-0.1.6.dist-info/licenses/LICENSE +661 -0
- 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
|