mdb-engine 0.6.0__py3-none-any.whl → 0.7.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.
@@ -1373,6 +1373,20 @@ MANIFEST_SCHEMA_V2 = {
1373
1373
  "alive (default: 30, min: 5, max: 300)"
1374
1374
  ),
1375
1375
  },
1376
+ "ticket_ttl_seconds": {
1377
+ "type": "integer",
1378
+ "minimum": 1,
1379
+ "maximum": 60,
1380
+ "default": 10,
1381
+ "description": (
1382
+ "WebSocket ticket time-to-live in seconds (default: 10). "
1383
+ "Tickets are single-use authentication tokens for "
1384
+ "WebSocket connections. "
1385
+ "Shorter TTL (3-5s) provides better security but requires "
1386
+ "faster connection. "
1387
+ "Longer TTL (10-30s) is more forgiving for slow networks."
1388
+ ),
1389
+ },
1376
1390
  },
1377
1391
  "required": ["path"],
1378
1392
  "additionalProperties": False,
@@ -360,10 +360,10 @@ def ray_actor_decorator(
360
360
  # Convert to Ray remote class
361
361
  ray_cls = ray.remote(cls)
362
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
363
+ # Store metadata on the class (intentional dynamic attribute setting for Ray)
364
+ ray_cls._app_slug = actor_app_slug # noqa: SLF001
365
+ ray_cls._namespace = actor_namespace # noqa: SLF001
366
+ ray_cls._isolated = isolated # noqa: SLF001
367
367
 
368
368
  # Add spawn class method
369
369
  @classmethod
mdb_engine/core/types.py CHANGED
@@ -253,6 +253,7 @@ class WebSocketEndpointDict(TypedDict, total=False):
253
253
  auth: WebSocketAuthDict
254
254
  description: str
255
255
  ping_interval: int
256
+ ticket_ttl_seconds: int
256
257
 
257
258
 
258
259
  class WebSocketsDict(TypedDict):
@@ -91,7 +91,8 @@ def get_shared_mongo_client(
91
91
  # Verify client is still connected
92
92
  try:
93
93
  # Non-blocking check - if client was closed, it will be None or invalid
94
- if hasattr(_shared_client, "_topology") and _shared_client._topology is not None:
94
+ # Accessing MongoDB client's internal _topology attribute to check connection status
95
+ if hasattr(_shared_client, "_topology") and _shared_client._topology is not None: # noqa: SLF001
95
96
  return _shared_client
96
97
  except (AttributeError, RuntimeError):
97
98
  # Client was closed or invalid, reset and recreate
@@ -104,7 +105,8 @@ def get_shared_mongo_client(
104
105
  # Double-check pattern: another thread may have initialized while we waited
105
106
  if _shared_client is not None:
106
107
  try:
107
- if hasattr(_shared_client, "_topology") and _shared_client._topology is not None:
108
+ # Accessing MongoDB client's internal _topology attribute to check connection status
109
+ if hasattr(_shared_client, "_topology") and _shared_client._topology is not None: # noqa: SLF001
108
110
  return _shared_client
109
111
  except (AttributeError, RuntimeError):
110
112
  # Client was closed or invalid, reset and recreate
@@ -234,7 +236,8 @@ async def get_pool_metrics(
234
236
  for registered_client in _registered_clients:
235
237
  try:
236
238
  # Verify client is still valid
237
- if hasattr(registered_client, "_topology") and registered_client._topology is not None:
239
+ # Accessing MongoDB client's internal _topology attribute to check connection status
240
+ if hasattr(registered_client, "_topology") and registered_client._topology is not None: # noqa: SLF001
238
241
  return await _get_client_pool_metrics(registered_client)
239
242
  except (AttributeError, RuntimeError):
240
243
  # Type 2: Recoverable - if this client is invalid, try next one
@@ -291,7 +291,7 @@ class AsyncAtlasIndexManager:
291
291
  """
292
292
  # Unwrap _SecureCollectionProxy if present to get the real collection
293
293
  if isinstance(real_collection, _SecureCollectionProxy):
294
- real_collection = real_collection._collection
294
+ real_collection = real_collection._collection # noqa: SLF001
295
295
  if not isinstance(real_collection, AsyncIOMotorCollection):
296
296
  raise TypeError(f"Expected AsyncIOMotorCollection, got {type(real_collection)}")
297
297
  self._collection = real_collection
@@ -1297,7 +1297,7 @@ class ScopedCollectionWrapper:
1297
1297
  try:
1298
1298
  # Verify token if needed (lazy verification for async contexts)
1299
1299
  if self._parent_wrapper:
1300
- await self._parent_wrapper._verify_token_if_needed()
1300
+ await self._parent_wrapper._verify_token_if_needed() # noqa: SLF001
1301
1301
 
1302
1302
  # Validate document size before insert
1303
1303
  self._resource_limiter.validate_document_size(document)
@@ -1390,7 +1390,7 @@ class ScopedCollectionWrapper:
1390
1390
  try:
1391
1391
  # Verify token if needed (lazy verification for async contexts)
1392
1392
  if self._parent_wrapper:
1393
- await self._parent_wrapper._verify_token_if_needed()
1393
+ await self._parent_wrapper._verify_token_if_needed() # noqa: SLF001
1394
1394
 
1395
1395
  # Validate query filter for security
1396
1396
  self._query_validator.validate_filter(filter)
@@ -441,7 +441,7 @@ async def _handle_search_index(
441
441
  logger.info(f"{log_prefix} Search index '{index_name}' definition matches.")
442
442
  if not existing_index.get("queryable") and existing_index.get("status") != "FAILED":
443
443
  logger.info(f"{log_prefix} Index '{index_name}' not queryable yet; waiting.")
444
- await index_manager._wait_for_search_index_ready(
444
+ await index_manager._wait_for_search_index_ready( # noqa: SLF001
445
445
  index_name, index_manager.DEFAULT_SEARCH_TIMEOUT
446
446
  )
447
447
  logger.info(f"{log_prefix} Index '{index_name}' now ready.")
@@ -580,7 +580,7 @@ async def _handle_hybrid_index(
580
580
  f"{log_prefix} Vector index '{vector_index_name}' "
581
581
  f"not queryable yet; waiting."
582
582
  )
583
- await index_manager._wait_for_search_index_ready(
583
+ await index_manager._wait_for_search_index_ready( # noqa: SLF001
584
584
  vector_index_name, index_manager.DEFAULT_SEARCH_TIMEOUT
585
585
  )
586
586
  logger.info(f"{log_prefix} Vector index '{vector_index_name}' now ready.")
@@ -634,7 +634,7 @@ async def _handle_hybrid_index(
634
634
  logger.info(
635
635
  f"{log_prefix} Text index '{text_index_name}' " f"not queryable yet; waiting."
636
636
  )
637
- await index_manager._wait_for_search_index_ready(
637
+ await index_manager._wait_for_search_index_ready( # noqa: SLF001
638
638
  text_index_name, index_manager.DEFAULT_SEARCH_TIMEOUT
639
639
  )
640
640
  logger.info(f"{log_prefix} Text index '{text_index_name}' now ready.")
@@ -184,15 +184,15 @@ async def check_engine_health(engine: Any | None) -> HealthCheckResult:
184
184
  message="MongoDBEngine not initialized",
185
185
  )
186
186
 
187
- if not engine._initialized:
188
- return HealthCheckResult(
189
- name="engine",
190
- status=HealthStatus.UNHEALTHY,
191
- message="MongoDBEngine not initialized",
192
- )
187
+ if not engine.initialized:
188
+ return HealthCheckResult(
189
+ name="engine",
190
+ status=HealthStatus.UNHEALTHY,
191
+ message="MongoDBEngine not initialized",
192
+ )
193
193
 
194
194
  # Check registered apps
195
- app_count = len(engine._apps)
195
+ app_count = len(engine.apps)
196
196
 
197
197
  return HealthCheckResult(
198
198
  name="engine",
@@ -133,8 +133,10 @@ register_message_handler("my_app", "realtime", handle_client_message)
133
133
 
134
134
  ### Authentication
135
135
 
136
- - Integrates with mdb_engine's JWT authentication system
137
- - Supports token via query parameter or cookie
136
+ - Uses ticket-based authentication (secure-by-default)
137
+ - Tickets are short-lived (10 seconds), single-use, and in-memory
138
+ - Exchange JWT for ticket via `/auth/ticket` endpoint
139
+ - Include ticket as query parameter (`?ticket=...`) or header (`X-WebSocket-Ticket`)
138
140
  - Respects app's `auth_policy` configuration
139
141
  - Can be overridden per endpoint
140
142
 
@@ -434,6 +436,11 @@ async def on_document_created(document):
434
436
  - **Simplicity**: Just declare in manifest.json, register handlers in code
435
437
  - **Flexibility**: Full two-way communication (broadcast + listen)
436
438
  - **Automatic**: Routes registered automatically during app registration
439
+ - **FastAPI Integration**: Uses FastAPI's `APIRouter` for WebSocket registration, ensuring:
440
+ - ✅ Full FastAPI feature support (dependency injection, OpenAPI docs, request/response models)
441
+ - ✅ Consistent behavior across single-app and multi-app modes
442
+ - ✅ Best practices compliance with FastAPI's recommended patterns
443
+ - ✅ Better maintainability through FastAPI abstractions
437
444
 
438
445
  ## Message Flow
439
446