mdb-engine 0.2.0__py3-none-any.whl → 0.2.3__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 (66) hide show
  1. mdb_engine/__init__.py +1 -1
  2. mdb_engine/auth/audit.py +40 -40
  3. mdb_engine/auth/base.py +3 -3
  4. mdb_engine/auth/casbin_factory.py +6 -6
  5. mdb_engine/auth/config_defaults.py +5 -5
  6. mdb_engine/auth/config_helpers.py +12 -12
  7. mdb_engine/auth/cookie_utils.py +9 -9
  8. mdb_engine/auth/csrf.py +9 -8
  9. mdb_engine/auth/decorators.py +7 -6
  10. mdb_engine/auth/dependencies.py +22 -21
  11. mdb_engine/auth/integration.py +9 -9
  12. mdb_engine/auth/jwt.py +9 -9
  13. mdb_engine/auth/middleware.py +4 -3
  14. mdb_engine/auth/oso_factory.py +6 -6
  15. mdb_engine/auth/provider.py +4 -4
  16. mdb_engine/auth/rate_limiter.py +12 -11
  17. mdb_engine/auth/restrictions.py +16 -15
  18. mdb_engine/auth/session_manager.py +11 -13
  19. mdb_engine/auth/shared_middleware.py +16 -15
  20. mdb_engine/auth/shared_users.py +20 -20
  21. mdb_engine/auth/token_lifecycle.py +10 -12
  22. mdb_engine/auth/token_store.py +4 -5
  23. mdb_engine/auth/users.py +51 -52
  24. mdb_engine/auth/utils.py +29 -33
  25. mdb_engine/cli/commands/generate.py +6 -6
  26. mdb_engine/cli/utils.py +4 -4
  27. mdb_engine/config.py +6 -7
  28. mdb_engine/core/app_registration.py +12 -12
  29. mdb_engine/core/app_secrets.py +1 -2
  30. mdb_engine/core/connection.py +3 -4
  31. mdb_engine/core/encryption.py +1 -2
  32. mdb_engine/core/engine.py +43 -44
  33. mdb_engine/core/manifest.py +59 -58
  34. mdb_engine/core/ray_integration.py +10 -9
  35. mdb_engine/core/seeding.py +3 -3
  36. mdb_engine/core/service_initialization.py +10 -9
  37. mdb_engine/core/types.py +40 -40
  38. mdb_engine/database/abstraction.py +15 -16
  39. mdb_engine/database/connection.py +40 -12
  40. mdb_engine/database/query_validator.py +8 -8
  41. mdb_engine/database/resource_limiter.py +7 -7
  42. mdb_engine/database/scoped_wrapper.py +51 -58
  43. mdb_engine/dependencies.py +14 -13
  44. mdb_engine/di/container.py +12 -13
  45. mdb_engine/di/providers.py +14 -13
  46. mdb_engine/di/scopes.py +5 -5
  47. mdb_engine/embeddings/dependencies.py +2 -2
  48. mdb_engine/embeddings/service.py +31 -43
  49. mdb_engine/exceptions.py +20 -20
  50. mdb_engine/indexes/helpers.py +11 -11
  51. mdb_engine/indexes/manager.py +9 -9
  52. mdb_engine/memory/service.py +30 -30
  53. mdb_engine/observability/health.py +10 -9
  54. mdb_engine/observability/logging.py +10 -10
  55. mdb_engine/observability/metrics.py +8 -7
  56. mdb_engine/repositories/base.py +25 -25
  57. mdb_engine/repositories/mongo.py +17 -17
  58. mdb_engine/repositories/unit_of_work.py +6 -6
  59. mdb_engine/routing/websockets.py +19 -18
  60. {mdb_engine-0.2.0.dist-info → mdb_engine-0.2.3.dist-info}/METADATA +8 -8
  61. mdb_engine-0.2.3.dist-info/RECORD +96 -0
  62. mdb_engine-0.2.0.dist-info/RECORD +0 -96
  63. {mdb_engine-0.2.0.dist-info → mdb_engine-0.2.3.dist-info}/WHEEL +0 -0
  64. {mdb_engine-0.2.0.dist-info → mdb_engine-0.2.3.dist-info}/entry_points.txt +0 -0
  65. {mdb_engine-0.2.0.dist-info → mdb_engine-0.2.3.dist-info}/licenses/LICENSE +0 -0
  66. {mdb_engine-0.2.0.dist-info → mdb_engine-0.2.3.dist-info}/top_level.txt +0 -0
@@ -9,8 +9,9 @@ This module is part of MDB_ENGINE - MongoDB Engine.
9
9
  import logging
10
10
  import os
11
11
  import uuid
12
+ from collections.abc import Mapping
12
13
  from datetime import datetime, timedelta
13
- from typing import Any, Dict, Mapping, Optional, Tuple
14
+ from typing import Any
14
15
 
15
16
  import jwt
16
17
  from fastapi import Cookie, Depends, HTTPException, Request, status
@@ -26,7 +27,7 @@ from .token_store import TokenBlacklist
26
27
 
27
28
  logger = logging.getLogger(__name__)
28
29
 
29
- _SECRET_KEY_CACHE: Optional[str] = None
30
+ _SECRET_KEY_CACHE: str | None = None
30
31
 
31
32
 
32
33
  def _get_secret_key() -> str:
@@ -94,7 +95,7 @@ def _get_secret_key_value() -> str:
94
95
  SECRET_KEY = _SecretKey()
95
96
 
96
97
 
97
- def _validate_next_url(next_url: Optional[str]) -> str:
98
+ def _validate_next_url(next_url: str | None) -> str:
98
99
  """
99
100
  Sanitizes a 'next' URL parameter to prevent Open Redirect vulnerabilities.
100
101
  """
@@ -127,7 +128,7 @@ async def get_authz_provider(request: Request) -> AuthorizationProvider:
127
128
  return provider
128
129
 
129
130
 
130
- async def get_token_blacklist(request: Request) -> Optional[TokenBlacklist]:
131
+ async def get_token_blacklist(request: Request) -> TokenBlacklist | None:
131
132
  """
132
133
  FastAPI Dependency: Retrieves token blacklist from app.state.
133
134
 
@@ -137,7 +138,7 @@ async def get_token_blacklist(request: Request) -> Optional[TokenBlacklist]:
137
138
  return blacklist
138
139
 
139
140
 
140
- async def get_session_manager(request: Request) -> Optional[SessionManager]:
141
+ async def get_session_manager(request: Request) -> SessionManager | None:
141
142
  """
142
143
  FastAPI Dependency: Retrieves session manager from app.state.
143
144
 
@@ -149,8 +150,8 @@ async def get_session_manager(request: Request) -> Optional[SessionManager]:
149
150
 
150
151
  async def get_current_user(
151
152
  request: Request,
152
- token: Optional[str] = Cookie(default=None),
153
- ) -> Optional[Dict[str, Any]]:
153
+ token: str | None = Cookie(default=None),
154
+ ) -> dict[str, Any] | None:
154
155
  """
155
156
  FastAPI Dependency: Decodes and validates the JWT stored in the 'token' cookie.
156
157
 
@@ -212,7 +213,7 @@ async def get_current_user(
212
213
  return None
213
214
 
214
215
 
215
- async def get_current_user_from_request(request: Request) -> Optional[Dict[str, Any]]:
216
+ async def get_current_user_from_request(request: Request) -> dict[str, Any] | None:
216
217
  """
217
218
  Helper function to get current user from a Request object.
218
219
  This is useful when you need to call get_current_user outside of FastAPI dependency injection.
@@ -289,8 +290,8 @@ async def get_current_user_from_request(request: Request) -> Optional[Dict[str,
289
290
 
290
291
  async def get_refresh_token(
291
292
  request: Request,
292
- refresh_token: Optional[str] = Cookie(default=None),
293
- ) -> Optional[Dict[str, Any]]:
293
+ refresh_token: str | None = Cookie(default=None),
294
+ ) -> dict[str, Any] | None:
294
295
  """
295
296
  FastAPI Dependency: Validates refresh token from cookie.
296
297
 
@@ -385,9 +386,9 @@ async def get_refresh_token(
385
386
 
386
387
 
387
388
  async def require_admin(
388
- user: Optional[Mapping[str, Any]] = Depends(get_current_user),
389
+ user: Mapping[str, Any] | None = Depends(get_current_user),
389
390
  authz: AuthorizationProvider = Depends(get_authz_provider),
390
- ) -> Dict[str, Any]:
391
+ ) -> dict[str, Any]:
391
392
  """
392
393
  FastAPI Dependency: Enforces admin privileges via the pluggable AuthZ provider.
393
394
  """
@@ -421,9 +422,9 @@ async def require_admin(
421
422
 
422
423
 
423
424
  async def require_admin_or_developer(
424
- user: Optional[Mapping[str, Any]] = Depends(get_current_user),
425
+ user: Mapping[str, Any] | None = Depends(get_current_user),
425
426
  authz: AuthorizationProvider = Depends(get_authz_provider),
426
- ) -> Dict[str, Any]:
427
+ ) -> dict[str, Any]:
427
428
  """
428
429
  FastAPI Dependency: Enforces admin OR developer privileges.
429
430
  Developers can upload apps, admins can upload any app.
@@ -482,8 +483,8 @@ async def require_admin_or_developer(
482
483
 
483
484
 
484
485
  async def get_current_user_or_redirect(
485
- request: Request, user: Optional[Mapping[str, Any]] = Depends(get_current_user)
486
- ) -> Dict[str, Any]:
486
+ request: Request, user: Mapping[str, Any] | None = Depends(get_current_user)
487
+ ) -> dict[str, Any]:
487
488
  """
488
489
  FastAPI Dependency: Enforces user authentication. Redirects to login if not authenticated.
489
490
  """
@@ -534,10 +535,10 @@ def require_permission(obj: str, act: str, force_login: bool = True):
534
535
 
535
536
  async def _check_permission(
536
537
  # 2. The type hint MUST be Optional now
537
- user: Optional[Dict[str, Any]] = Depends(user_dependency),
538
+ user: dict[str, Any] | None = Depends(user_dependency),
538
539
  # 3. Ask for the generic INTERFACE
539
540
  authz: AuthorizationProvider = Depends(get_authz_provider),
540
- ) -> Optional[Dict[str, Any]]: # 4. Return type is also Optional
541
+ ) -> dict[str, Any] | None: # 4. Return type is also Optional
541
542
  """Internal dependency function performing the AuthZ check."""
542
543
 
543
544
  # 5. Check for 'anonymous' if user is None
@@ -595,9 +596,9 @@ def require_permission(obj: str, act: str, force_login: bool = True):
595
596
 
596
597
  async def refresh_access_token(
597
598
  request: Request,
598
- refresh_token_payload: Dict[str, Any],
599
- device_info: Optional[Dict[str, Any]] = None,
600
- ) -> Optional[Tuple[str, str, Dict[str, Any]]]:
599
+ refresh_token_payload: dict[str, Any],
600
+ device_info: dict[str, Any] | None = None,
601
+ ) -> tuple[str, str, dict[str, Any]] | None:
601
602
  """
602
603
  Refresh an access token using a valid refresh token.
603
604
 
@@ -8,7 +8,7 @@ This module is part of MDB_ENGINE - MongoDB Engine.
8
8
 
9
9
  import logging
10
10
  import os
11
- from typing import Any, Dict, Optional
11
+ from typing import Any
12
12
 
13
13
  from fastapi import FastAPI
14
14
 
@@ -24,7 +24,7 @@ from .helpers import initialize_token_management
24
24
  logger = logging.getLogger(__name__)
25
25
 
26
26
  # Cache for auth configs
27
- _auth_config_cache: Dict[str, Dict[str, Any]] = {}
27
+ _auth_config_cache: dict[str, dict[str, Any]] = {}
28
28
 
29
29
 
30
30
  def _has_cors_middleware(app: FastAPI) -> bool:
@@ -57,7 +57,7 @@ def _has_cors_middleware(app: FastAPI) -> bool:
57
57
  return False
58
58
 
59
59
 
60
- def invalidate_auth_config_cache(slug_id: Optional[str] = None) -> None:
60
+ def invalidate_auth_config_cache(slug_id: str | None = None) -> None:
61
61
  """
62
62
  Invalidate auth config cache for a specific app or all apps.
63
63
 
@@ -72,7 +72,7 @@ def invalidate_auth_config_cache(slug_id: Optional[str] = None) -> None:
72
72
  logger.debug("Invalidated entire auth config cache")
73
73
 
74
74
 
75
- async def get_auth_config(slug_id: str, engine) -> Dict[str, Any]:
75
+ async def get_auth_config(slug_id: str, engine) -> dict[str, Any]:
76
76
  """
77
77
  Retrieve authentication configuration from manifest.
78
78
 
@@ -121,7 +121,7 @@ async def get_auth_config(slug_id: str, engine) -> Dict[str, Any]:
121
121
 
122
122
 
123
123
  async def _setup_authorization_provider(
124
- app: FastAPI, engine, slug_id: str, config: Dict[str, Any]
124
+ app: FastAPI, engine, slug_id: str, config: dict[str, Any]
125
125
  ) -> None:
126
126
  """Set up authorization provider (Casbin/OSO/custom) from manifest."""
127
127
  auth = config.get("auth", {})
@@ -181,7 +181,7 @@ async def _setup_authorization_provider(
181
181
  logger.info(f"Custom provider specified for {slug_id} - manual setup required")
182
182
 
183
183
 
184
- async def _setup_demo_users(app: FastAPI, engine, slug_id: str, config: Dict[str, Any]) -> list:
184
+ async def _setup_demo_users(app: FastAPI, engine, slug_id: str, config: dict[str, Any]) -> list:
185
185
  """Set up demo users and link with OSO roles if applicable."""
186
186
  auth = config.get("auth", {})
187
187
  users_config = auth.get("users", {})
@@ -325,7 +325,7 @@ async def _setup_demo_users(app: FastAPI, engine, slug_id: str, config: Dict[str
325
325
 
326
326
 
327
327
  async def _setup_token_management(
328
- app: FastAPI, engine, slug_id: str, token_management: Dict[str, Any]
328
+ app: FastAPI, engine, slug_id: str, token_management: dict[str, Any]
329
329
  ) -> None:
330
330
  """Initialize token management (blacklist and session manager)."""
331
331
  if token_management.get("auto_setup", True):
@@ -356,7 +356,7 @@ async def _setup_token_management(
356
356
 
357
357
 
358
358
  async def _setup_security_middleware(
359
- app: FastAPI, slug_id: str, security_config: Dict[str, Any]
359
+ app: FastAPI, slug_id: str, security_config: dict[str, Any]
360
360
  ) -> None:
361
361
  """Set up security middleware (if not already added)."""
362
362
  if security_config.get("csrf_protection", True) or security_config.get("require_https", False):
@@ -392,7 +392,7 @@ async def _setup_security_middleware(
392
392
 
393
393
 
394
394
  async def _setup_cors_and_observability(
395
- app: FastAPI, engine, slug_id: str, config: Dict[str, Any]
395
+ app: FastAPI, engine, slug_id: str, config: dict[str, Any]
396
396
  ) -> None:
397
397
  """Set up CORS and observability configs and middleware."""
398
398
  # Get manifest data first if available
mdb_engine/auth/jwt.py CHANGED
@@ -10,7 +10,7 @@ This module is part of MDB_ENGINE - MongoDB Engine.
10
10
  import logging
11
11
  import uuid
12
12
  from datetime import datetime, timedelta
13
- from typing import Any, Dict, Optional, Tuple
13
+ from typing import Any
14
14
 
15
15
  import jwt
16
16
 
@@ -21,7 +21,7 @@ from ..constants import CURRENT_TOKEN_VERSION
21
21
  logger = logging.getLogger(__name__)
22
22
 
23
23
 
24
- def decode_jwt_token(token: Any, secret_key: str) -> Dict[str, Any]:
24
+ def decode_jwt_token(token: Any, secret_key: str) -> dict[str, Any]:
25
25
  """
26
26
  Helper function to decode JWT tokens with automatic fallback to bytes format.
27
27
 
@@ -74,7 +74,7 @@ def decode_jwt_token(token: Any, secret_key: str) -> Dict[str, Any]:
74
74
 
75
75
 
76
76
  def encode_jwt_token(
77
- payload: Dict[str, Any], secret_key: str, expires_in: Optional[int] = None
77
+ payload: dict[str, Any], secret_key: str, expires_in: int | None = None
78
78
  ) -> str:
79
79
  """
80
80
  Encode a JWT token with enhanced claims.
@@ -123,12 +123,12 @@ def encode_jwt_token(
123
123
 
124
124
 
125
125
  def generate_token_pair(
126
- user_data: Dict[str, Any],
126
+ user_data: dict[str, Any],
127
127
  secret_key: str,
128
- device_info: Optional[Dict[str, Any]] = None,
129
- access_token_ttl: Optional[int] = None,
130
- refresh_token_ttl: Optional[int] = None,
131
- ) -> Tuple[str, str, Dict[str, Any]]:
128
+ device_info: dict[str, Any] | None = None,
129
+ access_token_ttl: int | None = None,
130
+ refresh_token_ttl: int | None = None,
131
+ ) -> tuple[str, str, dict[str, Any]]:
132
132
  """
133
133
  Generate a pair of access and refresh tokens.
134
134
 
@@ -190,7 +190,7 @@ def generate_token_pair(
190
190
  return access_token, refresh_token, token_metadata
191
191
 
192
192
 
193
- def extract_token_metadata(token: str, secret_key: str) -> Optional[Dict[str, Any]]:
193
+ def extract_token_metadata(token: str, secret_key: str) -> dict[str, Any] | None:
194
194
  """
195
195
  Extract metadata from a token without full validation.
196
196
 
@@ -15,7 +15,8 @@ Security Features:
15
15
  import logging
16
16
  import os
17
17
  import secrets
18
- from typing import Any, Awaitable, Callable, Dict, Optional
18
+ from collections.abc import Awaitable, Callable
19
+ from typing import Any
19
20
 
20
21
  from fastapi import HTTPException, Request, Response, status
21
22
  from fastapi.responses import RedirectResponse
@@ -53,7 +54,7 @@ class SecurityMiddleware(BaseHTTPMiddleware):
53
54
  require_https: bool = False,
54
55
  csrf_protection: bool = True,
55
56
  security_headers: bool = True,
56
- hsts_config: Optional[Dict[str, Any]] = None,
57
+ hsts_config: dict[str, Any] | None = None,
57
58
  ):
58
59
  """
59
60
  Initialize security middleware.
@@ -234,7 +235,7 @@ class StaleSessionMiddleware(BaseHTTPMiddleware):
234
235
  "session_cookie_name", "app_session"
235
236
  )
236
237
  cookie_name = f"{session_cookie_name}_{self.slug_id}"
237
- except (AttributeError, KeyError, TypeError, Exception):
238
+ except (AttributeError, KeyError, TypeError):
238
239
  pass
239
240
 
240
241
  # Final fallback to default naming convention
@@ -11,7 +11,7 @@ from __future__ import annotations
11
11
 
12
12
  import logging
13
13
  import os
14
- from typing import TYPE_CHECKING, Any, Optional
14
+ from typing import TYPE_CHECKING, Any
15
15
 
16
16
  if TYPE_CHECKING:
17
17
  from .provider import OsoAdapter
@@ -20,8 +20,8 @@ logger = logging.getLogger(__name__)
20
20
 
21
21
 
22
22
  async def create_oso_cloud_client(
23
- api_key: Optional[str] = None,
24
- url: Optional[str] = None,
23
+ api_key: str | None = None,
24
+ url: str | None = None,
25
25
  max_retries: int = 3,
26
26
  retry_delay: float = 2.0,
27
27
  ) -> Any:
@@ -114,8 +114,8 @@ async def create_oso_cloud_client(
114
114
 
115
115
  async def setup_initial_oso_facts(
116
116
  authz_provider: OsoAdapter,
117
- initial_roles: Optional[list[dict[str, Any]]] = None,
118
- initial_policies: Optional[list[dict[str, Any]]] = None,
117
+ initial_roles: list[dict[str, Any]] | None = None,
118
+ initial_policies: list[dict[str, Any]] | None = None,
119
119
  ) -> None:
120
120
  """
121
121
  Set up initial roles and policies in OSO Cloud.
@@ -149,7 +149,7 @@ async def setup_initial_oso_facts(
149
149
 
150
150
  async def initialize_oso_from_manifest(
151
151
  engine, app_slug: str, auth_config: dict[str, Any]
152
- ) -> Optional[OsoAdapter]:
152
+ ) -> OsoAdapter | None:
153
153
  """
154
154
  Initialize OSO Cloud provider from manifest configuration.
155
155
 
@@ -11,7 +11,7 @@ from __future__ import annotations # MUST be first import for string type hints
11
11
  import asyncio
12
12
  import logging
13
13
  import time
14
- from typing import TYPE_CHECKING, Any, Optional, Protocol
14
+ from typing import TYPE_CHECKING, Any, Protocol
15
15
 
16
16
  from ..constants import AUTHZ_CACHE_TTL, MAX_CACHE_SIZE
17
17
 
@@ -38,7 +38,7 @@ class AuthorizationProvider(Protocol):
38
38
  subject: str,
39
39
  resource: str,
40
40
  action: str,
41
- user_object: Optional[dict[str, Any]] = None,
41
+ user_object: dict[str, Any] | None = None,
42
42
  ) -> bool:
43
43
  """
44
44
  Checks if a subject is allowed to perform an action on a resource.
@@ -91,7 +91,7 @@ class CasbinAdapter(BaseAuthorizationProvider):
91
91
  subject: str,
92
92
  resource: str,
93
93
  action: str,
94
- user_object: Optional[dict[str, Any]] = None,
94
+ user_object: dict[str, Any] | None = None,
95
95
  ) -> bool:
96
96
  """
97
97
  Check authorization using Casbin's enforce method.
@@ -321,7 +321,7 @@ class OsoAdapter(BaseAuthorizationProvider):
321
321
  subject: str,
322
322
  resource: str,
323
323
  action: str,
324
- user_object: Optional[dict[str, Any]] = None,
324
+ user_object: dict[str, Any] | None = None,
325
325
  ) -> bool:
326
326
  """
327
327
  Check authorization using OSO's authorize method.
@@ -33,10 +33,11 @@ Usage:
33
33
  import logging
34
34
  import time
35
35
  from collections import defaultdict
36
+ from collections.abc import Callable
36
37
  from dataclasses import dataclass
37
38
  from datetime import datetime, timedelta
38
39
  from functools import wraps
39
- from typing import Any, Callable, Dict, List, Optional, Tuple
40
+ from typing import Any
40
41
 
41
42
  from pymongo.errors import OperationFailure
42
43
  from starlette.middleware.base import BaseHTTPMiddleware
@@ -53,7 +54,7 @@ class RateLimit:
53
54
  max_attempts: int = 5
54
55
  window_seconds: int = 300 # 5 minutes
55
56
 
56
- def to_dict(self) -> Dict[str, int]:
57
+ def to_dict(self) -> dict[str, int]:
57
58
  return {
58
59
  "max_attempts": self.max_attempts,
59
60
  "window_seconds": self.window_seconds,
@@ -61,7 +62,7 @@ class RateLimit:
61
62
 
62
63
 
63
64
  # Default rate limits for auth endpoints
64
- DEFAULT_AUTH_RATE_LIMITS: Dict[str, RateLimit] = {
65
+ DEFAULT_AUTH_RATE_LIMITS: dict[str, RateLimit] = {
65
66
  "/login": RateLimit(max_attempts=5, window_seconds=300),
66
67
  "/register": RateLimit(max_attempts=3, window_seconds=3600),
67
68
  "/logout": RateLimit(max_attempts=10, window_seconds=60),
@@ -78,7 +79,7 @@ class InMemoryRateLimitStore:
78
79
 
79
80
  def __init__(self):
80
81
  # Structure: {identifier: [(timestamp, count), ...]}
81
- self._storage: Dict[str, List[Tuple[float, int]]] = defaultdict(list)
82
+ self._storage: dict[str, list[tuple[float, int]]] = defaultdict(list)
82
83
 
83
84
  async def record_attempt(
84
85
  self,
@@ -282,8 +283,8 @@ class AuthRateLimitMiddleware(BaseHTTPMiddleware):
282
283
  def __init__(
283
284
  self,
284
285
  app: Callable,
285
- limits: Optional[Dict[str, RateLimit]] = None,
286
- store: Optional[InMemoryRateLimitStore] = None,
286
+ limits: dict[str, RateLimit] | None = None,
287
+ store: InMemoryRateLimitStore | None = None,
287
288
  include_email_in_key: bool = True,
288
289
  ):
289
290
  """
@@ -377,7 +378,7 @@ class AuthRateLimitMiddleware(BaseHTTPMiddleware):
377
378
 
378
379
  return "unknown"
379
380
 
380
- async def _extract_email(self, request: Request) -> Optional[str]:
381
+ async def _extract_email(self, request: Request) -> str | None:
381
382
  """Try to extract email from request body."""
382
383
  try:
383
384
  # Only try to read body for JSON requests
@@ -410,8 +411,8 @@ class AuthRateLimitMiddleware(BaseHTTPMiddleware):
410
411
 
411
412
 
412
413
  def create_rate_limit_middleware(
413
- manifest_auth: Dict[str, Any],
414
- store: Optional[InMemoryRateLimitStore] = None,
414
+ manifest_auth: dict[str, Any],
415
+ store: InMemoryRateLimitStore | None = None,
415
416
  ) -> type:
416
417
  """
417
418
  Factory function to create rate limit middleware from manifest config.
@@ -435,7 +436,7 @@ def create_rate_limit_middleware(
435
436
  """
436
437
  rate_limits_config = manifest_auth.get("rate_limits", {})
437
438
 
438
- limits: Dict[str, RateLimit] = {}
439
+ limits: dict[str, RateLimit] = {}
439
440
  for path, config in rate_limits_config.items():
440
441
  limits[path] = RateLimit(
441
442
  max_attempts=config.get("max_attempts", 5),
@@ -456,7 +457,7 @@ def create_rate_limit_middleware(
456
457
  def rate_limit(
457
458
  max_attempts: int = 5,
458
459
  window_seconds: int = 300,
459
- key_func: Optional[Callable[[Request], str]] = None,
460
+ key_func: Callable[[Request], str] | None = None,
460
461
  ):
461
462
  """
462
463
  Decorator for rate limiting individual endpoints.
@@ -16,7 +16,8 @@ This module is part of MDB_ENGINE - MongoDB Engine.
16
16
  """
17
17
 
18
18
  import logging
19
- from typing import Any, Awaitable, Callable, Dict, Optional
19
+ from collections.abc import Awaitable, Callable
20
+ from typing import Any
20
21
 
21
22
  from fastapi import HTTPException, Request, status
22
23
 
@@ -27,7 +28,7 @@ from .users import get_app_user
27
28
  logger = logging.getLogger(__name__)
28
29
 
29
30
 
30
- def is_demo_user(user: Optional[Dict[str, Any]] = None, email: Optional[str] = None) -> bool:
31
+ def is_demo_user(user: dict[str, Any] | None = None, email: str | None = None) -> bool:
31
32
  """
32
33
  Check if a user is a demo user.
33
34
 
@@ -55,7 +56,7 @@ def is_demo_user(user: Optional[Dict[str, Any]] = None, email: Optional[str] = N
55
56
  return False
56
57
 
57
58
 
58
- async def _get_platform_user(request: Request) -> Optional[Dict[str, Any]]:
59
+ async def _get_platform_user(request: Request) -> dict[str, Any] | None:
59
60
  """Try to get user from platform authentication."""
60
61
  try:
61
62
  platform_user = await get_current_user_from_request(request)
@@ -70,9 +71,9 @@ async def _get_platform_user(request: Request) -> Optional[Dict[str, Any]]:
70
71
  async def _get_sub_auth_user(
71
72
  request: Request,
72
73
  slug_id: str,
73
- get_app_config_func: Callable[[Request, str, Dict], Awaitable[Dict]],
74
+ get_app_config_func: Callable[[Request, str, dict], Awaitable[dict]],
74
75
  get_app_db_func: Callable[[Request], Awaitable[Any]],
75
- ) -> Optional[Dict[str, Any]]:
76
+ ) -> dict[str, Any] | None:
76
77
  """Try to get user from sub-authentication."""
77
78
  try:
78
79
  db = await get_app_db_func(request)
@@ -104,9 +105,9 @@ async def _get_sub_auth_user(
104
105
  async def _get_authenticated_user(
105
106
  request: Request,
106
107
  slug_id: str,
107
- get_app_config_func: Optional[Callable[[Request, str, Dict], Awaitable[Dict]]],
108
- get_app_db_func: Optional[Callable[[Request], Awaitable[Any]]],
109
- ) -> Optional[Dict[str, Any]]:
108
+ get_app_config_func: Callable[[Request, str, dict], Awaitable[dict]] | None,
109
+ get_app_db_func: Callable[[Request], Awaitable[Any]] | None,
110
+ ) -> dict[str, Any] | None:
110
111
  """Get authenticated user from platform or sub-auth."""
111
112
  # Try platform auth first
112
113
  user = await _get_platform_user(request)
@@ -132,8 +133,8 @@ def _validate_slug_id(request: Request) -> str:
132
133
 
133
134
 
134
135
  def _validate_dependencies(
135
- get_app_config_func: Optional[Callable[[Request, str, Dict], Awaitable[Dict]]],
136
- get_app_db_func: Optional[Callable[[Request], Awaitable[Any]]],
136
+ get_app_config_func: Callable[[Request, str, dict], Awaitable[dict]] | None,
137
+ get_app_db_func: Callable[[Request], Awaitable[Any]] | None,
137
138
  ) -> None:
138
139
  """Validate that required dependencies are provided."""
139
140
  if not get_app_db_func:
@@ -152,9 +153,9 @@ def _validate_dependencies(
152
153
 
153
154
  async def require_non_demo_user(
154
155
  request: Request,
155
- get_app_config_func: Optional[Callable[[Request, str, Dict], Awaitable[Dict]]] = None,
156
- get_app_db_func: Optional[Callable[[Request], Awaitable[Any]]] = None,
157
- ) -> Dict[str, Any]:
156
+ get_app_config_func: Callable[[Request, str, dict], Awaitable[dict]] | None = None,
157
+ get_app_db_func: Callable[[Request], Awaitable[Any]] | None = None,
158
+ ) -> dict[str, Any]:
158
159
  """
159
160
  FastAPI dependency that blocks demo users from accessing an endpoint.
160
161
 
@@ -205,8 +206,8 @@ async def require_non_demo_user(
205
206
 
206
207
  async def block_demo_users(
207
208
  request: Request,
208
- get_app_config_func: Optional[Callable[[Request, str, Dict], Awaitable[Dict]]] = None,
209
- get_app_db_func: Optional[Callable[[Request], Awaitable[Any]]] = None,
209
+ get_app_config_func: Callable[[Request, str, dict], Awaitable[dict]] | None = None,
210
+ get_app_db_func: Callable[[Request], Awaitable[Any]] | None = None,
210
211
  ):
211
212
  """
212
213
  FastAPI dependency that blocks demo users and returns an error response.
@@ -8,7 +8,7 @@ This module is part of MDB_ENGINE - MongoDB Engine.
8
8
 
9
9
  import logging
10
10
  from datetime import datetime, timedelta
11
- from typing import Any, Dict, List, Optional
11
+ from typing import Any
12
12
 
13
13
  from bson.objectid import ObjectId
14
14
 
@@ -96,10 +96,10 @@ class SessionManager:
96
96
  user_id: str,
97
97
  device_id: str,
98
98
  refresh_jti: str,
99
- device_info: Optional[Dict[str, Any]] = None,
100
- ip_address: Optional[str] = None,
101
- session_fingerprint: Optional[str] = None,
102
- ) -> Optional[Dict[str, Any]]:
99
+ device_info: dict[str, Any] | None = None,
100
+ ip_address: str | None = None,
101
+ session_fingerprint: str | None = None,
102
+ ) -> dict[str, Any] | None:
103
103
  """
104
104
  Create a new user session.
105
105
 
@@ -173,7 +173,7 @@ class SessionManager:
173
173
  return None
174
174
 
175
175
  async def update_session_activity(
176
- self, refresh_jti: str, ip_address: Optional[str] = None
176
+ self, refresh_jti: str, ip_address: str | None = None
177
177
  ) -> bool:
178
178
  """
179
179
  Update session last_seen timestamp (activity tracking).
@@ -207,7 +207,7 @@ class SessionManager:
207
207
  logger.error(f"Error updating session activity for {refresh_jti}: {e}", exc_info=True)
208
208
  return False
209
209
 
210
- async def get_session_by_refresh_token(self, refresh_jti: str) -> Optional[Dict[str, Any]]:
210
+ async def get_session_by_refresh_token(self, refresh_jti: str) -> dict[str, Any] | None:
211
211
  """
212
212
  Get session by refresh token JWT ID.
213
213
 
@@ -234,7 +234,7 @@ class SessionManager:
234
234
  return None
235
235
 
236
236
  async def validate_session_fingerprint(
237
- self, refresh_jti: str, current_fingerprint: str, strict: Optional[bool] = None
237
+ self, refresh_jti: str, current_fingerprint: str, strict: bool | None = None
238
238
  ) -> bool:
239
239
  """
240
240
  Validate session fingerprint matches stored fingerprint.
@@ -288,7 +288,7 @@ class SessionManager:
288
288
 
289
289
  async def get_user_sessions(
290
290
  self, user_id: str, active_only: bool = True
291
- ) -> List[Dict[str, Any]]:
291
+ ) -> list[dict[str, Any]]:
292
292
  """
293
293
  Get all sessions for a user.
294
294
 
@@ -354,9 +354,7 @@ class SessionManager:
354
354
  logger.error(f"Error revoking session {session_id}: {e}", exc_info=True)
355
355
  return False
356
356
 
357
- async def revoke_user_sessions(
358
- self, user_id: str, exclude_device_id: Optional[str] = None
359
- ) -> int:
357
+ async def revoke_user_sessions(self, user_id: str, exclude_device_id: str | None = None) -> int:
360
358
  """
361
359
  Revoke all sessions for a user.
362
360
 
@@ -390,7 +388,7 @@ class SessionManager:
390
388
  logger.error(f"Error revoking sessions for user {user_id}: {e}", exc_info=True)
391
389
  return 0
392
390
 
393
- async def cleanup_inactive_sessions(self, user_id: Optional[str] = None) -> int:
391
+ async def cleanup_inactive_sessions(self, user_id: str | None = None) -> int:
394
392
  """
395
393
  Clean up inactive sessions (beyond inactivity timeout).
396
394