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