mdb-engine 0.1.6__py3-none-any.whl → 0.4.12__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 (92) hide show
  1. mdb_engine/__init__.py +116 -11
  2. mdb_engine/auth/ARCHITECTURE.md +112 -0
  3. mdb_engine/auth/README.md +654 -11
  4. mdb_engine/auth/__init__.py +136 -29
  5. mdb_engine/auth/audit.py +592 -0
  6. mdb_engine/auth/base.py +252 -0
  7. mdb_engine/auth/casbin_factory.py +265 -70
  8. mdb_engine/auth/config_defaults.py +5 -5
  9. mdb_engine/auth/config_helpers.py +19 -18
  10. mdb_engine/auth/cookie_utils.py +12 -16
  11. mdb_engine/auth/csrf.py +483 -0
  12. mdb_engine/auth/decorators.py +10 -16
  13. mdb_engine/auth/dependencies.py +69 -71
  14. mdb_engine/auth/helpers.py +3 -3
  15. mdb_engine/auth/integration.py +61 -88
  16. mdb_engine/auth/jwt.py +11 -15
  17. mdb_engine/auth/middleware.py +79 -35
  18. mdb_engine/auth/oso_factory.py +21 -41
  19. mdb_engine/auth/provider.py +270 -171
  20. mdb_engine/auth/rate_limiter.py +505 -0
  21. mdb_engine/auth/restrictions.py +21 -36
  22. mdb_engine/auth/session_manager.py +24 -41
  23. mdb_engine/auth/shared_middleware.py +977 -0
  24. mdb_engine/auth/shared_users.py +775 -0
  25. mdb_engine/auth/token_lifecycle.py +10 -12
  26. mdb_engine/auth/token_store.py +17 -32
  27. mdb_engine/auth/users.py +99 -159
  28. mdb_engine/auth/utils.py +236 -42
  29. mdb_engine/cli/commands/generate.py +546 -10
  30. mdb_engine/cli/commands/validate.py +3 -7
  31. mdb_engine/cli/utils.py +7 -7
  32. mdb_engine/config.py +13 -28
  33. mdb_engine/constants.py +65 -0
  34. mdb_engine/core/README.md +117 -6
  35. mdb_engine/core/__init__.py +39 -7
  36. mdb_engine/core/app_registration.py +31 -50
  37. mdb_engine/core/app_secrets.py +289 -0
  38. mdb_engine/core/connection.py +20 -12
  39. mdb_engine/core/encryption.py +222 -0
  40. mdb_engine/core/engine.py +2862 -115
  41. mdb_engine/core/index_management.py +12 -16
  42. mdb_engine/core/manifest.py +628 -204
  43. mdb_engine/core/ray_integration.py +436 -0
  44. mdb_engine/core/seeding.py +13 -21
  45. mdb_engine/core/service_initialization.py +20 -30
  46. mdb_engine/core/types.py +40 -43
  47. mdb_engine/database/README.md +140 -17
  48. mdb_engine/database/__init__.py +17 -6
  49. mdb_engine/database/abstraction.py +37 -50
  50. mdb_engine/database/connection.py +51 -30
  51. mdb_engine/database/query_validator.py +367 -0
  52. mdb_engine/database/resource_limiter.py +204 -0
  53. mdb_engine/database/scoped_wrapper.py +747 -237
  54. mdb_engine/dependencies.py +427 -0
  55. mdb_engine/di/__init__.py +34 -0
  56. mdb_engine/di/container.py +247 -0
  57. mdb_engine/di/providers.py +206 -0
  58. mdb_engine/di/scopes.py +139 -0
  59. mdb_engine/embeddings/README.md +54 -24
  60. mdb_engine/embeddings/__init__.py +31 -24
  61. mdb_engine/embeddings/dependencies.py +38 -155
  62. mdb_engine/embeddings/service.py +78 -75
  63. mdb_engine/exceptions.py +104 -12
  64. mdb_engine/indexes/README.md +30 -13
  65. mdb_engine/indexes/__init__.py +1 -0
  66. mdb_engine/indexes/helpers.py +11 -11
  67. mdb_engine/indexes/manager.py +59 -123
  68. mdb_engine/memory/README.md +95 -4
  69. mdb_engine/memory/__init__.py +1 -2
  70. mdb_engine/memory/service.py +363 -1168
  71. mdb_engine/observability/README.md +4 -2
  72. mdb_engine/observability/__init__.py +26 -9
  73. mdb_engine/observability/health.py +17 -17
  74. mdb_engine/observability/logging.py +10 -10
  75. mdb_engine/observability/metrics.py +40 -19
  76. mdb_engine/repositories/__init__.py +34 -0
  77. mdb_engine/repositories/base.py +325 -0
  78. mdb_engine/repositories/mongo.py +233 -0
  79. mdb_engine/repositories/unit_of_work.py +166 -0
  80. mdb_engine/routing/README.md +1 -1
  81. mdb_engine/routing/__init__.py +1 -3
  82. mdb_engine/routing/websockets.py +41 -75
  83. mdb_engine/utils/__init__.py +3 -1
  84. mdb_engine/utils/mongo.py +117 -0
  85. mdb_engine-0.4.12.dist-info/METADATA +492 -0
  86. mdb_engine-0.4.12.dist-info/RECORD +97 -0
  87. {mdb_engine-0.1.6.dist-info → mdb_engine-0.4.12.dist-info}/WHEEL +1 -1
  88. mdb_engine-0.1.6.dist-info/METADATA +0 -213
  89. mdb_engine-0.1.6.dist-info/RECORD +0 -75
  90. {mdb_engine-0.1.6.dist-info → mdb_engine-0.4.12.dist-info}/entry_points.txt +0 -0
  91. {mdb_engine-0.1.6.dist-info → mdb_engine-0.4.12.dist-info}/licenses/LICENSE +0 -0
  92. {mdb_engine-0.1.6.dist-info → mdb_engine-0.4.12.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,436 @@
1
+ """
2
+ Optional Ray Integration for MDB Engine.
3
+
4
+ Provides optional Ray support for distributed processing with app isolation.
5
+ This module gracefully degrades if Ray is not installed.
6
+
7
+ Usage:
8
+ # Check if Ray is available
9
+ from mdb_engine.core.ray_integration import RAY_AVAILABLE
10
+
11
+ if RAY_AVAILABLE:
12
+ from mdb_engine.core.ray_integration import (
13
+ AppRayActor,
14
+ get_ray_actor_handle,
15
+ ray_actor_decorator,
16
+ )
17
+
18
+ # Or use the helper that handles availability
19
+ actor = await get_ray_actor_handle("my_app")
20
+ if actor:
21
+ result = await actor.process.remote(data)
22
+
23
+ This module is part of MDB_ENGINE - MongoDB Engine.
24
+ """
25
+
26
+ import logging
27
+ import os
28
+ from collections.abc import Callable
29
+ from typing import TYPE_CHECKING, Any, TypeVar
30
+
31
+ if TYPE_CHECKING:
32
+ pass
33
+
34
+ logger = logging.getLogger(__name__)
35
+
36
+ # --- Ray Availability Check ---
37
+ # This flag allows code to check Ray availability without try/except
38
+ RAY_AVAILABLE = False
39
+ ray = None
40
+
41
+ try:
42
+ import ray as _ray
43
+
44
+ ray = _ray
45
+ RAY_AVAILABLE = True
46
+ logger.debug("Ray is available")
47
+ except ImportError:
48
+ logger.debug("Ray not installed - Ray features will be disabled")
49
+
50
+
51
+ # Type variable for decorator
52
+ T = TypeVar("T")
53
+
54
+
55
+ class AppRayActor:
56
+ """
57
+ Base Ray actor class for app-specific isolated environments.
58
+
59
+ Each app can have its own Ray actor with:
60
+ - Isolated MongoDB connection
61
+ - App-specific configuration
62
+ - Isolated state
63
+
64
+ Subclass this to create app-specific actors:
65
+
66
+ @ray.remote
67
+ class MyAppActor(AppRayActor):
68
+ async def process(self, data):
69
+ db = await self.get_app_db()
70
+ # Process with isolated DB access
71
+ return result
72
+
73
+ Attributes:
74
+ app_slug: Application identifier
75
+ mongo_uri: MongoDB connection URI
76
+ db_name: Database name
77
+ use_in_memory_fallback: Whether to use in-memory DB if MongoDB unavailable
78
+ """
79
+
80
+ def __init__(
81
+ self,
82
+ app_slug: str,
83
+ mongo_uri: str,
84
+ db_name: str,
85
+ use_in_memory_fallback: bool = False,
86
+ ) -> None:
87
+ """
88
+ Initialize app-specific Ray actor.
89
+
90
+ Args:
91
+ app_slug: Application identifier
92
+ mongo_uri: MongoDB connection URI
93
+ db_name: Database name
94
+ use_in_memory_fallback: Use in-memory DB if MongoDB unavailable
95
+ """
96
+ self.app_slug = app_slug
97
+ self.mongo_uri = mongo_uri
98
+ self.db_name = db_name
99
+ self.use_in_memory_fallback = use_in_memory_fallback
100
+
101
+ # Lazy initialization for engine
102
+ self._engine = None
103
+ self._initialized = False
104
+
105
+ logger.info(f"AppRayActor created for '{app_slug}' " f"(fallback={use_in_memory_fallback})")
106
+
107
+ async def _ensure_initialized(self) -> None:
108
+ """
109
+ Ensure the actor is initialized with database connection.
110
+
111
+ Called lazily on first database access.
112
+ """
113
+ if self._initialized:
114
+ return
115
+
116
+ try:
117
+ from .engine import MongoDBEngine
118
+
119
+ self._engine = MongoDBEngine(
120
+ mongo_uri=self.mongo_uri,
121
+ db_name=self.db_name,
122
+ )
123
+ await self._engine.initialize()
124
+ self._initialized = True
125
+ logger.info(f"AppRayActor engine initialized for '{self.app_slug}'")
126
+
127
+ except (ConnectionError, ValueError, RuntimeError, OSError) as e:
128
+ logger.exception(f"Error initializing engine in Ray actor for '{self.app_slug}': {e}")
129
+ if self.use_in_memory_fallback:
130
+ logger.warning(f"Using in-memory fallback for '{self.app_slug}'")
131
+ self._engine = None
132
+ self._initialized = True
133
+ else:
134
+ raise
135
+
136
+ async def get_app_db(self, app_token: str | None = None) -> Any:
137
+ """
138
+ Get scoped database for this app.
139
+
140
+ Args:
141
+ app_token: Optional app token for authentication
142
+
143
+ Returns:
144
+ ScopedMongoWrapper for this app
145
+
146
+ Raises:
147
+ RuntimeError: If engine not available and no fallback
148
+ """
149
+ await self._ensure_initialized()
150
+
151
+ if not self._engine:
152
+ if self.use_in_memory_fallback:
153
+ raise RuntimeError(f"In-memory fallback not yet implemented for '{self.app_slug}'")
154
+ raise RuntimeError(f"Engine not available for '{self.app_slug}'")
155
+
156
+ # Try to get app token from environment if not provided
157
+ if not app_token:
158
+ env_var_name = f"{self.app_slug.upper()}_SECRET"
159
+ app_token = os.getenv(env_var_name)
160
+
161
+ return self._engine.get_scoped_db(self.app_slug, app_token=app_token)
162
+
163
+ def get_app_slug(self) -> str:
164
+ """Get the app slug."""
165
+ return self.app_slug
166
+
167
+ async def health_check(self) -> dict:
168
+ """
169
+ Health check for the actor.
170
+
171
+ Returns:
172
+ Dict with health status information
173
+ """
174
+ await self._ensure_initialized()
175
+
176
+ return {
177
+ "app_slug": self.app_slug,
178
+ "initialized": self._initialized,
179
+ "engine_available": self._engine is not None,
180
+ "status": "healthy" if self._initialized else "initializing",
181
+ }
182
+
183
+ async def shutdown(self) -> None:
184
+ """Shutdown the actor and release resources."""
185
+ if self._engine:
186
+ await self._engine.shutdown()
187
+ self._engine = None
188
+ self._initialized = False
189
+ logger.info(f"AppRayActor shutdown for '{self.app_slug}'")
190
+
191
+
192
+ async def get_ray_actor_handle(
193
+ app_slug: str,
194
+ namespace: str = "modular_labs",
195
+ mongo_uri: str | None = None,
196
+ db_name: str | None = None,
197
+ create_if_missing: bool = True,
198
+ actor_class: type | None = None,
199
+ ) -> Any | None:
200
+ """
201
+ Get or create a Ray actor handle for an app.
202
+
203
+ This function:
204
+ 1. Checks if Ray is available
205
+ 2. Initializes Ray if not already done
206
+ 3. Tries to get existing actor by name
207
+ 4. Creates new actor if missing and create_if_missing=True
208
+
209
+ Args:
210
+ app_slug: Application identifier
211
+ namespace: Ray namespace (default: "modular_labs")
212
+ mongo_uri: MongoDB URI (default: from MONGODB_URI env var)
213
+ db_name: Database name (default: from MONGODB_DB env var)
214
+ create_if_missing: Create actor if it doesn't exist (default: True)
215
+ actor_class: Custom actor class to use (default: AppRayActor)
216
+
217
+ Returns:
218
+ Ray actor handle, or None if Ray unavailable or actor not found
219
+
220
+ Example:
221
+ # Get or create actor
222
+ actor = await get_ray_actor_handle("my_app")
223
+ if actor:
224
+ result = await actor.process.remote(data)
225
+
226
+ # Get existing actor only
227
+ actor = await get_ray_actor_handle("my_app", create_if_missing=False)
228
+ """
229
+ if not RAY_AVAILABLE:
230
+ logger.debug(f"Ray not available - cannot get actor for '{app_slug}'")
231
+ return None
232
+
233
+ actor_name = f"{app_slug}-actor"
234
+
235
+ # Ensure Ray is initialized
236
+ if not ray.is_initialized():
237
+ ray_address = os.getenv("RAY_ADDRESS")
238
+ if ray_address:
239
+ try:
240
+ ray.init(address=ray_address, namespace=namespace)
241
+ logger.info(f"Ray initialized with address: {ray_address}")
242
+ except (RuntimeError, ConnectionError) as e:
243
+ logger.exception(f"Failed to initialize Ray: {e}")
244
+ return None
245
+ else:
246
+ try:
247
+ # Initialize local Ray instance
248
+ ray.init(namespace=namespace, ignore_reinit_error=True)
249
+ logger.info("Ray initialized locally")
250
+ except RuntimeError as e:
251
+ logger.exception(f"Failed to initialize local Ray: {e}")
252
+ return None
253
+
254
+ # Try to get existing actor
255
+ try:
256
+ handle = ray.get_actor(actor_name, namespace=namespace)
257
+ logger.debug(f"Found existing Ray actor: {actor_name}")
258
+ return handle
259
+ except ValueError:
260
+ # Actor doesn't exist
261
+ if not create_if_missing:
262
+ logger.debug(f"Ray actor '{actor_name}' not found")
263
+ return None
264
+
265
+ # Create new actor
266
+ try:
267
+ # Use provided class or default to AppRayActor
268
+ cls = actor_class or AppRayActor
269
+
270
+ # Make it a Ray remote class if not already
271
+ if not hasattr(cls, "remote"):
272
+ cls = ray.remote(cls)
273
+
274
+ # Get connection parameters
275
+ uri = mongo_uri or os.getenv("MONGODB_URI", "mongodb://localhost:27017")
276
+ db = db_name or os.getenv("MONGODB_DB", "mdb_runtime")
277
+
278
+ handle = cls.options(
279
+ name=actor_name,
280
+ namespace=namespace,
281
+ lifetime="detached",
282
+ get_if_exists=True,
283
+ ).remote(
284
+ app_slug=app_slug,
285
+ mongo_uri=uri,
286
+ db_name=db,
287
+ )
288
+
289
+ logger.info(f"Created Ray actor '{actor_name}' in namespace '{namespace}'")
290
+ return handle
291
+
292
+ except ray.exceptions.RayError as e:
293
+ logger.error(f"Error creating Ray actor for '{app_slug}': {e}", exc_info=True)
294
+ return None
295
+
296
+
297
+ def ray_actor_decorator(
298
+ app_slug: str | None = None,
299
+ namespace: str = "modular_labs",
300
+ isolated: bool = True,
301
+ lifetime: str = "detached",
302
+ max_restarts: int = -1,
303
+ ) -> Callable[[type], type]:
304
+ """
305
+ Decorator for creating Ray actors with app isolation.
306
+
307
+ This decorator:
308
+ 1. Converts a class to a Ray remote class
309
+ 2. Adds app-specific namespace (if isolated=True)
310
+ 3. Provides a convenient spawn() class method
311
+
312
+ Args:
313
+ app_slug: Application identifier. If None, derived from class name
314
+ namespace: Base Ray namespace (default: "modular_labs")
315
+ isolated: Use app-specific namespace for isolation (default: True)
316
+ lifetime: Actor lifetime ("detached" or "non_detached")
317
+ max_restarts: Max automatic restarts (-1 for unlimited)
318
+
319
+ Returns:
320
+ Decorated class
321
+
322
+ Example:
323
+ @ray_actor_decorator(app_slug="my_app", isolated=True)
324
+ class MyAppActor(AppRayActor):
325
+ async def process(self, data):
326
+ db = await self.get_app_db()
327
+ return await db.items.find_one({"id": data["id"]})
328
+
329
+ # Spawn the actor
330
+ actor = MyAppActor.spawn()
331
+ result = await actor.process.remote({"id": "123"})
332
+
333
+ Note:
334
+ If Ray is not available, returns the class unchanged (no-op decorator).
335
+ """
336
+ if not RAY_AVAILABLE:
337
+
338
+ def noop_decorator(cls: type) -> type:
339
+ logger.debug(f"Ray not available - {cls.__name__} not converted to Ray actor")
340
+ return cls
341
+
342
+ return noop_decorator
343
+
344
+ def decorator(cls: type) -> type:
345
+ # Determine app slug from class name if not provided
346
+ actor_app_slug = app_slug
347
+ if actor_app_slug is None:
348
+ # Convert class name to slug: MyAppActor -> my_app
349
+ name = cls.__name__
350
+ if name.endswith("Actor"):
351
+ name = name[:-5]
352
+ # Convert CamelCase to snake_case
353
+ import re
354
+
355
+ actor_app_slug = re.sub(r"(?<!^)(?=[A-Z])", "_", name).lower()
356
+
357
+ # Determine namespace
358
+ actor_namespace = f"{namespace}_{actor_app_slug}" if isolated else namespace
359
+
360
+ # Convert to Ray remote class
361
+ ray_cls = ray.remote(cls)
362
+
363
+ # Store metadata on the class
364
+ ray_cls._app_slug = actor_app_slug
365
+ ray_cls._namespace = actor_namespace
366
+ ray_cls._isolated = isolated
367
+
368
+ # Add spawn class method
369
+ @classmethod
370
+ def spawn(
371
+ cls,
372
+ *args,
373
+ mongo_uri: str | None = None,
374
+ db_name: str | None = None,
375
+ **kwargs,
376
+ ):
377
+ """
378
+ Spawn an instance of this actor.
379
+
380
+ Args:
381
+ *args: Positional arguments for actor __init__
382
+ mongo_uri: MongoDB URI (default: from env)
383
+ db_name: Database name (default: from env)
384
+ **kwargs: Keyword arguments for actor __init__
385
+
386
+ Returns:
387
+ Ray actor handle
388
+ """
389
+ actor_name = f"{actor_app_slug}-{cls.__name__.lower()}"
390
+
391
+ # Get connection parameters if not in kwargs
392
+ if "mongo_uri" not in kwargs:
393
+ kwargs["mongo_uri"] = mongo_uri or os.getenv(
394
+ "MONGODB_URI", "mongodb://localhost:27017"
395
+ )
396
+ if "db_name" not in kwargs:
397
+ kwargs["db_name"] = db_name or os.getenv("MONGODB_DB", "mdb_runtime")
398
+ if "app_slug" not in kwargs:
399
+ kwargs["app_slug"] = actor_app_slug
400
+
401
+ return cls.options(
402
+ name=actor_name,
403
+ namespace=actor_namespace,
404
+ lifetime=lifetime,
405
+ max_restarts=max_restarts,
406
+ get_if_exists=True,
407
+ ).remote(*args, **kwargs)
408
+
409
+ ray_cls.spawn = spawn
410
+
411
+ logger.debug(
412
+ f"Created Ray actor class '{cls.__name__}' "
413
+ f"(app_slug={actor_app_slug}, namespace={actor_namespace})"
414
+ )
415
+
416
+ return ray_cls
417
+
418
+ return decorator
419
+
420
+
421
+ # Convenience aliases
422
+ if RAY_AVAILABLE:
423
+ # Export ray module for convenience
424
+ get_actor = ray.get_actor
425
+ init_ray = ray.init
426
+ is_initialized = ray.is_initialized
427
+ else:
428
+ # Stub functions when Ray not available
429
+ def get_actor(*args, **kwargs):
430
+ raise RuntimeError("Ray is not available")
431
+
432
+ def init_ray(*args, **kwargs):
433
+ raise RuntimeError("Ray is not available")
434
+
435
+ def is_initialized():
436
+ return False
@@ -8,17 +8,20 @@ This module is part of MDB_ENGINE - MongoDB Engine.
8
8
 
9
9
  import logging
10
10
  from datetime import datetime
11
- from typing import Any, Dict, List
11
+ from typing import Any
12
12
 
13
- from pymongo.errors import (ConnectionFailure, OperationFailure,
14
- ServerSelectionTimeoutError)
13
+ from pymongo.errors import (
14
+ ConnectionFailure,
15
+ OperationFailure,
16
+ ServerSelectionTimeoutError,
17
+ )
15
18
 
16
19
  logger = logging.getLogger(__name__)
17
20
 
18
21
 
19
22
  async def seed_initial_data(
20
- db, app_slug: str, initial_data: Dict[str, List[Dict[str, Any]]]
21
- ) -> Dict[str, int]:
23
+ db, app_slug: str, initial_data: dict[str, list[dict[str, Any]]]
24
+ ) -> dict[str, int]:
22
25
  """
23
26
  Seed initial data into collections.
24
27
 
@@ -47,9 +50,7 @@ async def seed_initial_data(
47
50
  # Get existing seeding metadata
48
51
  seeding_metadata = await metadata_collection.find_one({"app_slug": app_slug})
49
52
  seeded_collections = (
50
- set(seeding_metadata.get("seeded_collections", []))
51
- if seeding_metadata
52
- else set()
53
+ set(seeding_metadata.get("seeded_collections", [])) if seeding_metadata else set()
53
54
  )
54
55
 
55
56
  for collection_name, documents in initial_data.items():
@@ -90,9 +91,7 @@ async def seed_initial_data(
90
91
  # Try to parse ISO format datetime strings
91
92
  try:
92
93
  # Check if it looks like a datetime string
93
- if "T" in value and (
94
- "Z" in value or "+" in value or "-" in value[-6:]
95
- ):
94
+ if "T" in value and ("Z" in value or "+" in value or "-" in value[-6:]):
96
95
  # Try parsing as ISO format
97
96
  from dateutil.parser import parse as parse_date
98
97
 
@@ -110,10 +109,7 @@ async def seed_initial_data(
110
109
  pass
111
110
 
112
111
  # Add created_at if not present and document doesn't have timestamp fields
113
- if (
114
- "created_at" not in prepared_doc
115
- and "date_created" not in prepared_doc
116
- ):
112
+ if "created_at" not in prepared_doc and "date_created" not in prepared_doc:
117
113
  prepared_doc["created_at"] = datetime.utcnow()
118
114
 
119
115
  prepared_docs.append(prepared_doc)
@@ -144,9 +140,7 @@ async def seed_initial_data(
144
140
  Exception,
145
141
  ) as e:
146
142
  # Type 2: Recoverable - log error and continue with other collections
147
- logger.exception(
148
- f"Failed to seed collection '{collection_name}' for {app_slug}: {e}"
149
- )
143
+ logger.exception(f"Failed to seed collection '{collection_name}' for {app_slug}: {e}")
150
144
  results[collection_name] = 0
151
145
 
152
146
  # Update seeding metadata
@@ -172,8 +166,6 @@ async def seed_initial_data(
172
166
  TypeError,
173
167
  KeyError,
174
168
  ) as e:
175
- logger.warning(
176
- f"Failed to update seeding metadata for {app_slug}: {e}", exc_info=True
177
- )
169
+ logger.warning(f"Failed to update seeding metadata for {app_slug}: {e}", exc_info=True)
178
170
 
179
171
  return results
@@ -11,10 +11,14 @@ This module is part of MDB_ENGINE - MongoDB Engine.
11
11
  """
12
12
 
13
13
  import logging
14
- from typing import Any, Callable, Dict, List, Optional
14
+ from collections.abc import Callable
15
+ from typing import Any
15
16
 
16
- from pymongo.errors import (ConnectionFailure, OperationFailure,
17
- ServerSelectionTimeoutError)
17
+ from pymongo.errors import (
18
+ ConnectionFailure,
19
+ OperationFailure,
20
+ ServerSelectionTimeoutError,
21
+ )
18
22
 
19
23
  from ..database import ScopedMongoWrapper
20
24
  from ..observability import get_logger as get_contextual_logger
@@ -47,12 +51,10 @@ class ServiceInitializer:
47
51
  self.mongo_uri = mongo_uri
48
52
  self.db_name = db_name
49
53
  self.get_scoped_db_fn = get_scoped_db_fn
50
- self._memory_services: Dict[str, Any] = {}
51
- self._websocket_configs: Dict[str, Dict[str, Any]] = {}
54
+ self._memory_services: dict[str, Any] = {}
55
+ self._websocket_configs: dict[str, dict[str, Any]] = {}
52
56
 
53
- async def initialize_memory_service(
54
- self, slug: str, memory_config: Dict[str, Any]
55
- ) -> None:
57
+ async def initialize_memory_service(self, slug: str, memory_config: dict[str, Any]) -> None:
56
58
  """
57
59
  Initialize Mem0 memory service for an app.
58
60
 
@@ -79,9 +81,7 @@ class ServiceInitializer:
79
81
  f"Initializing Mem0 memory service for app '{slug}'",
80
82
  extra={
81
83
  "app_slug": slug,
82
- "collection_name": memory_config.get(
83
- "collection_name", f"{slug}_memories"
84
- ),
84
+ "collection_name": memory_config.get("collection_name", f"{slug}_memories"),
85
85
  "enable_graph": memory_config.get("enable_graph", False),
86
86
  "embedding_model_dims": memory_config.get("embedding_model_dims", 1536),
87
87
  "infer": memory_config.get("infer", True),
@@ -152,9 +152,7 @@ class ServiceInitializer:
152
152
  exc_info=True,
153
153
  )
154
154
 
155
- async def register_websockets(
156
- self, slug: str, websockets_config: Dict[str, Any]
157
- ) -> None:
155
+ async def register_websockets(self, slug: str, websockets_config: dict[str, Any]) -> None:
158
156
  """
159
157
  Register WebSocket endpoints for an app.
160
158
 
@@ -190,9 +188,7 @@ class ServiceInitializer:
190
188
  try:
191
189
  await get_websocket_manager(slug)
192
190
  except (ImportError, AttributeError, RuntimeError) as e:
193
- contextual_logger.warning(
194
- f"Could not initialize WebSocket manager for {slug}: {e}"
195
- )
191
+ contextual_logger.warning(f"Could not initialize WebSocket manager for {slug}: {e}")
196
192
  continue
197
193
  contextual_logger.debug(
198
194
  f"Configured WebSocket endpoint '{endpoint_name}' at path '{path}'",
@@ -200,7 +196,7 @@ class ServiceInitializer:
200
196
  )
201
197
 
202
198
  async def seed_initial_data(
203
- self, slug: str, initial_data: Dict[str, List[Dict[str, Any]]]
199
+ self, slug: str, initial_data: dict[str, list[dict[str, Any]]]
204
200
  ) -> None:
205
201
  """
206
202
  Seed initial data into collections for an app.
@@ -221,9 +217,7 @@ class ServiceInitializer:
221
217
  f"Seeded initial data for app '{slug}'",
222
218
  extra={
223
219
  "app_slug": slug,
224
- "collections_seeded": len(
225
- [c for c, count in results.items() if count > 0]
226
- ),
220
+ "collections_seeded": len([c for c, count in results.items() if count > 0]),
227
221
  "total_documents": total_inserted,
228
222
  },
229
223
  )
@@ -247,7 +241,7 @@ class ServiceInitializer:
247
241
  )
248
242
 
249
243
  async def setup_observability(
250
- self, slug: str, manifest: Dict[str, Any], observability_config: Dict[str, Any]
244
+ self, slug: str, manifest: dict[str, Any], observability_config: dict[str, Any]
251
245
  ) -> None:
252
246
  """
253
247
  Set up observability features (health checks, metrics, logging) from manifest.
@@ -276,9 +270,7 @@ class ServiceInitializer:
276
270
  contextual_logger.info(
277
271
  f"Metrics collection configured for {slug}",
278
272
  extra={
279
- "operation_metrics": metrics_config.get(
280
- "collect_operation_metrics", True
281
- ),
273
+ "operation_metrics": metrics_config.get("collect_operation_metrics", True),
282
274
  "performance_metrics": metrics_config.get(
283
275
  "collect_performance_metrics", True
284
276
  ),
@@ -296,9 +288,7 @@ class ServiceInitializer:
296
288
  extra={
297
289
  "level": log_level,
298
290
  "format": log_format,
299
- "include_request_id": logging_config.get(
300
- "include_request_id", True
301
- ),
291
+ "include_request_id": logging_config.get("include_request_id", True),
302
292
  },
303
293
  )
304
294
 
@@ -307,7 +297,7 @@ class ServiceInitializer:
307
297
  f"Could not set up observability for {slug}: {e}", exc_info=True
308
298
  )
309
299
 
310
- def get_websocket_config(self, slug: str) -> Optional[Dict[str, Any]]:
300
+ def get_websocket_config(self, slug: str) -> dict[str, Any] | None:
311
301
  """
312
302
  Get WebSocket configuration for an app.
313
303
 
@@ -319,7 +309,7 @@ class ServiceInitializer:
319
309
  """
320
310
  return self._websocket_configs.get(slug)
321
311
 
322
- def get_memory_service(self, slug: str) -> Optional[Any]:
312
+ def get_memory_service(self, slug: str) -> Any | None:
323
313
  """
324
314
  Get Mem0 memory service for an app.
325
315