mdb-engine 0.2.1__py3-none-any.whl → 0.2.3__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/auth/audit.py +40 -40
- mdb_engine/auth/base.py +3 -3
- mdb_engine/auth/casbin_factory.py +6 -6
- mdb_engine/auth/config_defaults.py +5 -5
- mdb_engine/auth/config_helpers.py +12 -12
- mdb_engine/auth/cookie_utils.py +9 -9
- mdb_engine/auth/csrf.py +9 -8
- mdb_engine/auth/decorators.py +7 -6
- mdb_engine/auth/dependencies.py +22 -21
- mdb_engine/auth/integration.py +9 -9
- mdb_engine/auth/jwt.py +9 -9
- mdb_engine/auth/middleware.py +4 -3
- mdb_engine/auth/oso_factory.py +6 -6
- mdb_engine/auth/provider.py +4 -4
- mdb_engine/auth/rate_limiter.py +12 -11
- mdb_engine/auth/restrictions.py +16 -15
- mdb_engine/auth/session_manager.py +11 -13
- mdb_engine/auth/shared_middleware.py +16 -15
- mdb_engine/auth/shared_users.py +20 -20
- mdb_engine/auth/token_lifecycle.py +10 -12
- mdb_engine/auth/token_store.py +4 -5
- mdb_engine/auth/users.py +51 -52
- mdb_engine/auth/utils.py +29 -33
- mdb_engine/cli/commands/generate.py +6 -6
- mdb_engine/cli/utils.py +4 -4
- mdb_engine/config.py +6 -7
- mdb_engine/core/app_registration.py +12 -12
- mdb_engine/core/app_secrets.py +1 -2
- mdb_engine/core/connection.py +3 -4
- mdb_engine/core/encryption.py +1 -2
- mdb_engine/core/engine.py +43 -44
- mdb_engine/core/manifest.py +59 -58
- mdb_engine/core/ray_integration.py +10 -9
- mdb_engine/core/seeding.py +3 -3
- mdb_engine/core/service_initialization.py +10 -9
- mdb_engine/core/types.py +40 -40
- mdb_engine/database/abstraction.py +15 -16
- mdb_engine/database/connection.py +40 -12
- mdb_engine/database/query_validator.py +8 -8
- mdb_engine/database/resource_limiter.py +7 -7
- mdb_engine/database/scoped_wrapper.py +51 -58
- mdb_engine/dependencies.py +14 -13
- mdb_engine/di/container.py +12 -13
- mdb_engine/di/providers.py +14 -13
- mdb_engine/di/scopes.py +5 -5
- mdb_engine/embeddings/dependencies.py +2 -2
- mdb_engine/embeddings/service.py +31 -43
- mdb_engine/exceptions.py +20 -20
- mdb_engine/indexes/helpers.py +11 -11
- mdb_engine/indexes/manager.py +9 -9
- mdb_engine/memory/service.py +30 -30
- mdb_engine/observability/health.py +10 -9
- mdb_engine/observability/logging.py +10 -10
- mdb_engine/observability/metrics.py +8 -7
- mdb_engine/repositories/base.py +25 -25
- mdb_engine/repositories/mongo.py +17 -17
- mdb_engine/repositories/unit_of_work.py +6 -6
- mdb_engine/routing/websockets.py +19 -18
- {mdb_engine-0.2.1.dist-info → mdb_engine-0.2.3.dist-info}/METADATA +8 -8
- mdb_engine-0.2.3.dist-info/RECORD +96 -0
- mdb_engine-0.2.1.dist-info/RECORD +0 -96
- {mdb_engine-0.2.1.dist-info → mdb_engine-0.2.3.dist-info}/WHEEL +0 -0
- {mdb_engine-0.2.1.dist-info → mdb_engine-0.2.3.dist-info}/entry_points.txt +0 -0
- {mdb_engine-0.2.1.dist-info → mdb_engine-0.2.3.dist-info}/licenses/LICENSE +0 -0
- {mdb_engine-0.2.1.dist-info → mdb_engine-0.2.3.dist-info}/top_level.txt +0 -0
|
@@ -24,7 +24,8 @@ Usage:
|
|
|
24
24
|
"""
|
|
25
25
|
|
|
26
26
|
import logging
|
|
27
|
-
from
|
|
27
|
+
from collections.abc import Callable
|
|
28
|
+
from typing import Any
|
|
28
29
|
|
|
29
30
|
from ..exceptions import MongoDBEngineError
|
|
30
31
|
from .scoped_wrapper import ScopedMongoWrapper
|
|
@@ -88,8 +89,8 @@ class Collection:
|
|
|
88
89
|
self._collection = scoped_collection
|
|
89
90
|
|
|
90
91
|
async def find_one(
|
|
91
|
-
self, filter:
|
|
92
|
-
) ->
|
|
92
|
+
self, filter: dict[str, Any] | None = None, *args, **kwargs
|
|
93
|
+
) -> dict[str, Any] | None:
|
|
93
94
|
"""
|
|
94
95
|
Find a single document matching the filter.
|
|
95
96
|
|
|
@@ -120,7 +121,7 @@ class Collection:
|
|
|
120
121
|
context={"operation": "find_one"},
|
|
121
122
|
) from e
|
|
122
123
|
|
|
123
|
-
def find(self, filter:
|
|
124
|
+
def find(self, filter: dict[str, Any] | None = None, *args, **kwargs) -> AsyncIOMotorCursor:
|
|
124
125
|
"""
|
|
125
126
|
Find documents matching the filter.
|
|
126
127
|
|
|
@@ -146,7 +147,7 @@ class Collection:
|
|
|
146
147
|
"""
|
|
147
148
|
return self._collection.find(filter or {}, *args, **kwargs)
|
|
148
149
|
|
|
149
|
-
async def insert_one(self, document:
|
|
150
|
+
async def insert_one(self, document: dict[str, Any], *args, **kwargs) -> InsertOneResult:
|
|
150
151
|
"""
|
|
151
152
|
Insert a single document.
|
|
152
153
|
|
|
@@ -178,7 +179,7 @@ class Collection:
|
|
|
178
179
|
) from e
|
|
179
180
|
|
|
180
181
|
async def insert_many(
|
|
181
|
-
self, documents:
|
|
182
|
+
self, documents: list[dict[str, Any]], *args, **kwargs
|
|
182
183
|
) -> InsertManyResult:
|
|
183
184
|
"""
|
|
184
185
|
Insert multiple documents at once.
|
|
@@ -213,7 +214,7 @@ class Collection:
|
|
|
213
214
|
) from e
|
|
214
215
|
|
|
215
216
|
async def update_one(
|
|
216
|
-
self, filter:
|
|
217
|
+
self, filter: dict[str, Any], update: dict[str, Any], *args, **kwargs
|
|
217
218
|
) -> UpdateResult:
|
|
218
219
|
"""
|
|
219
220
|
Update a single document matching the filter.
|
|
@@ -251,7 +252,7 @@ class Collection:
|
|
|
251
252
|
) from e
|
|
252
253
|
|
|
253
254
|
async def update_many(
|
|
254
|
-
self, filter:
|
|
255
|
+
self, filter: dict[str, Any], update: dict[str, Any], *args, **kwargs
|
|
255
256
|
) -> UpdateResult:
|
|
256
257
|
"""
|
|
257
258
|
Update multiple documents matching the filter.
|
|
@@ -288,7 +289,7 @@ class Collection:
|
|
|
288
289
|
) from e
|
|
289
290
|
|
|
290
291
|
async def replace_one(
|
|
291
|
-
self, filter:
|
|
292
|
+
self, filter: dict[str, Any], replacement: dict[str, Any], *args, **kwargs
|
|
292
293
|
) -> UpdateResult:
|
|
293
294
|
"""
|
|
294
295
|
Replace a single document matching the filter.
|
|
@@ -367,7 +368,7 @@ class Collection:
|
|
|
367
368
|
context={"operation": "replace_one"},
|
|
368
369
|
) from e
|
|
369
370
|
|
|
370
|
-
async def delete_one(self, filter:
|
|
371
|
+
async def delete_one(self, filter: dict[str, Any], *args, **kwargs) -> DeleteResult:
|
|
371
372
|
"""
|
|
372
373
|
Delete a single document matching the filter.
|
|
373
374
|
|
|
@@ -400,7 +401,7 @@ class Collection:
|
|
|
400
401
|
) from e
|
|
401
402
|
|
|
402
403
|
async def delete_many(
|
|
403
|
-
self, filter:
|
|
404
|
+
self, filter: dict[str, Any] | None = None, *args, **kwargs
|
|
404
405
|
) -> DeleteResult:
|
|
405
406
|
"""
|
|
406
407
|
Delete multiple documents matching the filter.
|
|
@@ -432,9 +433,7 @@ class Collection:
|
|
|
432
433
|
context={"operation": "delete_many"},
|
|
433
434
|
) from e
|
|
434
435
|
|
|
435
|
-
async def count_documents(
|
|
436
|
-
self, filter: Optional[Dict[str, Any]] = None, *args, **kwargs
|
|
437
|
-
) -> int:
|
|
436
|
+
async def count_documents(self, filter: dict[str, Any] | None = None, *args, **kwargs) -> int:
|
|
438
437
|
"""
|
|
439
438
|
Count documents matching the filter.
|
|
440
439
|
|
|
@@ -464,7 +463,7 @@ class Collection:
|
|
|
464
463
|
context={"operation": "count_documents"},
|
|
465
464
|
) from e
|
|
466
465
|
|
|
467
|
-
def aggregate(self, pipeline:
|
|
466
|
+
def aggregate(self, pipeline: list[dict[str, Any]], *args, **kwargs) -> AsyncIOMotorCursor:
|
|
468
467
|
"""
|
|
469
468
|
Perform aggregation pipeline.
|
|
470
469
|
|
|
@@ -520,7 +519,7 @@ class AppDB:
|
|
|
520
519
|
raise RuntimeError("ScopedMongoWrapper is not available. Check imports.")
|
|
521
520
|
|
|
522
521
|
self._wrapper = scoped_wrapper
|
|
523
|
-
self._collection_cache:
|
|
522
|
+
self._collection_cache: dict[str, Collection] = {}
|
|
524
523
|
|
|
525
524
|
def collection(self, name: str) -> Collection:
|
|
526
525
|
"""
|
|
@@ -23,7 +23,7 @@ Usage:
|
|
|
23
23
|
import logging
|
|
24
24
|
import os
|
|
25
25
|
import threading
|
|
26
|
-
from typing import Any
|
|
26
|
+
from typing import Any
|
|
27
27
|
|
|
28
28
|
from motor.motor_asyncio import AsyncIOMotorClient
|
|
29
29
|
from pymongo.errors import (
|
|
@@ -36,7 +36,7 @@ from pymongo.errors import (
|
|
|
36
36
|
logger = logging.getLogger(__name__)
|
|
37
37
|
|
|
38
38
|
# Global singleton instance
|
|
39
|
-
_shared_client:
|
|
39
|
+
_shared_client: AsyncIOMotorClient | None = None
|
|
40
40
|
# Use threading.Lock for cross-thread safety in multi-threaded environments
|
|
41
41
|
# asyncio.Lock isn't sufficient for thread-safe initialization
|
|
42
42
|
_init_lock = threading.Lock()
|
|
@@ -44,8 +44,8 @@ _init_lock = threading.Lock()
|
|
|
44
44
|
|
|
45
45
|
def get_shared_mongo_client(
|
|
46
46
|
mongo_uri: str,
|
|
47
|
-
max_pool_size:
|
|
48
|
-
min_pool_size:
|
|
47
|
+
max_pool_size: int | None = None,
|
|
48
|
+
min_pool_size: int | None = None,
|
|
49
49
|
server_selection_timeout_ms: int = 5000,
|
|
50
50
|
max_idle_time_ms: int = 45000,
|
|
51
51
|
retry_writes: bool = True,
|
|
@@ -203,8 +203,8 @@ def register_client_for_metrics(client: AsyncIOMotorClient) -> None:
|
|
|
203
203
|
|
|
204
204
|
|
|
205
205
|
async def get_pool_metrics(
|
|
206
|
-
client:
|
|
207
|
-
) ->
|
|
206
|
+
client: AsyncIOMotorClient | None = None,
|
|
207
|
+
) -> dict[str, Any]:
|
|
208
208
|
"""
|
|
209
209
|
Gets connection pool metrics for monitoring.
|
|
210
210
|
Returns information about pool size, active connections, etc.
|
|
@@ -247,7 +247,7 @@ async def get_pool_metrics(
|
|
|
247
247
|
}
|
|
248
248
|
|
|
249
249
|
|
|
250
|
-
async def _get_client_pool_metrics(client: AsyncIOMotorClient) ->
|
|
250
|
+
async def _get_client_pool_metrics(client: AsyncIOMotorClient) -> dict[str, Any]:
|
|
251
251
|
"""
|
|
252
252
|
Internal helper to get pool metrics from a specific client.
|
|
253
253
|
|
|
@@ -299,10 +299,32 @@ async def _get_client_pool_metrics(client: AsyncIOMotorClient) -> Dict[str, Any]
|
|
|
299
299
|
|
|
300
300
|
try:
|
|
301
301
|
server_status = await client.admin.command("serverStatus")
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
302
|
+
if not isinstance(server_status, dict):
|
|
303
|
+
# Mock or invalid response - skip connection metrics
|
|
304
|
+
current_connections = None
|
|
305
|
+
available_connections = None
|
|
306
|
+
total_created = None
|
|
307
|
+
else:
|
|
308
|
+
connections = server_status.get("connections", {})
|
|
309
|
+
if not isinstance(connections, dict):
|
|
310
|
+
# Mock or invalid response - skip connection metrics
|
|
311
|
+
current_connections = None
|
|
312
|
+
available_connections = None
|
|
313
|
+
total_created = None
|
|
314
|
+
else:
|
|
315
|
+
# Get values, ensuring they're numeric (not MagicMocks)
|
|
316
|
+
current_raw = connections.get("current", 0)
|
|
317
|
+
available_raw = connections.get("available", 0)
|
|
318
|
+
total_raw = connections.get("totalCreated", 0)
|
|
319
|
+
|
|
320
|
+
# Only use if actually numeric
|
|
321
|
+
current_connections = (
|
|
322
|
+
int(current_raw) if isinstance(current_raw, int | float) else None
|
|
323
|
+
)
|
|
324
|
+
available_connections = (
|
|
325
|
+
int(available_raw) if isinstance(available_raw, int | float) else None
|
|
326
|
+
)
|
|
327
|
+
total_created = int(total_raw) if isinstance(total_raw, int | float) else None
|
|
306
328
|
except (
|
|
307
329
|
OperationFailure,
|
|
308
330
|
ConnectionFailure,
|
|
@@ -330,7 +352,13 @@ async def _get_client_pool_metrics(client: AsyncIOMotorClient) -> Dict[str, Any]
|
|
|
330
352
|
metrics["total_connections_created"] = total_created
|
|
331
353
|
|
|
332
354
|
# Calculate pool usage if we have max_pool_size and current connections
|
|
333
|
-
|
|
355
|
+
# Ensure both are numeric (not MagicMock or other types)
|
|
356
|
+
if (
|
|
357
|
+
max_pool_size
|
|
358
|
+
and current_connections is not None
|
|
359
|
+
and isinstance(max_pool_size, int | float)
|
|
360
|
+
and isinstance(current_connections, int | float)
|
|
361
|
+
):
|
|
334
362
|
usage_percent = (current_connections / max_pool_size) * 100
|
|
335
363
|
metrics["pool_usage_percent"] = round(usage_percent, 2)
|
|
336
364
|
metrics["active_connections"] = current_connections # Alias for compatibility
|
|
@@ -14,7 +14,7 @@ Security Features:
|
|
|
14
14
|
|
|
15
15
|
import logging
|
|
16
16
|
import re
|
|
17
|
-
from typing import Any
|
|
17
|
+
from typing import Any
|
|
18
18
|
|
|
19
19
|
from ..constants import (
|
|
20
20
|
DANGEROUS_OPERATORS,
|
|
@@ -46,7 +46,7 @@ class QueryValidator:
|
|
|
46
46
|
max_pipeline_stages: int = MAX_PIPELINE_STAGES,
|
|
47
47
|
max_regex_length: int = MAX_REGEX_LENGTH,
|
|
48
48
|
max_regex_complexity: int = MAX_REGEX_COMPLEXITY,
|
|
49
|
-
dangerous_operators:
|
|
49
|
+
dangerous_operators: set[str] | None = None,
|
|
50
50
|
):
|
|
51
51
|
"""
|
|
52
52
|
Initialize the query validator.
|
|
@@ -80,7 +80,7 @@ class QueryValidator:
|
|
|
80
80
|
else DANGEROUS_OPERATORS
|
|
81
81
|
)
|
|
82
82
|
|
|
83
|
-
def validate_filter(self, filter:
|
|
83
|
+
def validate_filter(self, filter: dict[str, Any] | None, path: str = "") -> None:
|
|
84
84
|
"""
|
|
85
85
|
Validate a MongoDB query filter.
|
|
86
86
|
|
|
@@ -105,7 +105,7 @@ class QueryValidator:
|
|
|
105
105
|
self._check_dangerous_operators(filter, path)
|
|
106
106
|
self._check_query_depth(filter, path, depth=0)
|
|
107
107
|
|
|
108
|
-
def validate_pipeline(self, pipeline:
|
|
108
|
+
def validate_pipeline(self, pipeline: list[dict[str, Any]]) -> None:
|
|
109
109
|
"""
|
|
110
110
|
Validate an aggregation pipeline.
|
|
111
111
|
|
|
@@ -201,7 +201,7 @@ class QueryValidator:
|
|
|
201
201
|
path=path,
|
|
202
202
|
) from e
|
|
203
203
|
|
|
204
|
-
def validate_sort(self, sort:
|
|
204
|
+
def validate_sort(self, sort: Any | None) -> None:
|
|
205
205
|
"""
|
|
206
206
|
Validate a sort specification.
|
|
207
207
|
|
|
@@ -228,7 +228,7 @@ class QueryValidator:
|
|
|
228
228
|
)
|
|
229
229
|
|
|
230
230
|
def _check_dangerous_operators(
|
|
231
|
-
self, query:
|
|
231
|
+
self, query: dict[str, Any], path: str = "", depth: int = 0
|
|
232
232
|
) -> None:
|
|
233
233
|
"""
|
|
234
234
|
Recursively check for dangerous operators in a query.
|
|
@@ -287,7 +287,7 @@ class QueryValidator:
|
|
|
287
287
|
# Direct $regex value (less common but possible)
|
|
288
288
|
self.validate_regex(value, current_path)
|
|
289
289
|
|
|
290
|
-
def _check_query_depth(self, query:
|
|
290
|
+
def _check_query_depth(self, query: dict[str, Any], path: str = "", depth: int = 0) -> None:
|
|
291
291
|
"""
|
|
292
292
|
Check query nesting depth.
|
|
293
293
|
|
|
@@ -348,7 +348,7 @@ class QueryValidator:
|
|
|
348
348
|
|
|
349
349
|
return complexity
|
|
350
350
|
|
|
351
|
-
def _extract_sort_fields(self, sort: Any) ->
|
|
351
|
+
def _extract_sort_fields(self, sort: Any) -> list[str]:
|
|
352
352
|
"""
|
|
353
353
|
Extract field names from a sort specification.
|
|
354
354
|
|
|
@@ -12,7 +12,7 @@ Features:
|
|
|
12
12
|
"""
|
|
13
13
|
|
|
14
14
|
import logging
|
|
15
|
-
from typing import Any
|
|
15
|
+
from typing import Any
|
|
16
16
|
|
|
17
17
|
from bson import encode as bson_encode
|
|
18
18
|
from bson.errors import InvalidDocument
|
|
@@ -65,8 +65,8 @@ class ResourceLimiter:
|
|
|
65
65
|
self.max_document_size = max_document_size
|
|
66
66
|
|
|
67
67
|
def enforce_query_timeout(
|
|
68
|
-
self, kwargs:
|
|
69
|
-
) ->
|
|
68
|
+
self, kwargs: dict[str, Any], default_timeout: int | None = None
|
|
69
|
+
) -> dict[str, Any]:
|
|
70
70
|
"""
|
|
71
71
|
Enforce query timeout by adding maxTimeMS if not present.
|
|
72
72
|
|
|
@@ -97,7 +97,7 @@ class ResourceLimiter:
|
|
|
97
97
|
|
|
98
98
|
return kwargs
|
|
99
99
|
|
|
100
|
-
def enforce_result_limit(self, limit:
|
|
100
|
+
def enforce_result_limit(self, limit: int | None, max_limit: int | None = None) -> int:
|
|
101
101
|
"""
|
|
102
102
|
Enforce maximum result limit.
|
|
103
103
|
|
|
@@ -122,7 +122,7 @@ class ResourceLimiter:
|
|
|
122
122
|
|
|
123
123
|
return limit
|
|
124
124
|
|
|
125
|
-
def enforce_batch_size(self, batch_size:
|
|
125
|
+
def enforce_batch_size(self, batch_size: int | None, max_batch: int | None = None) -> int:
|
|
126
126
|
"""
|
|
127
127
|
Enforce maximum batch size for cursor operations.
|
|
128
128
|
|
|
@@ -147,7 +147,7 @@ class ResourceLimiter:
|
|
|
147
147
|
|
|
148
148
|
return batch_size
|
|
149
149
|
|
|
150
|
-
def validate_document_size(self, document:
|
|
150
|
+
def validate_document_size(self, document: dict[str, Any]) -> None:
|
|
151
151
|
"""
|
|
152
152
|
Validate that a document doesn't exceed size limits.
|
|
153
153
|
|
|
@@ -180,7 +180,7 @@ class ResourceLimiter:
|
|
|
180
180
|
# MongoDB will catch this anyway during actual insert
|
|
181
181
|
logger.warning(f"Could not encode document as BSON for size validation: {e}")
|
|
182
182
|
|
|
183
|
-
def validate_documents_size(self, documents: list[
|
|
183
|
+
def validate_documents_size(self, documents: list[dict[str, Any]]) -> None:
|
|
184
184
|
"""
|
|
185
185
|
Validate that multiple documents don't exceed size limits.
|
|
186
186
|
|