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
mdb_engine/config.py CHANGED
@@ -7,7 +7,6 @@ with direct parameters as before.
7
7
  """
8
8
 
9
9
  import os
10
- from typing import Optional
11
10
 
12
11
  try:
13
12
  from pydantic import BaseSettings, Field
@@ -46,12 +45,12 @@ class EngineConfig:
46
45
 
47
46
  def __init__(
48
47
  self,
49
- mongo_uri: Optional[str] = None,
50
- db_name: Optional[str] = None,
51
- max_pool_size: Optional[int] = None,
52
- min_pool_size: Optional[int] = None,
53
- server_selection_timeout_ms: Optional[int] = None,
54
- authz_cache_ttl: Optional[int] = None,
48
+ mongo_uri: str | None = None,
49
+ db_name: str | None = None,
50
+ max_pool_size: int | None = None,
51
+ min_pool_size: int | None = None,
52
+ server_selection_timeout_ms: int | None = None,
53
+ authz_cache_ttl: int | None = None,
55
54
  ):
56
55
  """
57
56
  Initialize configuration.
@@ -66,18 +65,12 @@ class EngineConfig:
66
65
  """
67
66
  self.mongo_uri = mongo_uri or os.getenv("MONGO_URI", "")
68
67
  self.db_name = db_name or os.getenv("DB_NAME", "")
69
- self.max_pool_size = max_pool_size or int(
70
- os.getenv("MONGO_MAX_POOL_SIZE", "50")
71
- )
72
- self.min_pool_size = min_pool_size or int(
73
- os.getenv("MONGO_MIN_POOL_SIZE", "10")
74
- )
68
+ self.max_pool_size = max_pool_size or int(os.getenv("MONGO_MAX_POOL_SIZE", "50"))
69
+ self.min_pool_size = min_pool_size or int(os.getenv("MONGO_MIN_POOL_SIZE", "10"))
75
70
  self.server_selection_timeout_ms = server_selection_timeout_ms or int(
76
71
  os.getenv("MONGO_SERVER_SELECTION_TIMEOUT_MS", "5000")
77
72
  )
78
- self.authz_cache_ttl = authz_cache_ttl or int(
79
- os.getenv("AUTHZ_CACHE_TTL", "300")
80
- )
73
+ self.authz_cache_ttl = authz_cache_ttl or int(os.getenv("AUTHZ_CACHE_TTL", "300"))
81
74
 
82
75
  def validate(self) -> None:
83
76
  """
@@ -115,9 +108,7 @@ class EngineConfig:
115
108
  )
116
109
 
117
110
  if self.authz_cache_ttl < 0:
118
- raise ValueError(
119
- f"authz_cache_ttl must be >= 0, got {self.authz_cache_ttl}"
120
- )
111
+ raise ValueError(f"authz_cache_ttl must be >= 0, got {self.authz_cache_ttl}")
121
112
 
122
113
 
123
114
  # Pydantic-based configuration (optional, only if Pydantic is available)
@@ -138,9 +129,7 @@ if PYDANTIC_AVAILABLE:
138
129
  )
139
130
  """
140
131
 
141
- mongo_uri: str = Field(
142
- ..., env="MONGO_URI", description="MongoDB connection URI"
143
- )
132
+ mongo_uri: str = Field(..., env="MONGO_URI", description="MongoDB connection URI")
144
133
  db_name: str = Field(..., env="DB_NAME", description="Database name")
145
134
  max_pool_size: int = Field(
146
135
  50,
@@ -203,15 +192,11 @@ ACCESS_TOKEN_TTL: int = int(os.getenv("ACCESS_TOKEN_TTL", "900")) # 15 minutes
203
192
  REFRESH_TOKEN_TTL: int = int(os.getenv("REFRESH_TOKEN_TTL", "604800")) # 7 days
204
193
  """Refresh token TTL in seconds (default: 604800 / 7 days)."""
205
194
 
206
- TOKEN_ROTATION_ENABLED: bool = (
207
- os.getenv("TOKEN_ROTATION_ENABLED", "true").lower() == "true"
208
- )
195
+ TOKEN_ROTATION_ENABLED: bool = os.getenv("TOKEN_ROTATION_ENABLED", "true").lower() == "true"
209
196
  """Whether to rotate refresh tokens on each use (default: true)."""
210
197
 
211
198
  MAX_SESSIONS_PER_USER: int = int(os.getenv("MAX_SESSIONS_PER_USER", "10"))
212
199
  """Maximum number of concurrent sessions per user (default: 10)."""
213
200
 
214
- SESSION_INACTIVITY_TIMEOUT: int = int(
215
- os.getenv("SESSION_INACTIVITY_TIMEOUT", "1800")
216
- ) # 30 minutes
201
+ SESSION_INACTIVITY_TIMEOUT: int = int(os.getenv("SESSION_INACTIVITY_TIMEOUT", "1800")) # 30 minutes
217
202
  """Session inactivity timeout in seconds (default: 1800 / 30 minutes)."""
mdb_engine/constants.py CHANGED
@@ -57,6 +57,25 @@ DEFAULT_MAX_IDLE_TIME_MS: Final[int] = 45000
57
57
  MAX_COLLECTION_NAME_LENGTH: Final[int] = 255
58
58
  """Maximum length for MongoDB collection names."""
59
59
 
60
+ MIN_COLLECTION_NAME_LENGTH: Final[int] = 1
61
+ """Minimum length for MongoDB collection names."""
62
+
63
+ # Reserved collection name prefixes (MongoDB system collections)
64
+ RESERVED_COLLECTION_PREFIXES: Final[tuple[str, ...]] = (
65
+ "system",
66
+ "admin",
67
+ "config",
68
+ "local",
69
+ )
70
+ """Reserved MongoDB collection name prefixes that cannot be used."""
71
+
72
+ # Reserved collection names (engine-internal)
73
+ RESERVED_COLLECTION_NAMES: Final[tuple[str, ...]] = (
74
+ "apps_config", # Engine internal - app registration
75
+ "_mdb_engine_app_secrets", # Engine internal - encrypted app secrets
76
+ )
77
+ """Reserved collection names that cannot be accessed through scoped wrappers."""
78
+
60
79
  # App slug constraints
61
80
  MAX_APP_SLUG_LENGTH: Final[int] = 100
62
81
  """Maximum length for app slugs."""
@@ -158,3 +177,49 @@ SUPPORTED_APP_STATUSES: Final[tuple[str, ...]] = (
158
177
  APP_STATUS_ARCHIVED,
159
178
  APP_STATUS_INACTIVE,
160
179
  )
180
+
181
+ # ============================================================================
182
+ # QUERY SECURITY & RESOURCE LIMITS
183
+ # ============================================================================
184
+
185
+ # Query execution limits
186
+ DEFAULT_MAX_TIME_MS: Final[int] = 30000
187
+ """Default query timeout in milliseconds (30 seconds)."""
188
+
189
+ MAX_QUERY_TIME_MS: Final[int] = 300000
190
+ """Maximum allowed query timeout in milliseconds (5 minutes)."""
191
+
192
+ MAX_QUERY_RESULT_SIZE: Final[int] = 10000
193
+ """Maximum number of documents that can be returned in a single query."""
194
+
195
+ MAX_CURSOR_BATCH_SIZE: Final[int] = 1000
196
+ """Maximum batch size for cursor operations."""
197
+
198
+ MAX_DOCUMENT_SIZE: Final[int] = 16 * 1024 * 1024
199
+ """Maximum document size in bytes (16MB MongoDB limit)."""
200
+
201
+ # Pipeline limits
202
+ MAX_PIPELINE_STAGES: Final[int] = 50
203
+ """Maximum number of stages allowed in an aggregation pipeline."""
204
+
205
+ MAX_SORT_FIELDS: Final[int] = 10
206
+ """Maximum number of fields that can be sorted in a single query."""
207
+
208
+ MAX_QUERY_DEPTH: Final[int] = 10
209
+ """Maximum nesting depth for query filters (prevents deeply nested queries)."""
210
+
211
+ # Regex limits (prevent ReDoS attacks)
212
+ MAX_REGEX_LENGTH: Final[int] = 1000
213
+ """Maximum length of regex patterns to prevent ReDoS attacks."""
214
+
215
+ MAX_REGEX_COMPLEXITY: Final[int] = 50
216
+ """Maximum complexity score for regex patterns (prevents ReDoS)."""
217
+
218
+ # Dangerous MongoDB operators that should be blocked
219
+ DANGEROUS_OPERATORS: Final[tuple[str, ...]] = (
220
+ "$where", # JavaScript execution (security risk)
221
+ "$eval", # JavaScript evaluation (deprecated, security risk)
222
+ "$function", # JavaScript functions (security risk)
223
+ "$accumulator", # Can be abused for code execution
224
+ )
225
+ """MongoDB operators that are blocked for security reasons."""
mdb_engine/core/README.md CHANGED
@@ -2,6 +2,26 @@
2
2
 
3
3
  The central orchestration engine for MDB_ENGINE that manages database connections, app registration, manifest validation, index management, and resource lifecycle.
4
4
 
5
+ ## When to Use What
6
+
7
+ ### Integration Patterns
8
+
9
+ | Pattern | Best For | Code |
10
+ |---------|----------|------|
11
+ | **`create_app()`** | New FastAPI apps | `app = engine.create_app(slug, manifest)` |
12
+ | **`lifespan()`** | Custom FastAPI setup | `FastAPI(lifespan=engine.lifespan(...))` |
13
+ | **Manual** | Existing apps, scripts | `await engine.initialize()` / `await engine.shutdown()` |
14
+ | **Context Manager** | Scripts, tests | `async with engine: ...` |
15
+
16
+ ### Feature Activation
17
+
18
+ | Feature | When to Enable | Configuration |
19
+ |---------|---------------|---------------|
20
+ | **Ray** | Distributed processing, isolated actors | `enable_ray=True` |
21
+ | **Multi-site** | Cross-app data sharing | `read_scopes` in manifest |
22
+ | **Auto-indexing** | Let engine optimize queries | Default ON (`auto_index=True`) |
23
+ | **App Tokens** | Production security | Set `MDB_ENGINE_MASTER_KEY` |
24
+
5
25
  ## Features
6
26
 
7
27
  - **MongoDBEngine**: Central orchestration for all engine components
@@ -54,12 +74,57 @@ engine = MongoDBEngine(
54
74
  manifests_dir=Path("./manifests"), # Optional
55
75
  authz_provider=authz_provider, # Optional
56
76
  max_pool_size=10, # Optional
57
- min_pool_size=1 # Optional
77
+ min_pool_size=1, # Optional
78
+ enable_ray=False, # Optional: Enable Ray support
79
+ ray_namespace="modular_labs" # Optional: Ray namespace
58
80
  )
59
81
 
60
82
  await engine.initialize()
61
83
  ```
62
84
 
85
+ ### FastAPI Integration
86
+
87
+ The simplest way to create a FastAPI app with automatic lifecycle management:
88
+
89
+ ```python
90
+ from mdb_engine import MongoDBEngine
91
+ from pathlib import Path
92
+
93
+ engine = MongoDBEngine(
94
+ mongo_uri="mongodb://localhost:27017",
95
+ db_name="my_database"
96
+ )
97
+
98
+ # Create FastAPI app with automatic initialization and cleanup
99
+ app = engine.create_app(
100
+ slug="my_app",
101
+ manifest=Path("manifest.json")
102
+ )
103
+
104
+ @app.get("/items")
105
+ async def get_items():
106
+ db = engine.get_scoped_db("my_app")
107
+ return await db.items.find({}).to_list(10)
108
+ ```
109
+
110
+ This automatically:
111
+ - Initializes the engine on startup
112
+ - Loads and registers the manifest
113
+ - Auto-detects multi-site mode from manifest
114
+ - Auto-retrieves app tokens
115
+ - Shuts down cleanly on app exit
116
+
117
+ ### Custom Lifespan
118
+
119
+ For more control, use the `lifespan()` helper:
120
+
121
+ ```python
122
+ from fastapi import FastAPI
123
+
124
+ engine = MongoDBEngine(...)
125
+ app = FastAPI(lifespan=engine.lifespan("my_app", Path("manifest.json")))
126
+ ```
127
+
63
128
  ### Get Scoped Database
64
129
 
65
130
  Get a database wrapper that automatically isolates data by app:
@@ -91,6 +156,33 @@ manifest = await engine.load_manifest("manifest.json")
91
156
  await engine.register_app(manifest)
92
157
  ```
93
158
 
159
+ ### Optional Ray Integration
160
+
161
+ Enable Ray for distributed processing (Ray must be installed):
162
+
163
+ ```python
164
+ from mdb_engine import MongoDBEngine
165
+
166
+ # Enable Ray support
167
+ engine = MongoDBEngine(
168
+ mongo_uri="mongodb://localhost:27017",
169
+ db_name="my_database",
170
+ enable_ray=True,
171
+ ray_namespace="my_namespace"
172
+ )
173
+
174
+ await engine.initialize()
175
+
176
+ # Check if Ray is available
177
+ if engine.has_ray:
178
+ print(f"Ray initialized in namespace: {engine.ray_namespace}")
179
+ ```
180
+
181
+ Ray integration features:
182
+ - Isolated app environments via Ray actors
183
+ - Automatic graceful degradation if Ray not installed
184
+ - App-specific namespaces for isolation
185
+
94
186
  ### Health Checks
95
187
 
96
188
  Check engine and MongoDB health:
@@ -98,10 +190,13 @@ Check engine and MongoDB health:
98
190
  ```python
99
191
  from mdb_engine.observability import check_engine_health, check_mongodb_health
100
192
 
101
- # Check MongoDB connection
193
+ # Check MongoDB connection (mongo_client is for observability only, not data access)
102
194
  mongodb_status = await check_mongodb_health(engine.mongo_client)
103
195
  print(mongodb_status)
104
196
 
197
+ # ⚠️ SECURITY: engine.mongo_client is for observability/admin operations only
198
+ # Always use engine.get_scoped_db() for all data access operations
199
+
105
200
  # Check engine health
106
201
  engine_status = await check_engine_health(engine)
107
202
  print(engine_status)
@@ -299,11 +394,23 @@ is_valid, error, paths = await validate_manifest_with_db(
299
394
  - `get_app_info(app_slug)` - Get information about registered app
300
395
  - `unregister_app(app_slug)` - Unregister an app
301
396
 
397
+ **FastAPI Integration Methods:**
398
+
399
+ - `create_app(slug, manifest, title=None, **fastapi_kwargs)` - Create a FastAPI app with automatic lifecycle management
400
+ - `lifespan(slug, manifest)` - Create a lifespan context manager for FastAPI
401
+
402
+ **App Token Methods:**
403
+
404
+ - `auto_retrieve_app_token(slug)` - Auto-retrieve app token from environment or database
405
+ - `get_app_token(slug)` - Get cached app token for a slug
406
+
302
407
  #### Properties
303
408
 
304
- - `mongo_client` - MongoDB client instance
305
- - `mongo_db` - MongoDB database instance
409
+ - `mongo_client` - MongoDB client instance (for observability only)
306
410
  - `initialized` - Whether engine is initialized
411
+ - `has_ray` - Whether Ray is enabled and initialized
412
+ - `enable_ray` - Whether Ray support is enabled
413
+ - `ray_namespace` - Ray namespace for actor isolation
307
414
 
308
415
  ### ManifestValidator
309
416
 
@@ -354,6 +461,8 @@ export MONGO_SERVER_SELECTION_TIMEOUT_MS=5000
354
461
  - `authz_provider` (optional): Authorization provider instance
355
462
  - `max_pool_size` (optional): Maximum connection pool size (default: 10)
356
463
  - `min_pool_size` (optional): Minimum connection pool size (default: 1)
464
+ - `enable_ray` (optional): Enable Ray support for distributed processing (default: False)
465
+ - `ray_namespace` (optional): Ray namespace for actor isolation (default: "modular_labs")
357
466
 
358
467
  ## Manifest Schema Features
359
468
 
@@ -480,13 +589,15 @@ Define various index types:
480
589
  ```python
481
590
  from mdb_engine.exceptions import InitializationError
482
591
 
592
+ from pymongo.errors import ConnectionFailure, ServerSelectionTimeoutError
593
+
483
594
  try:
484
595
  await engine.initialize()
485
596
  except InitializationError as e:
486
597
  print(f"Initialization failed: {e}")
487
598
  print(f"MongoDB URI: {e.mongo_uri}")
488
- except Exception as e:
489
- print(f"Unexpected error: {e}")
599
+ except (ConnectionFailure, ServerSelectionTimeoutError) as e:
600
+ print(f"MongoDB connection error: {e}")
490
601
  ```
491
602
 
492
603
  ## Integration Examples
@@ -3,20 +3,52 @@ Core MongoDB Engine components.
3
3
 
4
4
  This module contains the main MongoDBEngine class and core
5
5
  orchestration logic for managing apps.
6
+
7
+ The MongoDBEngine now includes:
8
+ - FastAPI integration with create_app() method
9
+ - Optional Ray support with enable_ray parameter
10
+ - Automatic app token retrieval
11
+ - Multi-site mode auto-detection from manifest
6
12
  """
7
13
 
8
14
  from .engine import MongoDBEngine
9
15
  from .manifest import ( # Classes; Constants; Functions (for backward compatibility); Schemas
10
- CURRENT_SCHEMA_VERSION, DEFAULT_SCHEMA_VERSION, MANIFEST_SCHEMA,
11
- MANIFEST_SCHEMA_V1, MANIFEST_SCHEMA_V2, SCHEMA_REGISTRY, ManifestParser,
12
- ManifestValidator, clear_validation_cache, get_schema_for_version,
13
- get_schema_version, migrate_manifest, validate_developer_id,
14
- validate_index_definition, validate_managed_indexes, validate_manifest,
15
- validate_manifest_with_db, validate_manifests_parallel)
16
+ CURRENT_SCHEMA_VERSION,
17
+ DEFAULT_SCHEMA_VERSION,
18
+ MANIFEST_SCHEMA,
19
+ MANIFEST_SCHEMA_V1,
20
+ MANIFEST_SCHEMA_V2,
21
+ SCHEMA_REGISTRY,
22
+ ManifestParser,
23
+ ManifestValidator,
24
+ clear_validation_cache,
25
+ get_schema_for_version,
26
+ get_schema_version,
27
+ migrate_manifest,
28
+ validate_developer_id,
29
+ validate_index_definition,
30
+ validate_managed_indexes,
31
+ validate_manifest,
32
+ validate_manifest_with_db,
33
+ validate_manifests_parallel,
34
+ )
35
+
36
+ # Optional Ray integration (gracefully handles missing Ray)
37
+ from .ray_integration import (
38
+ RAY_AVAILABLE,
39
+ AppRayActor,
40
+ get_ray_actor_handle,
41
+ ray_actor_decorator,
42
+ )
16
43
 
17
44
  __all__ = [
18
- # MongoDB Engine
45
+ # MongoDB Engine (includes FastAPI integration and optional Ray)
19
46
  "MongoDBEngine",
47
+ # Ray Integration (optional - only active if Ray installed)
48
+ "RAY_AVAILABLE",
49
+ "AppRayActor",
50
+ "get_ray_actor_handle",
51
+ "ray_actor_decorator",
20
52
  # Classes
21
53
  "ManifestValidator",
22
54
  "ManifestParser",
@@ -10,16 +10,22 @@ This module is part of MDB_ENGINE - MongoDB Engine.
10
10
  import asyncio
11
11
  import logging
12
12
  import time
13
+ from collections.abc import Callable
13
14
  from pathlib import Path
14
- from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Tuple
15
+ from typing import TYPE_CHECKING, Any, Optional
15
16
 
17
+ from jsonschema import SchemaError
18
+ from jsonschema import ValidationError as JsonSchemaValidationError
16
19
  from motor.motor_asyncio import AsyncIOMotorDatabase
17
- from pymongo.errors import (ConnectionFailure, InvalidOperation,
18
- OperationFailure, ServerSelectionTimeoutError)
19
-
20
- from ..observability import clear_app_context
20
+ from pymongo.errors import (
21
+ ConnectionFailure,
22
+ InvalidOperation,
23
+ OperationFailure,
24
+ ServerSelectionTimeoutError,
25
+ )
26
+
27
+ from ..observability import clear_app_context, record_operation, set_app_context
21
28
  from ..observability import get_logger as get_contextual_logger
22
- from ..observability import record_operation, set_app_context
23
29
  from .manifest import ManifestParser, ManifestValidator
24
30
 
25
31
  if TYPE_CHECKING:
@@ -53,11 +59,11 @@ class AppRegistrationManager:
53
59
  self._mongo_db = mongo_db
54
60
  self.manifest_validator = manifest_validator
55
61
  self.manifest_parser = manifest_parser
56
- self._apps: Dict[str, Dict[str, Any]] = {}
62
+ self._apps: dict[str, dict[str, Any]] = {}
57
63
 
58
64
  async def validate_manifest(
59
65
  self, manifest: "ManifestDict"
60
- ) -> Tuple[bool, Optional[str], Optional[List[str]]]:
66
+ ) -> tuple[bool, str | None, list[str] | None]:
61
67
  """
62
68
  Validate a manifest against the schema.
63
69
 
@@ -81,7 +87,7 @@ class AppRegistrationManager:
81
87
  experiment_slug=slug,
82
88
  )
83
89
  return result
84
- except Exception:
90
+ except (JsonSchemaValidationError, SchemaError, ValueError, TypeError, KeyError):
85
91
  duration_ms = (time.time() - start_time) * 1000
86
92
  record_operation(
87
93
  "app_registration.validate_manifest",
@@ -110,19 +116,12 @@ class AppRegistrationManager:
110
116
  async def register_app(
111
117
  self,
112
118
  manifest: "ManifestDict",
113
- create_indexes_callback: Optional[Callable[[str, "ManifestDict"], Any]] = None,
114
- seed_data_callback: Optional[
115
- Callable[[str, Dict[str, List[Dict[str, Any]]]], Any]
116
- ] = None,
117
- initialize_memory_callback: Optional[
118
- Callable[[str, Dict[str, Any]], Any]
119
- ] = None,
120
- register_websockets_callback: Optional[
121
- Callable[[str, Dict[str, Any]], Any]
122
- ] = None,
123
- setup_observability_callback: Optional[
124
- Callable[[str, "ManifestDict", Dict[str, Any]], Any]
125
- ] = None,
119
+ create_indexes_callback: Callable[[str, "ManifestDict"], Any] | None = None,
120
+ seed_data_callback: Callable[[str, dict[str, list[dict[str, Any]]]], Any] | None = None,
121
+ initialize_memory_callback: Callable[[str, dict[str, Any]], Any] | None = None,
122
+ register_websockets_callback: Callable[[str, dict[str, Any]], Any] | None = None,
123
+ setup_observability_callback: Callable[[str, "ManifestDict", dict[str, Any]], Any]
124
+ | None = None,
126
125
  ) -> bool:
127
126
  """
128
127
  Register an app from its manifest.
@@ -143,7 +142,7 @@ class AppRegistrationManager:
143
142
  """
144
143
  start_time = time.time()
145
144
 
146
- slug: Optional[str] = manifest.get("slug")
145
+ slug: str | None = manifest.get("slug")
147
146
  if not slug:
148
147
  contextual_logger.error(
149
148
  "Cannot register app: missing 'slug' in manifest",
@@ -210,9 +209,7 @@ class AppRegistrationManager:
210
209
  )
211
210
  # Continue even if persistence fails - app is still registered in memory
212
211
  except InvalidOperation as e:
213
- logger.debug(
214
- f"Cannot persist app '{slug}': MongoDB client is closed: {e}"
215
- )
212
+ logger.debug(f"Cannot persist app '{slug}': MongoDB client is closed: {e}")
216
213
  # Continue - app is still registered in memory
217
214
 
218
215
  # Invalidate auth config cache for this app
@@ -228,32 +225,22 @@ class AppRegistrationManager:
228
225
 
229
226
  # Create indexes if requested
230
227
  if create_indexes_callback and "managed_indexes" in manifest:
231
- logger.info(
232
- f"[{slug}] Creating managed indexes " f"(has_managed_indexes=True)"
233
- )
228
+ logger.info(f"[{slug}] Creating managed indexes " f"(has_managed_indexes=True)")
234
229
  callback_tasks.append(create_indexes_callback(slug, manifest))
235
230
 
236
231
  # Seed initial data if configured
237
232
  if seed_data_callback and "initial_data" in manifest:
238
- callback_tasks.append(
239
- seed_data_callback(slug, manifest["initial_data"])
240
- )
233
+ callback_tasks.append(seed_data_callback(slug, manifest["initial_data"]))
241
234
 
242
235
  # Initialize Memory service if configured
243
236
  memory_config = manifest.get("memory_config")
244
- if (
245
- initialize_memory_callback
246
- and memory_config
247
- and memory_config.get("enabled", False)
248
- ):
237
+ if initialize_memory_callback and memory_config and memory_config.get("enabled", False):
249
238
  callback_tasks.append(initialize_memory_callback(slug, memory_config))
250
239
 
251
240
  # Register WebSocket endpoints if configured
252
241
  websockets_config = manifest.get("websockets")
253
242
  if register_websockets_callback and websockets_config:
254
- callback_tasks.append(
255
- register_websockets_callback(slug, websockets_config)
256
- )
243
+ callback_tasks.append(register_websockets_callback(slug, websockets_config))
257
244
 
258
245
  # Set up observability (health checks, metrics, logging)
259
246
  observability_config = manifest.get("observability", {})
@@ -275,9 +262,7 @@ class AppRegistrationManager:
275
262
  "register_websockets",
276
263
  "setup_observability",
277
264
  ]
278
- callback_name = (
279
- callback_names[i] if i < len(callback_names) else "unknown"
280
- )
265
+ callback_name = callback_names[i] if i < len(callback_names) else "unknown"
281
266
  logger.warning(
282
267
  f"[{slug}] Callback '{callback_name}' failed during "
283
268
  f"app registration: {result}",
@@ -295,9 +280,7 @@ class AppRegistrationManager:
295
280
  "App registered successfully",
296
281
  extra={
297
282
  "app_slug": slug,
298
- "memory_enabled": bool(
299
- memory_config and memory_config.get("enabled", False)
300
- ),
283
+ "memory_enabled": bool(memory_config and memory_config.get("enabled", False)),
301
284
  "websockets_configured": bool(websockets_config),
302
285
  "duration_ms": round(duration_ms, 2),
303
286
  },
@@ -324,9 +307,7 @@ class AppRegistrationManager:
324
307
  try:
325
308
  # Fetch active apps
326
309
  active_cfgs = (
327
- await self._mongo_db.apps_config.find({"status": "active"})
328
- .limit(500)
329
- .to_list(None)
310
+ await self._mongo_db.apps_config.find({"status": "active"}).limit(500).to_list(None)
330
311
  )
331
312
 
332
313
  logger.info(f"Found {len(active_cfgs)} active app(s).")
@@ -378,7 +359,7 @@ class AppRegistrationManager:
378
359
  """
379
360
  return self._apps.get(slug)
380
361
 
381
- def list_apps(self) -> List[str]:
362
+ def list_apps(self) -> list[str]:
382
363
  """
383
364
  List all registered app slugs.
384
365