mdb-engine 0.1.6__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 (75) hide show
  1. mdb_engine/README.md +144 -0
  2. mdb_engine/__init__.py +37 -0
  3. mdb_engine/auth/README.md +631 -0
  4. mdb_engine/auth/__init__.py +128 -0
  5. mdb_engine/auth/casbin_factory.py +199 -0
  6. mdb_engine/auth/casbin_models.py +46 -0
  7. mdb_engine/auth/config_defaults.py +71 -0
  8. mdb_engine/auth/config_helpers.py +213 -0
  9. mdb_engine/auth/cookie_utils.py +158 -0
  10. mdb_engine/auth/decorators.py +350 -0
  11. mdb_engine/auth/dependencies.py +747 -0
  12. mdb_engine/auth/helpers.py +64 -0
  13. mdb_engine/auth/integration.py +578 -0
  14. mdb_engine/auth/jwt.py +225 -0
  15. mdb_engine/auth/middleware.py +241 -0
  16. mdb_engine/auth/oso_factory.py +323 -0
  17. mdb_engine/auth/provider.py +570 -0
  18. mdb_engine/auth/restrictions.py +271 -0
  19. mdb_engine/auth/session_manager.py +477 -0
  20. mdb_engine/auth/token_lifecycle.py +213 -0
  21. mdb_engine/auth/token_store.py +289 -0
  22. mdb_engine/auth/users.py +1516 -0
  23. mdb_engine/auth/utils.py +614 -0
  24. mdb_engine/cli/__init__.py +13 -0
  25. mdb_engine/cli/commands/__init__.py +7 -0
  26. mdb_engine/cli/commands/generate.py +105 -0
  27. mdb_engine/cli/commands/migrate.py +83 -0
  28. mdb_engine/cli/commands/show.py +70 -0
  29. mdb_engine/cli/commands/validate.py +63 -0
  30. mdb_engine/cli/main.py +41 -0
  31. mdb_engine/cli/utils.py +92 -0
  32. mdb_engine/config.py +217 -0
  33. mdb_engine/constants.py +160 -0
  34. mdb_engine/core/README.md +542 -0
  35. mdb_engine/core/__init__.py +42 -0
  36. mdb_engine/core/app_registration.py +392 -0
  37. mdb_engine/core/connection.py +243 -0
  38. mdb_engine/core/engine.py +749 -0
  39. mdb_engine/core/index_management.py +162 -0
  40. mdb_engine/core/manifest.py +2793 -0
  41. mdb_engine/core/seeding.py +179 -0
  42. mdb_engine/core/service_initialization.py +355 -0
  43. mdb_engine/core/types.py +413 -0
  44. mdb_engine/database/README.md +522 -0
  45. mdb_engine/database/__init__.py +31 -0
  46. mdb_engine/database/abstraction.py +635 -0
  47. mdb_engine/database/connection.py +387 -0
  48. mdb_engine/database/scoped_wrapper.py +1721 -0
  49. mdb_engine/embeddings/README.md +184 -0
  50. mdb_engine/embeddings/__init__.py +62 -0
  51. mdb_engine/embeddings/dependencies.py +193 -0
  52. mdb_engine/embeddings/service.py +759 -0
  53. mdb_engine/exceptions.py +167 -0
  54. mdb_engine/indexes/README.md +651 -0
  55. mdb_engine/indexes/__init__.py +21 -0
  56. mdb_engine/indexes/helpers.py +145 -0
  57. mdb_engine/indexes/manager.py +895 -0
  58. mdb_engine/memory/README.md +451 -0
  59. mdb_engine/memory/__init__.py +30 -0
  60. mdb_engine/memory/service.py +1285 -0
  61. mdb_engine/observability/README.md +515 -0
  62. mdb_engine/observability/__init__.py +42 -0
  63. mdb_engine/observability/health.py +296 -0
  64. mdb_engine/observability/logging.py +161 -0
  65. mdb_engine/observability/metrics.py +297 -0
  66. mdb_engine/routing/README.md +462 -0
  67. mdb_engine/routing/__init__.py +73 -0
  68. mdb_engine/routing/websockets.py +813 -0
  69. mdb_engine/utils/__init__.py +7 -0
  70. mdb_engine-0.1.6.dist-info/METADATA +213 -0
  71. mdb_engine-0.1.6.dist-info/RECORD +75 -0
  72. mdb_engine-0.1.6.dist-info/WHEEL +5 -0
  73. mdb_engine-0.1.6.dist-info/entry_points.txt +2 -0
  74. mdb_engine-0.1.6.dist-info/licenses/LICENSE +661 -0
  75. mdb_engine-0.1.6.dist-info/top_level.txt +1 -0
@@ -0,0 +1,462 @@
1
+ # WebSocket Support in MDB_ENGINE
2
+
3
+ MDB_ENGINE provides built-in WebSocket support with app-level isolation and automatic route registration via `manifest.json`.
4
+
5
+ ## Quick Start
6
+
7
+ ### 1. Add WebSocket to manifest.json
8
+
9
+ ```json
10
+ {
11
+ "slug": "my_app",
12
+ "websockets": {
13
+ "realtime": {
14
+ "path": "/ws"
15
+ }
16
+ }
17
+ }
18
+ ```
19
+
20
+ That's it! The engine automatically:
21
+ - ✅ Registers the WebSocket route with FastAPI
22
+ - ✅ Creates an isolated connection manager for your app
23
+ - ✅ Handles authentication (uses app's `auth_policy` by default)
24
+ - ✅ Manages connection lifecycle
25
+ - ✅ Provides ping/pong keepalive
26
+
27
+ ### 2. Broadcast messages (Server → Clients)
28
+
29
+ Send messages from your server code to connected clients:
30
+
31
+ ```python
32
+ from mdb_engine.routing.websockets import broadcast_to_app
33
+
34
+ # Broadcast to all connected clients for your app
35
+ await broadcast_to_app("my_app", {
36
+ "type": "update",
37
+ "data": {"status": "completed"}
38
+ })
39
+
40
+ # Broadcast to specific user only
41
+ await broadcast_to_app("my_app", {
42
+ "type": "notification",
43
+ "data": {"message": "Hello"}
44
+ }, user_id="user123")
45
+ ```
46
+
47
+ ### 3. Listen to client messages (Clients → Server)
48
+
49
+ Handle messages sent from clients to your server:
50
+
51
+ ```python
52
+ from mdb_engine.routing.websockets import register_message_handler, broadcast_to_app
53
+
54
+ async def handle_client_message(websocket, message):
55
+ """Handle incoming messages from WebSocket clients."""
56
+ message_type = message.get("type")
57
+
58
+ if message_type == "subscribe":
59
+ # Client wants to subscribe to a channel
60
+ channel = message.get("channel")
61
+ await broadcast_to_app("my_app", {
62
+ "type": "subscribed",
63
+ "channel": channel,
64
+ "user_id": message.get("user_id")
65
+ })
66
+
67
+ elif message_type == "ping":
68
+ # Respond to custom ping
69
+ await broadcast_to_app("my_app", {
70
+ "type": "pong",
71
+ "timestamp": message.get("timestamp")
72
+ }, user_id=message.get("user_id"))
73
+
74
+ # Register handler for the "realtime" endpoint
75
+ register_message_handler("my_app", "realtime", handle_client_message)
76
+ ```
77
+
78
+ **Note:** Register handlers **before** calling `engine.register_app()` or `engine.register_websocket_routes()`.
79
+
80
+ ## Configuration Options
81
+
82
+ ### Minimal Configuration (Recommended)
83
+
84
+ ```json
85
+ {
86
+ "websockets": {
87
+ "realtime": {
88
+ "path": "/ws"
89
+ }
90
+ }
91
+ }
92
+ ```
93
+
94
+ **Defaults:**
95
+ - ✅ Authentication: Uses app's `auth_policy` (respects `auth_required` and `auth_policy.required`)
96
+ - ✅ Ping interval: 30 seconds
97
+ - ✅ Automatic route registration
98
+ - ✅ App-level isolation (secure by default)
99
+
100
+ ### Advanced Configuration
101
+
102
+ ```json
103
+ {
104
+ "websockets": {
105
+ "realtime": {
106
+ "path": "/ws",
107
+ "description": "Real-time updates",
108
+ "auth": {
109
+ "required": true,
110
+ "allow_anonymous": false
111
+ },
112
+ "ping_interval": 30
113
+ },
114
+ "events": {
115
+ "path": "/events",
116
+ "auth": {
117
+ "required": false
118
+ },
119
+ "description": "Public event stream"
120
+ }
121
+ }
122
+ }
123
+ ```
124
+
125
+ ## Security Features
126
+
127
+ ### App-Level Isolation
128
+
129
+ - Each app has its own `WebSocketConnectionManager` instance
130
+ - Connections are automatically scoped to the app's `slug`
131
+ - Messages include `app_slug` in metadata to prevent cross-app leakage
132
+ - Broadcasts only reach clients connected to that specific app
133
+
134
+ ### Authentication
135
+
136
+ - Integrates with mdb_engine's JWT authentication system
137
+ - Supports token via query parameter or cookie
138
+ - Respects app's `auth_policy` configuration
139
+ - Can be overridden per endpoint
140
+
141
+ ### Connection Metadata
142
+
143
+ Each connection tracks:
144
+ - `app_slug`: Ensures isolation
145
+ - `user_id`: For user-specific filtering
146
+ - `user_email`: For logging and debugging
147
+ - `connected_at`: Connection timestamp
148
+
149
+ ## Usage Examples
150
+
151
+ ### Broadcasting (Server → Clients)
152
+
153
+ #### Basic Broadcast
154
+
155
+ ```python
156
+ from mdb_engine.routing.websockets import broadcast_to_app
157
+
158
+ # After a database operation
159
+ await db.collection.insert_one(document)
160
+ await broadcast_to_app("my_app", {
161
+ "type": "document_created",
162
+ "data": {"id": str(document["_id"])}
163
+ })
164
+ ```
165
+
166
+ #### User-Specific Broadcast
167
+
168
+ ```python
169
+ # Send notification to specific user
170
+ await broadcast_to_app("my_app", {
171
+ "type": "notification",
172
+ "data": {"message": "Task completed"}
173
+ }, user_id=current_user_id)
174
+ ```
175
+
176
+ #### Broadcast After CRUD Operations
177
+
178
+ ```python
179
+ # After creating a document
180
+ result = await db.items.insert_one(item)
181
+ await broadcast_to_app("my_app", {
182
+ "type": "item_created",
183
+ "data": {"id": str(result.inserted_id), "item": item}
184
+ })
185
+
186
+ # After updating
187
+ await db.items.update_one({"_id": item_id}, {"$set": updates})
188
+ await broadcast_to_app("my_app", {
189
+ "type": "item_updated",
190
+ "data": {"id": str(item_id), "updates": updates}
191
+ })
192
+
193
+ # After deleting
194
+ await db.items.delete_one({"_id": item_id})
195
+ await broadcast_to_app("my_app", {
196
+ "type": "item_deleted",
197
+ "data": {"id": str(item_id)}
198
+ })
199
+ ```
200
+
201
+ ### Listening (Clients → Server)
202
+
203
+ #### Basic Message Handler
204
+
205
+ ```python
206
+ from mdb_engine.routing.websockets import register_message_handler, broadcast_to_app
207
+
208
+ async def handle_realtime_messages(websocket, message):
209
+ """Handle messages from WebSocket clients."""
210
+ msg_type = message.get("type")
211
+
212
+ if msg_type == "subscribe":
213
+ # Client subscribes to updates
214
+ channel = message.get("channel", "default")
215
+ await broadcast_to_app("my_app", {
216
+ "type": "subscribed",
217
+ "channel": channel
218
+ })
219
+
220
+ elif msg_type == "unsubscribe":
221
+ # Client unsubscribes
222
+ await broadcast_to_app("my_app", {
223
+ "type": "unsubscribed"
224
+ })
225
+
226
+ elif msg_type == "request_data":
227
+ # Client requests specific data
228
+ data_type = message.get("data_type")
229
+ # Fetch and send data
230
+ data = await fetch_data(data_type)
231
+ await broadcast_to_app("my_app", {
232
+ "type": "data_response",
233
+ "data_type": data_type,
234
+ "data": data
235
+ })
236
+
237
+ # Register before routes are registered
238
+ register_message_handler("my_app", "realtime", handle_realtime_messages)
239
+ ```
240
+
241
+ #### Advanced: User-Aware Handler
242
+
243
+ ```python
244
+ from mdb_engine.routing.websockets import register_message_handler, get_websocket_manager
245
+
246
+ async def handle_user_actions(websocket, message):
247
+ """Handle user actions with authentication context."""
248
+ # Get connection metadata to identify user
249
+ manager = await get_websocket_manager("my_app")
250
+ connection = next(
251
+ (conn for conn in manager.active_connections if conn.websocket is websocket),
252
+ None
253
+ )
254
+
255
+ if connection and connection.user_id:
256
+ user_id = connection.user_id
257
+ msg_type = message.get("type")
258
+
259
+ if msg_type == "update_preferences":
260
+ # Update user preferences
261
+ prefs = message.get("preferences")
262
+ await db.users.update_one(
263
+ {"_id": user_id},
264
+ {"$set": {"preferences": prefs}}
265
+ )
266
+ # Notify user of success
267
+ await broadcast_to_app("my_app", {
268
+ "type": "preferences_updated",
269
+ "user_id": user_id
270
+ }, user_id=user_id)
271
+
272
+ register_message_handler("my_app", "realtime", handle_user_actions)
273
+ ```
274
+
275
+ #### Error Handling in Handlers
276
+
277
+ ```python
278
+ async def safe_message_handler(websocket, message):
279
+ """Handler with error handling."""
280
+ try:
281
+ msg_type = message.get("type")
282
+ if msg_type == "action":
283
+ # Process action
284
+ result = await process_action(message.get("action"))
285
+ await broadcast_to_app("my_app", {
286
+ "type": "action_result",
287
+ "result": result
288
+ })
289
+ except Exception as e:
290
+ logger.error(f"Error handling message: {e}")
291
+ # Send error to client
292
+ manager = await get_websocket_manager("my_app")
293
+ await manager.send_to_connection(websocket, {
294
+ "type": "error",
295
+ "message": str(e)
296
+ })
297
+
298
+ register_message_handler("my_app", "realtime", safe_message_handler)
299
+ ```
300
+
301
+ ### Connection Management
302
+
303
+ #### Get Connection Manager
304
+
305
+ ```python
306
+ from mdb_engine.routing.websockets import get_websocket_manager
307
+
308
+ manager = await get_websocket_manager("my_app")
309
+ connection_count = manager.get_connection_count()
310
+ user_connections = manager.get_connections_by_user("user123")
311
+ ```
312
+
313
+ #### Check Connection Status
314
+
315
+ ```python
316
+ manager = await get_websocket_manager("my_app")
317
+
318
+ # Total connections
319
+ total = manager.get_connection_count()
320
+
321
+ # Connections for specific user
322
+ user_conns = manager.get_connections_by_user("user123")
323
+ user_count = manager.get_connection_count_by_user("user123")
324
+ ```
325
+
326
+ #### Send to Specific Connection
327
+
328
+ ```python
329
+ manager = await get_websocket_manager("my_app")
330
+
331
+ # Find user's connection
332
+ user_connections = manager.get_connections_by_user("user123")
333
+ if user_connections:
334
+ await manager.send_to_connection(
335
+ user_connections[0].websocket,
336
+ {"type": "personal_message", "data": "Hello!"}
337
+ )
338
+ ```
339
+
340
+ ## Automatic Route Registration
341
+
342
+ When you call `engine.register_app(manifest)`, WebSocket routes are automatically registered:
343
+
344
+ ```python
345
+ from mdb_engine import MongoDBEngine
346
+ from mdb_engine.routing.websockets import register_message_handler
347
+
348
+ # 1. Register message handlers FIRST (before route registration)
349
+ async def handle_messages(websocket, message):
350
+ # Your handler logic
351
+ pass
352
+
353
+ register_message_handler("my_app", "realtime", handle_messages)
354
+
355
+ # 2. Initialize engine and register app
356
+ engine = MongoDBEngine(mongo_uri="...", db_name="...")
357
+ await engine.initialize()
358
+
359
+ manifest = await engine.load_manifest("manifest.json")
360
+ await engine.register_app(manifest) # WebSocket config loaded here
361
+
362
+ # 3. Register routes with FastAPI app (routes are created here)
363
+ engine.register_websocket_routes(app, manifest["slug"])
364
+ ```
365
+
366
+ **Important:** Register message handlers **before** calling `register_websocket_routes()` so handlers are available when routes are created.
367
+
368
+ ## Two-Way Communication
369
+
370
+ MDB_ENGINE WebSockets support **both directions**:
371
+
372
+ ### Server → Clients (Broadcasting)
373
+ - Use `broadcast_to_app()` to send messages to clients
374
+ - Messages are automatically scoped to the app
375
+ - Can filter by user_id for targeted messages
376
+ - Perfect for: real-time updates, notifications, data changes
377
+
378
+ ### Clients → Server (Listening)
379
+ - Register handlers with `register_message_handler()`
380
+ - Handlers process incoming client messages
381
+ - Can respond with broadcasts or direct messages
382
+ - Perfect for: subscriptions, user actions, data requests
383
+
384
+ ### Complete Example
385
+
386
+ ```python
387
+ from mdb_engine.routing.websockets import (
388
+ register_message_handler,
389
+ broadcast_to_app
390
+ )
391
+
392
+ # 1. Register handler to LISTEN to client messages
393
+ async def handle_client_requests(websocket, message):
394
+ msg_type = message.get("type")
395
+
396
+ if msg_type == "get_status":
397
+ # Client requests status - respond with broadcast
398
+ status = await get_current_status()
399
+ await broadcast_to_app("my_app", {
400
+ "type": "status_update",
401
+ "data": status
402
+ })
403
+
404
+ elif msg_type == "subscribe_channel":
405
+ # Client subscribes to channel
406
+ channel = message.get("channel")
407
+ await subscribe_user_to_channel(message.get("user_id"), channel)
408
+
409
+ register_message_handler("my_app", "realtime", handle_client_requests)
410
+
411
+ # 2. BROADCAST messages from your app code
412
+ async def on_document_created(document):
413
+ await broadcast_to_app("my_app", {
414
+ "type": "document_created",
415
+ "data": {"id": str(document["_id"])}
416
+ })
417
+ ```
418
+
419
+ ## Best Practices
420
+
421
+ 1. **Keep manifest simple**: Only specify `path` in manifest.json - defaults are secure
422
+ 2. **Use `broadcast_to_app()`**: Simplest way to send messages to clients
423
+ 3. **Register handlers early**: Register message handlers before route registration
424
+ 4. **Respect auth_policy**: Let the engine handle authentication automatically
425
+ 5. **Scope messages**: Always include `app_slug` (automatically added by engine)
426
+ 6. **Filter by user**: Use `user_id` parameter for user-specific messages
427
+ 7. **Handle errors**: Wrap handler logic in try/except and send error responses
428
+ 8. **Use message types**: Structure messages with `type` field for easy routing
429
+
430
+ ## Architecture
431
+
432
+ - **Isolation**: Each app has isolated WebSocket manager (no cross-app access)
433
+ - **Security**: Messages automatically scoped to app, authentication integrated
434
+ - **Simplicity**: Just declare in manifest.json, register handlers in code
435
+ - **Flexibility**: Full two-way communication (broadcast + listen)
436
+ - **Automatic**: Routes registered automatically during app registration
437
+
438
+ ## Message Flow
439
+
440
+ ```
441
+ Client Server App Code
442
+ | | |
443
+ |--- WebSocket Connect -->| |
444
+ | |--- Authenticate ------->|
445
+ |<-- Connection Confirmed-| |
446
+ | | |
447
+ |--- Message: {"type": | |
448
+ | "subscribe"} ------>| |
449
+ | |--- Handler Called ------>|
450
+ | | |
451
+ | |<-- broadcast_to_app() ---|
452
+ |<-- Broadcast Message ---| |
453
+ | | |
454
+ ```
455
+
456
+ ## Security Considerations
457
+
458
+ 1. **App Isolation**: Each app's WebSocket manager is completely isolated
459
+ 2. **Authentication**: Uses app's `auth_policy` by default
460
+ 3. **Message Scoping**: All messages include `app_slug` automatically
461
+ 4. **User Context**: Connection metadata tracks user_id and user_email
462
+ 5. **Error Handling**: Handler errors don't crash the connection
@@ -0,0 +1,73 @@
1
+ """
2
+ Route registration and WebSocket support.
3
+
4
+ Handles FastAPI route mounting, middleware configuration, and WebSocket endpoints.
5
+
6
+ WebSocket support is OPTIONAL and only enabled when:
7
+ 1. Apps define "websockets" in their manifest.json
8
+ 2. WebSocket dependencies are available (FastAPI WebSocket support)
9
+
10
+ If WebSockets are not configured or dependencies are missing, the engine
11
+ gracefully degrades without WebSocket functionality.
12
+ """
13
+
14
+ # WebSocket support is optional - lazy import to avoid breaking if dependencies missing
15
+ _websockets_available = None
16
+ _websockets_module = None
17
+
18
+
19
+ def _check_websockets_available():
20
+ """Check if WebSocket support is available."""
21
+ global _websockets_available, _websockets_module
22
+ if _websockets_available is None:
23
+ try:
24
+ from fastapi import WebSocket
25
+
26
+ _websockets_module = __import__(
27
+ ".websockets", fromlist=[""], package=__name__
28
+ )
29
+ _websockets_available = True
30
+ except (ImportError, AttributeError):
31
+ _websockets_available = False
32
+ _websockets_module = None
33
+ return _websockets_available
34
+
35
+
36
+ def _get_websocket_attr(name):
37
+ """Lazy getter for WebSocket attributes - raises ImportError if not available."""
38
+ if not _check_websockets_available():
39
+ raise ImportError(
40
+ "WebSocket support is not available. "
41
+ "WebSockets must be defined in manifest.json and FastAPI "
42
+ "WebSocket support must be installed."
43
+ )
44
+ return getattr(_websockets_module, name)
45
+
46
+
47
+ # Lazy exports - only available if WebSockets are configured and dependencies exist
48
+ def __getattr__(name):
49
+ """Lazy attribute access for WebSocket exports."""
50
+ if name in [
51
+ "WebSocketConnectionManager",
52
+ "WebSocketConnection",
53
+ "get_websocket_manager",
54
+ "create_websocket_endpoint",
55
+ "authenticate_websocket",
56
+ "broadcast_to_app",
57
+ "register_message_handler",
58
+ "get_message_handler",
59
+ ]:
60
+ return _get_websocket_attr(name)
61
+ raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
62
+
63
+
64
+ __all__ = [
65
+ "WebSocketConnectionManager",
66
+ "WebSocketConnection",
67
+ "get_websocket_manager",
68
+ "create_websocket_endpoint",
69
+ "authenticate_websocket",
70
+ "broadcast_to_app", # Simplest way to broadcast from app code
71
+ "register_message_handler", # Register handlers to listen to client messages
72
+ "get_message_handler",
73
+ ]