mdb-engine 0.2.1__py3-none-any.whl → 0.2.4__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 (70) hide show
  1. mdb_engine/__init__.py +7 -1
  2. mdb_engine/auth/README.md +6 -0
  3. mdb_engine/auth/audit.py +40 -40
  4. mdb_engine/auth/base.py +3 -3
  5. mdb_engine/auth/casbin_factory.py +6 -6
  6. mdb_engine/auth/config_defaults.py +5 -5
  7. mdb_engine/auth/config_helpers.py +12 -12
  8. mdb_engine/auth/cookie_utils.py +9 -9
  9. mdb_engine/auth/csrf.py +9 -8
  10. mdb_engine/auth/decorators.py +7 -6
  11. mdb_engine/auth/dependencies.py +22 -21
  12. mdb_engine/auth/integration.py +9 -9
  13. mdb_engine/auth/jwt.py +9 -9
  14. mdb_engine/auth/middleware.py +4 -3
  15. mdb_engine/auth/oso_factory.py +6 -6
  16. mdb_engine/auth/provider.py +4 -4
  17. mdb_engine/auth/rate_limiter.py +12 -11
  18. mdb_engine/auth/restrictions.py +16 -15
  19. mdb_engine/auth/session_manager.py +11 -13
  20. mdb_engine/auth/shared_middleware.py +344 -132
  21. mdb_engine/auth/shared_users.py +20 -20
  22. mdb_engine/auth/token_lifecycle.py +10 -12
  23. mdb_engine/auth/token_store.py +4 -5
  24. mdb_engine/auth/users.py +51 -52
  25. mdb_engine/auth/utils.py +29 -33
  26. mdb_engine/cli/commands/generate.py +6 -6
  27. mdb_engine/cli/utils.py +4 -4
  28. mdb_engine/config.py +6 -7
  29. mdb_engine/core/app_registration.py +12 -12
  30. mdb_engine/core/app_secrets.py +1 -2
  31. mdb_engine/core/connection.py +3 -4
  32. mdb_engine/core/encryption.py +1 -2
  33. mdb_engine/core/engine.py +43 -44
  34. mdb_engine/core/manifest.py +80 -58
  35. mdb_engine/core/ray_integration.py +10 -9
  36. mdb_engine/core/seeding.py +3 -3
  37. mdb_engine/core/service_initialization.py +10 -9
  38. mdb_engine/core/types.py +40 -40
  39. mdb_engine/database/abstraction.py +15 -16
  40. mdb_engine/database/connection.py +40 -12
  41. mdb_engine/database/query_validator.py +8 -8
  42. mdb_engine/database/resource_limiter.py +7 -7
  43. mdb_engine/database/scoped_wrapper.py +51 -58
  44. mdb_engine/dependencies.py +14 -13
  45. mdb_engine/di/container.py +12 -13
  46. mdb_engine/di/providers.py +14 -13
  47. mdb_engine/di/scopes.py +5 -5
  48. mdb_engine/embeddings/dependencies.py +2 -2
  49. mdb_engine/embeddings/service.py +67 -50
  50. mdb_engine/exceptions.py +20 -20
  51. mdb_engine/indexes/helpers.py +11 -11
  52. mdb_engine/indexes/manager.py +9 -9
  53. mdb_engine/memory/README.md +93 -2
  54. mdb_engine/memory/service.py +361 -1109
  55. mdb_engine/observability/health.py +10 -9
  56. mdb_engine/observability/logging.py +10 -10
  57. mdb_engine/observability/metrics.py +8 -7
  58. mdb_engine/repositories/base.py +25 -25
  59. mdb_engine/repositories/mongo.py +17 -17
  60. mdb_engine/repositories/unit_of_work.py +6 -6
  61. mdb_engine/routing/websockets.py +19 -18
  62. mdb_engine/utils/__init__.py +3 -1
  63. mdb_engine/utils/mongo.py +117 -0
  64. {mdb_engine-0.2.1.dist-info → mdb_engine-0.2.4.dist-info}/METADATA +88 -13
  65. mdb_engine-0.2.4.dist-info/RECORD +97 -0
  66. {mdb_engine-0.2.1.dist-info → mdb_engine-0.2.4.dist-info}/WHEEL +1 -1
  67. mdb_engine-0.2.1.dist-info/RECORD +0 -96
  68. {mdb_engine-0.2.1.dist-info → mdb_engine-0.2.4.dist-info}/entry_points.txt +0 -0
  69. {mdb_engine-0.2.1.dist-info → mdb_engine-0.2.4.dist-info}/licenses/LICENSE +0 -0
  70. {mdb_engine-0.2.1.dist-info → mdb_engine-0.2.4.dist-info}/top_level.txt +0 -0
mdb_engine/core/types.py CHANGED
@@ -8,7 +8,7 @@ throughout the codebase.
8
8
  This module is part of MDB_ENGINE - MongoDB Engine.
9
9
  """
10
10
 
11
- from typing import TYPE_CHECKING, Any, Dict, List, Literal, Optional, TypedDict, Union
11
+ from typing import TYPE_CHECKING, Any, Literal, TypedDict
12
12
 
13
13
  if TYPE_CHECKING:
14
14
  from ..memory import Mem0MemoryService
@@ -41,20 +41,20 @@ class IndexDefinitionDict(TypedDict, total=False):
41
41
  "partial",
42
42
  "hybrid",
43
43
  ]
44
- keys: Union[Dict[str, Union[int, str]], List[List[Union[str, int]]]]
44
+ keys: dict[str, int | str] | list[list[str | int]]
45
45
  unique: bool
46
46
  sparse: bool
47
47
  background: bool
48
48
  expireAfterSeconds: int
49
- partialFilterExpression: Dict[str, Any]
50
- weights: Dict[str, int] # For text indexes
49
+ partialFilterExpression: dict[str, Any]
50
+ weights: dict[str, int] # For text indexes
51
51
  default_language: str # For text indexes
52
52
  language_override: str # For text indexes
53
53
  textIndexVersion: int # For text indexes
54
54
  # Vector search specific
55
- fields: List[Dict[str, Any]] # For vectorSearch indexes
55
+ fields: list[dict[str, Any]] # For vectorSearch indexes
56
56
  # Search index specific
57
- mappings: Dict[str, Any] # For search indexes
57
+ mappings: dict[str, Any] # For search indexes
58
58
  # Geospatial specific
59
59
  bucketSize: float # For geoHaystack
60
60
  # TTL specific
@@ -79,12 +79,12 @@ class AuthAuthorizationDict(TypedDict, total=False):
79
79
  model: str
80
80
  policies_collection: str
81
81
  link_users_roles: bool
82
- default_roles: List[str]
82
+ default_roles: list[str]
83
83
  # OSO-specific
84
- api_key: Optional[str]
85
- url: Optional[str]
86
- initial_roles: List[Dict[str, str]]
87
- initial_policies: List[Dict[str, str]]
84
+ api_key: str | None
85
+ url: str | None
86
+ initial_roles: list[dict[str, str]]
87
+ initial_policies: list[dict[str, str]]
88
88
 
89
89
 
90
90
  class AuthPolicyDict(TypedDict, total=False):
@@ -93,12 +93,12 @@ class AuthPolicyDict(TypedDict, total=False):
93
93
  required: bool
94
94
  provider: Literal["casbin", "oso", "custom"]
95
95
  authorization: AuthAuthorizationDict
96
- allowed_roles: List[str]
97
- allowed_users: List[str]
98
- denied_users: List[str]
99
- required_permissions: List[str]
96
+ allowed_roles: list[str]
97
+ allowed_users: list[str]
98
+ denied_users: list[str]
99
+ required_permissions: list[str]
100
100
  custom_resource: str
101
- custom_actions: List[Literal["access", "read", "write", "admin"]]
101
+ custom_actions: list[Literal["access", "read", "write", "admin"]]
102
102
  allow_anonymous: bool
103
103
  owner_can_access: bool
104
104
 
@@ -111,7 +111,7 @@ class DemoUserDict(TypedDict, total=False):
111
111
  role: str
112
112
  auto_create: bool
113
113
  link_to_platform: bool
114
- extra_data: Dict[str, Any]
114
+ extra_data: dict[str, Any]
115
115
 
116
116
 
117
117
  class UsersConfigDict(TypedDict, total=False):
@@ -126,7 +126,7 @@ class UsersConfigDict(TypedDict, total=False):
126
126
  link_platform_users: bool
127
127
  anonymous_user_prefix: str
128
128
  user_id_field: str
129
- demo_users: List[DemoUserDict]
129
+ demo_users: list[DemoUserDict]
130
130
  auto_link_platform_demo: bool
131
131
  demo_user_seed_strategy: Literal["auto", "manual", "disabled"]
132
132
  enable_demo_user_access: bool
@@ -312,7 +312,7 @@ class MetricsConfigDict(TypedDict, total=False):
312
312
  enabled: bool
313
313
  collect_operation_metrics: bool
314
314
  collect_performance_metrics: bool
315
- custom_metrics: List[str]
315
+ custom_metrics: list[str]
316
316
 
317
317
 
318
318
  class LoggingConfigDict(TypedDict, total=False):
@@ -341,11 +341,11 @@ class CORSConfigDict(TypedDict, total=False):
341
341
  """CORS configuration."""
342
342
 
343
343
  enabled: bool
344
- allow_origins: List[str]
344
+ allow_origins: list[str]
345
345
  allow_credentials: bool
346
- allow_methods: List[Literal["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD", "*"]]
347
- allow_headers: List[str]
348
- expose_headers: List[str]
346
+ allow_methods: list[Literal["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD", "*"]]
347
+ allow_headers: list[str]
348
+ expose_headers: list[str]
349
349
  max_age: int
350
350
 
351
351
 
@@ -371,22 +371,22 @@ class ManifestDict(TypedDict, total=False):
371
371
  schema_version: str
372
372
  slug: str
373
373
  name: str
374
- description: Optional[str]
374
+ description: str | None
375
375
  status: Literal["active", "draft", "archived", "inactive"]
376
376
  auth_required: bool # Backward compatibility
377
- auth: Optional[AuthConfigDict]
378
- token_management: Optional[TokenManagementDict]
379
- data_scope: List[str]
380
- pip_deps: List[str]
381
- managed_indexes: Optional[ManagedIndexesDict]
382
- collection_settings: Optional[Dict[str, Dict[str, Any]]]
383
- websockets: Optional[WebSocketsDict]
384
- embedding_config: Optional[EmbeddingConfigDict]
385
- memory_config: Optional[MemoryConfigDict]
386
- cors: Optional[CORSConfigDict]
387
- observability: Optional[ObservabilityConfigDict]
388
- initial_data: Optional[InitialDataDict]
389
- developer_id: Optional[str]
377
+ auth: AuthConfigDict | None
378
+ token_management: TokenManagementDict | None
379
+ data_scope: list[str]
380
+ pip_deps: list[str]
381
+ managed_indexes: ManagedIndexesDict | None
382
+ collection_settings: dict[str, dict[str, Any]] | None
383
+ websockets: WebSocketsDict | None
384
+ embedding_config: EmbeddingConfigDict | None
385
+ memory_config: MemoryConfigDict | None
386
+ cors: CORSConfigDict | None
387
+ observability: ObservabilityConfigDict | None
388
+ initial_data: InitialDataDict | None
389
+ developer_id: str | None
390
390
 
391
391
 
392
392
  # ============================================================================
@@ -398,13 +398,13 @@ class HealthStatusDict(TypedDict, total=False):
398
398
  """Health status response."""
399
399
 
400
400
  status: Literal["healthy", "degraded", "unhealthy"]
401
- checks: Dict[str, Dict[str, Any]]
401
+ checks: dict[str, dict[str, Any]]
402
402
  timestamp: str
403
403
 
404
404
 
405
405
  class MetricsDict(TypedDict, total=False):
406
406
  """Metrics response."""
407
407
 
408
- operations: Dict[str, Dict[str, Any]]
409
- summary: Dict[str, Any]
408
+ operations: dict[str, dict[str, Any]]
409
+ summary: dict[str, Any]
410
410
  timestamp: str
@@ -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