mdb-engine 0.2.0__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.
Files changed (66) hide show
  1. mdb_engine/__init__.py +1 -1
  2. mdb_engine/auth/audit.py +40 -40
  3. mdb_engine/auth/base.py +3 -3
  4. mdb_engine/auth/casbin_factory.py +6 -6
  5. mdb_engine/auth/config_defaults.py +5 -5
  6. mdb_engine/auth/config_helpers.py +12 -12
  7. mdb_engine/auth/cookie_utils.py +9 -9
  8. mdb_engine/auth/csrf.py +9 -8
  9. mdb_engine/auth/decorators.py +7 -6
  10. mdb_engine/auth/dependencies.py +22 -21
  11. mdb_engine/auth/integration.py +9 -9
  12. mdb_engine/auth/jwt.py +9 -9
  13. mdb_engine/auth/middleware.py +4 -3
  14. mdb_engine/auth/oso_factory.py +6 -6
  15. mdb_engine/auth/provider.py +4 -4
  16. mdb_engine/auth/rate_limiter.py +12 -11
  17. mdb_engine/auth/restrictions.py +16 -15
  18. mdb_engine/auth/session_manager.py +11 -13
  19. mdb_engine/auth/shared_middleware.py +16 -15
  20. mdb_engine/auth/shared_users.py +20 -20
  21. mdb_engine/auth/token_lifecycle.py +10 -12
  22. mdb_engine/auth/token_store.py +4 -5
  23. mdb_engine/auth/users.py +51 -52
  24. mdb_engine/auth/utils.py +29 -33
  25. mdb_engine/cli/commands/generate.py +6 -6
  26. mdb_engine/cli/utils.py +4 -4
  27. mdb_engine/config.py +6 -7
  28. mdb_engine/core/app_registration.py +12 -12
  29. mdb_engine/core/app_secrets.py +1 -2
  30. mdb_engine/core/connection.py +3 -4
  31. mdb_engine/core/encryption.py +1 -2
  32. mdb_engine/core/engine.py +43 -44
  33. mdb_engine/core/manifest.py +59 -58
  34. mdb_engine/core/ray_integration.py +10 -9
  35. mdb_engine/core/seeding.py +3 -3
  36. mdb_engine/core/service_initialization.py +10 -9
  37. mdb_engine/core/types.py +40 -40
  38. mdb_engine/database/abstraction.py +15 -16
  39. mdb_engine/database/connection.py +40 -12
  40. mdb_engine/database/query_validator.py +8 -8
  41. mdb_engine/database/resource_limiter.py +7 -7
  42. mdb_engine/database/scoped_wrapper.py +51 -58
  43. mdb_engine/dependencies.py +14 -13
  44. mdb_engine/di/container.py +12 -13
  45. mdb_engine/di/providers.py +14 -13
  46. mdb_engine/di/scopes.py +5 -5
  47. mdb_engine/embeddings/dependencies.py +2 -2
  48. mdb_engine/embeddings/service.py +31 -43
  49. mdb_engine/exceptions.py +20 -20
  50. mdb_engine/indexes/helpers.py +11 -11
  51. mdb_engine/indexes/manager.py +9 -9
  52. mdb_engine/memory/service.py +30 -30
  53. mdb_engine/observability/health.py +10 -9
  54. mdb_engine/observability/logging.py +10 -10
  55. mdb_engine/observability/metrics.py +8 -7
  56. mdb_engine/repositories/base.py +25 -25
  57. mdb_engine/repositories/mongo.py +17 -17
  58. mdb_engine/repositories/unit_of_work.py +6 -6
  59. mdb_engine/routing/websockets.py +19 -18
  60. {mdb_engine-0.2.0.dist-info → mdb_engine-0.2.3.dist-info}/METADATA +8 -8
  61. mdb_engine-0.2.3.dist-info/RECORD +96 -0
  62. mdb_engine-0.2.0.dist-info/RECORD +0 -96
  63. {mdb_engine-0.2.0.dist-info → mdb_engine-0.2.3.dist-info}/WHEEL +0 -0
  64. {mdb_engine-0.2.0.dist-info → mdb_engine-0.2.3.dist-info}/entry_points.txt +0 -0
  65. {mdb_engine-0.2.0.dist-info → mdb_engine-0.2.3.dist-info}/licenses/LICENSE +0 -0
  66. {mdb_engine-0.2.0.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 typing import Any, Callable, Dict, List, Optional
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: Optional[Dict[str, Any]] = None, *args, **kwargs
92
- ) -> Optional[Dict[str, Any]]:
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: Optional[Dict[str, Any]] = None, *args, **kwargs) -> AsyncIOMotorCursor:
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: Dict[str, Any], *args, **kwargs) -> InsertOneResult:
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: List[Dict[str, Any]], *args, **kwargs
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: Dict[str, Any], update: Dict[str, Any], *args, **kwargs
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: Dict[str, Any], update: Dict[str, Any], *args, **kwargs
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: Dict[str, Any], replacement: Dict[str, Any], *args, **kwargs
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: Dict[str, Any], *args, **kwargs) -> DeleteResult:
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: Optional[Dict[str, Any]] = None, *args, **kwargs
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: List[Dict[str, Any]], *args, **kwargs) -> AsyncIOMotorCursor:
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: Dict[str, Collection] = {}
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, Dict, Optional
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: Optional[AsyncIOMotorClient] = None
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: Optional[int] = None,
48
- min_pool_size: Optional[int] = None,
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: Optional[AsyncIOMotorClient] = None,
207
- ) -> Dict[str, Any]:
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) -> Dict[str, Any]:
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
- connections = server_status.get("connections", {})
303
- current_connections = connections.get("current", 0)
304
- available_connections = connections.get("available", 0)
305
- total_created = connections.get("totalCreated", 0)
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
- if max_pool_size and current_connections is not None:
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, Dict, List, Optional, Set
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: Optional[Set[str]] = None,
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: Optional[Dict[str, Any]], path: str = "") -> None:
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: List[Dict[str, Any]]) -> None:
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: Optional[Any]) -> None:
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: Dict[str, Any], path: str = "", depth: int = 0
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: Dict[str, Any], path: str = "", depth: int = 0) -> None:
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) -> List[str]:
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, Dict, Optional
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: Dict[str, Any], default_timeout: Optional[int] = None
69
- ) -> Dict[str, Any]:
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: Optional[int], max_limit: Optional[int] = None) -> int:
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: Optional[int], max_batch: Optional[int] = None) -> int:
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: Dict[str, Any]) -> None:
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[Dict[str, Any]]) -> None:
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