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
@@ -32,7 +32,8 @@ Manual usage:
32
32
  import fnmatch
33
33
  import hashlib
34
34
  import logging
35
- from typing import Any, Callable, Dict, List, Optional
35
+ from collections.abc import Callable
36
+ from typing import Any
36
37
 
37
38
  import jwt
38
39
  from starlette.middleware.base import BaseHTTPMiddleware
@@ -49,7 +50,7 @@ AUTH_HEADER_NAME = "Authorization"
49
50
  AUTH_HEADER_PREFIX = "Bearer "
50
51
 
51
52
 
52
- def _get_client_ip(request: Request) -> Optional[str]:
53
+ def _get_client_ip(request: Request) -> str | None:
53
54
  """Extract client IP address from request, handling proxies."""
54
55
  # Check X-Forwarded-For header (behind load balancer/proxy)
55
56
  forwarded_for = request.headers.get("x-forwarded-for")
@@ -99,12 +100,12 @@ class SharedAuthMiddleware(BaseHTTPMiddleware):
99
100
  def __init__(
100
101
  self,
101
102
  app: Callable,
102
- user_pool: Optional[SharedUserPool],
103
+ user_pool: SharedUserPool | None,
103
104
  app_slug: str,
104
- require_role: Optional[str] = None,
105
- public_routes: Optional[List[str]] = None,
106
- role_hierarchy: Optional[Dict[str, List[str]]] = None,
107
- session_binding: Optional[Dict[str, Any]] = None,
105
+ require_role: str | None = None,
106
+ public_routes: list[str] | None = None,
107
+ role_hierarchy: dict[str, list[str]] | None = None,
108
+ session_binding: dict[str, Any] | None = None,
108
109
  cookie_name: str = AUTH_COOKIE_NAME,
109
110
  header_name: str = AUTH_HEADER_NAME,
110
111
  header_prefix: str = AUTH_HEADER_PREFIX,
@@ -145,7 +146,7 @@ class SharedAuthMiddleware(BaseHTTPMiddleware):
145
146
  f"session_binding={bool(self._session_binding)})"
146
147
  )
147
148
 
148
- def get_user_pool(self, request: Request) -> Optional[SharedUserPool]:
149
+ def get_user_pool(self, request: Request) -> SharedUserPool | None:
149
150
  """Get the user pool instance. Override in subclasses for lazy loading."""
150
151
  return self._user_pool
151
152
 
@@ -219,7 +220,7 @@ class SharedAuthMiddleware(BaseHTTPMiddleware):
219
220
  self,
220
221
  request: Request,
221
222
  token: str,
222
- ) -> Optional[str]:
223
+ ) -> str | None:
223
224
  """
224
225
  Validate session binding claims in token.
225
226
 
@@ -260,7 +261,7 @@ class SharedAuthMiddleware(BaseHTTPMiddleware):
260
261
  logger.warning(f"Error validating session binding: {e}")
261
262
  return None # Don't reject for binding check errors
262
263
 
263
- def _extract_token(self, request: Request) -> Optional[str]:
264
+ def _extract_token(self, request: Request) -> str | None:
264
265
  """Extract JWT token from cookie or header."""
265
266
  # Try cookie first
266
267
  token = request.cookies.get(self._cookie_name)
@@ -317,7 +318,7 @@ class SharedAuthMiddleware(BaseHTTPMiddleware):
317
318
  def create_shared_auth_middleware(
318
319
  user_pool: SharedUserPool,
319
320
  app_slug: str,
320
- manifest_auth: Dict[str, Any],
321
+ manifest_auth: dict[str, Any],
321
322
  ) -> type:
322
323
  """
323
324
  Factory function to create SharedAuthMiddleware configured from manifest.
@@ -365,7 +366,7 @@ def create_shared_auth_middleware(
365
366
 
366
367
  def create_shared_auth_middleware_lazy(
367
368
  app_slug: str,
368
- manifest_auth: Dict[str, Any],
369
+ manifest_auth: dict[str, Any],
369
370
  ) -> type:
370
371
  """
371
372
  Factory function to create a lazy SharedAuthMiddleware that reads user_pool from app.state.
@@ -441,7 +442,7 @@ def create_shared_auth_middleware_lazy(
441
442
  request.state.user_roles = []
442
443
 
443
444
  # Get user_pool from app.state (set during lifespan)
444
- user_pool: Optional[SharedUserPool] = getattr(request.app.state, "user_pool", None)
445
+ user_pool: SharedUserPool | None = getattr(request.app.state, "user_pool", None)
445
446
 
446
447
  if user_pool is None:
447
448
  # User pool not initialized yet, skip auth
@@ -498,7 +499,7 @@ def create_shared_auth_middleware_lazy(
498
499
 
499
500
  return await call_next(request)
500
501
 
501
- def _extract_token(self, request: Request) -> Optional[str]:
502
+ def _extract_token(self, request: Request) -> str | None:
502
503
  """Extract JWT token from cookie or header."""
503
504
  # Try cookie first
504
505
  token = request.cookies.get(self._cookie_name)
@@ -555,7 +556,7 @@ def create_shared_auth_middleware_lazy(
555
556
  self,
556
557
  request: Request,
557
558
  token: str,
558
- ) -> Optional[str]:
559
+ ) -> str | None:
559
560
  """
560
561
  Validate session binding claims in token.
561
562
 
@@ -45,7 +45,7 @@ import logging
45
45
  import os
46
46
  import secrets
47
47
  from datetime import datetime, timedelta
48
- from typing import TYPE_CHECKING, Any, Dict, List, Optional
48
+ from typing import TYPE_CHECKING, Any
49
49
 
50
50
  import bcrypt
51
51
  import jwt
@@ -114,8 +114,8 @@ class SharedUserPool:
114
114
  def __init__(
115
115
  self,
116
116
  mongo_db: AsyncIOMotorDatabase,
117
- jwt_secret: Optional[str] = None,
118
- jwt_public_key: Optional[str] = None,
117
+ jwt_secret: str | None = None,
118
+ jwt_public_key: str | None = None,
119
119
  jwt_algorithm: str = DEFAULT_JWT_ALGORITHM,
120
120
  token_expiry_hours: int = DEFAULT_TOKEN_EXPIRY_HOURS,
121
121
  allow_insecure_dev: bool = False,
@@ -285,9 +285,9 @@ class SharedUserPool:
285
285
  self,
286
286
  email: str,
287
287
  password: str,
288
- app_roles: Optional[Dict[str, List[str]]] = None,
288
+ app_roles: dict[str, list[str]] | None = None,
289
289
  is_active: bool = True,
290
- ) -> Dict[str, Any]:
290
+ ) -> dict[str, Any]:
291
291
  """
292
292
  Create a new shared user.
293
293
 
@@ -332,10 +332,10 @@ class SharedUserPool:
332
332
  self,
333
333
  email: str,
334
334
  password: str,
335
- ip_address: Optional[str] = None,
336
- fingerprint: Optional[str] = None,
337
- session_binding: Optional[Dict[str, Any]] = None,
338
- ) -> Optional[str]:
335
+ ip_address: str | None = None,
336
+ fingerprint: str | None = None,
337
+ session_binding: dict[str, Any] | None = None,
338
+ ) -> str | None:
339
339
  """
340
340
  Authenticate user and return JWT token.
341
341
 
@@ -390,7 +390,7 @@ class SharedUserPool:
390
390
  logger.info(f"User '{email}' authenticated successfully")
391
391
  return token
392
392
 
393
- async def validate_token(self, token: str) -> Optional[Dict[str, Any]]:
393
+ async def validate_token(self, token: str) -> dict[str, Any] | None:
394
394
  """
395
395
  Validate JWT token and return user data.
396
396
 
@@ -547,7 +547,7 @@ class SharedUserPool:
547
547
  )
548
548
  logger.info(f"All tokens revoked for user {user_id}: {reason}")
549
549
 
550
- async def get_user_by_email(self, email: str) -> Optional[Dict[str, Any]]:
550
+ async def get_user_by_email(self, email: str) -> dict[str, Any] | None:
551
551
  """Get user by email."""
552
552
  user = await self._collection.find_one({"email": email})
553
553
  if user:
@@ -558,7 +558,7 @@ class SharedUserPool:
558
558
  self,
559
559
  email: str,
560
560
  app_slug: str,
561
- roles: List[str],
561
+ roles: list[str],
562
562
  ) -> bool:
563
563
  """
564
564
  Update a user's roles for a specific app.
@@ -638,10 +638,10 @@ class SharedUserPool:
638
638
 
639
639
  @staticmethod
640
640
  def user_has_role(
641
- user: Dict[str, Any],
641
+ user: dict[str, Any],
642
642
  app_slug: str,
643
643
  required_role: str,
644
- role_hierarchy: Optional[Dict[str, List[str]]] = None,
644
+ role_hierarchy: dict[str, list[str]] | None = None,
645
645
  ) -> bool:
646
646
  """
647
647
  Check if user has a required role for an app.
@@ -673,16 +673,16 @@ class SharedUserPool:
673
673
 
674
674
  @staticmethod
675
675
  def get_user_roles_for_app(
676
- user: Dict[str, Any],
676
+ user: dict[str, Any],
677
677
  app_slug: str,
678
- ) -> List[str]:
678
+ ) -> list[str]:
679
679
  """Get user's roles for a specific app."""
680
680
  return user.get("app_roles", {}).get(app_slug, [])
681
681
 
682
682
  def _generate_token(
683
683
  self,
684
- user: Dict[str, Any],
685
- extra_claims: Optional[Dict[str, Any]] = None,
684
+ user: dict[str, Any],
685
+ extra_claims: dict[str, Any] | None = None,
686
686
  ) -> str:
687
687
  """
688
688
  Generate JWT token for user with unique JTI for revocation support.
@@ -718,7 +718,7 @@ class SharedUserPool:
718
718
  return jwt.encode(payload, self._signing_key, algorithm=self._jwt_algorithm)
719
719
 
720
720
  @staticmethod
721
- def _sanitize_user(user: Dict[str, Any]) -> Dict[str, Any]:
721
+ def _sanitize_user(user: dict[str, Any]) -> dict[str, Any]:
722
722
  """Remove sensitive fields from user document."""
723
723
  sanitized = dict(user)
724
724
  sanitized.pop("password_hash", None)
@@ -727,7 +727,7 @@ class SharedUserPool:
727
727
  sanitized["_id"] = str(sanitized["_id"])
728
728
  return sanitized
729
729
 
730
- def get_secure_cookie_config(self, request: "Request") -> Dict[str, Any]:
730
+ def get_secure_cookie_config(self, request: "Request") -> dict[str, Any]:
731
731
  """
732
732
  Get secure cookie settings for auth tokens.
733
733
 
@@ -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
11
- from typing import Any, Dict, Optional
11
+ from typing import Any
12
12
 
13
13
  from ..config import ACCESS_TOKEN_TTL as CONFIG_ACCESS_TTL
14
14
  from .jwt import extract_token_metadata
@@ -16,7 +16,7 @@ from .jwt import extract_token_metadata
16
16
  logger = logging.getLogger(__name__)
17
17
 
18
18
 
19
- def get_token_expiry_time(token: str, secret_key: str) -> Optional[datetime]:
19
+ def get_token_expiry_time(token: str, secret_key: str) -> datetime | None:
20
20
  """
21
21
  Get the expiration time of a token.
22
22
 
@@ -31,7 +31,7 @@ def get_token_expiry_time(token: str, secret_key: str) -> Optional[datetime]:
31
31
  metadata = extract_token_metadata(token, secret_key)
32
32
  if metadata and metadata.get("exp"):
33
33
  exp_timestamp = metadata["exp"]
34
- if isinstance(exp_timestamp, (int, float)):
34
+ if isinstance(exp_timestamp, int | float):
35
35
  return datetime.utcfromtimestamp(exp_timestamp)
36
36
  return None
37
37
  except (ValueError, TypeError, AttributeError, KeyError) as e:
@@ -40,7 +40,7 @@ def get_token_expiry_time(token: str, secret_key: str) -> Optional[datetime]:
40
40
 
41
41
 
42
42
  def is_token_expiring_soon(
43
- token: str, secret_key: str, threshold_seconds: Optional[int] = None
43
+ token: str, secret_key: str, threshold_seconds: int | None = None
44
44
  ) -> bool:
45
45
  """
46
46
  Check if a token is expiring soon.
@@ -68,9 +68,7 @@ def is_token_expiring_soon(
68
68
  return False
69
69
 
70
70
 
71
- def should_refresh_token(
72
- token: str, secret_key: str, refresh_threshold: Optional[int] = None
73
- ) -> bool:
71
+ def should_refresh_token(token: str, secret_key: str, refresh_threshold: int | None = None) -> bool:
74
72
  """
75
73
  Determine if a token should be refreshed.
76
74
 
@@ -97,7 +95,7 @@ def should_refresh_token(
97
95
  return False
98
96
 
99
97
 
100
- def get_token_age(token: str, secret_key: str) -> Optional[float]:
98
+ def get_token_age(token: str, secret_key: str) -> float | None:
101
99
  """
102
100
  Get the age of a token in seconds.
103
101
 
@@ -112,7 +110,7 @@ def get_token_age(token: str, secret_key: str) -> Optional[float]:
112
110
  metadata = extract_token_metadata(token, secret_key)
113
111
  if metadata and metadata.get("iat"):
114
112
  iat_timestamp = metadata["iat"]
115
- if isinstance(iat_timestamp, (int, float)):
113
+ if isinstance(iat_timestamp, int | float):
116
114
  issued_at = datetime.utcfromtimestamp(iat_timestamp)
117
115
  age = (datetime.utcnow() - issued_at).total_seconds()
118
116
  return age
@@ -122,7 +120,7 @@ def get_token_age(token: str, secret_key: str) -> Optional[float]:
122
120
  return None
123
121
 
124
122
 
125
- def get_time_until_expiry(token: str, secret_key: str) -> Optional[float]:
123
+ def get_time_until_expiry(token: str, secret_key: str) -> float | None:
126
124
  """
127
125
  Get time until token expiry in seconds.
128
126
 
@@ -146,7 +144,7 @@ def get_time_until_expiry(token: str, secret_key: str) -> Optional[float]:
146
144
 
147
145
 
148
146
  def validate_token_version(
149
- token: str, secret_key: str, required_version: Optional[str] = None
147
+ token: str, secret_key: str, required_version: str | None = None
150
148
  ) -> bool:
151
149
  """
152
150
  Validate token version compatibility.
@@ -177,7 +175,7 @@ def validate_token_version(
177
175
  return False
178
176
 
179
177
 
180
- def get_token_info(token: str, secret_key: str) -> Optional[Dict[str, Any]]:
178
+ def get_token_info(token: str, secret_key: str) -> dict[str, Any] | None:
181
179
  """
182
180
  Get comprehensive token information.
183
181
 
@@ -8,7 +8,6 @@ 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 Optional
12
11
 
13
12
  try:
14
13
  from pymongo.errors import (
@@ -79,9 +78,9 @@ class TokenBlacklist:
79
78
  async def revoke_token(
80
79
  self,
81
80
  jti: str,
82
- user_id: Optional[str] = None,
83
- expires_at: Optional[datetime] = None,
84
- reason: Optional[str] = None,
81
+ user_id: str | None = None,
82
+ expires_at: datetime | None = None,
83
+ reason: str | None = None,
85
84
  ) -> bool:
86
85
  """
87
86
  Revoke a token by adding it to the blacklist.
@@ -161,7 +160,7 @@ class TokenBlacklist:
161
160
  # On error, assume not revoked (fail open for availability)
162
161
  return False
163
162
 
164
- async def revoke_all_user_tokens(self, user_id: str, reason: Optional[str] = None) -> int:
163
+ async def revoke_all_user_tokens(self, user_id: str, reason: str | None = None) -> int:
165
164
  """
166
165
  Revoke all tokens for a specific user.
167
166
 
mdb_engine/auth/users.py CHANGED
@@ -13,8 +13,9 @@ This module is part of MDB_ENGINE - MongoDB Engine.
13
13
  import logging
14
14
  import os
15
15
  import uuid
16
+ from collections.abc import Awaitable, Callable
16
17
  from datetime import datetime, timedelta
17
- from typing import Any, Awaitable, Callable, Dict, List, Optional, Tuple
18
+ from typing import Any
18
19
 
19
20
  import bcrypt
20
21
  import jwt
@@ -49,9 +50,9 @@ def _is_auth_route(request_path: str) -> bool:
49
50
  async def _get_app_user_config(
50
51
  request: Request,
51
52
  slug_id: str,
52
- config: Optional[Dict[str, Any]],
53
- get_app_config_func: Optional[Callable[[Request, str, Dict], Awaitable[Dict]]],
54
- ) -> Optional[Dict[str, Any]]:
53
+ config: dict[str, Any] | None,
54
+ get_app_config_func: Callable[[Request, str, dict], Awaitable[dict]] | None,
55
+ ) -> dict[str, Any] | None:
55
56
  """Fetch and validate app user config."""
56
57
  if config is None:
57
58
  if not get_app_config_func:
@@ -72,7 +73,7 @@ async def _get_app_user_config(
72
73
  return config
73
74
 
74
75
 
75
- def _convert_user_id_to_objectid(user_id: Any) -> Tuple[Any, Optional[str]]:
76
+ def _convert_user_id_to_objectid(user_id: Any) -> tuple[Any, str | None]:
76
77
  """
77
78
  Convert user_id to ObjectId if valid, otherwise keep as string.
78
79
 
@@ -104,7 +105,7 @@ def _convert_user_id_to_objectid(user_id: Any) -> Tuple[Any, Optional[str]]:
104
105
 
105
106
  async def _validate_and_decode_session_token(
106
107
  session_token: str, slug_id: str
107
- ) -> Tuple[Optional[Dict[str, Any]], Optional[Exception]]:
108
+ ) -> tuple[dict[str, Any] | None, Exception | None]:
108
109
  """Validate and decode session token."""
109
110
  try:
110
111
  from .jwt import decode_jwt_token
@@ -135,9 +136,7 @@ async def _validate_and_decode_session_token(
135
136
  return None, e
136
137
 
137
138
 
138
- async def _fetch_app_user_from_db(
139
- db, collection_name: str, user_id: Any
140
- ) -> Optional[Dict[str, Any]]:
139
+ async def _fetch_app_user_from_db(db, collection_name: str, user_id: Any) -> dict[str, Any] | None:
141
140
  """Fetch user from database."""
142
141
  # Use getattr for attribute access (works with both AppDB and ScopedMongoWrapper)
143
142
  collection = getattr(db, collection_name)
@@ -155,10 +154,10 @@ async def get_app_user(
155
154
  request: Request,
156
155
  slug_id: str,
157
156
  db,
158
- config: Optional[Dict[str, Any]] = None,
157
+ config: dict[str, Any] | None = None,
159
158
  allow_demo_fallback: bool = False,
160
- get_app_config_func: Optional[Callable[[Request, str, Dict], Awaitable[Dict]]] = None,
161
- ) -> Optional[Dict[str, Any]]:
159
+ get_app_config_func: Callable[[Request, str, dict], Awaitable[dict]] | None = None,
160
+ ) -> dict[str, Any] | None:
162
161
  """
163
162
  Get app-level user from session cookie.
164
163
 
@@ -234,8 +233,8 @@ async def get_app_user(
234
233
 
235
234
 
236
235
  async def _try_demo_mode(
237
- request: Request, slug_id: str, db, config: Dict[str, Any]
238
- ) -> Optional[Dict[str, Any]]:
236
+ request: Request, slug_id: str, db, config: dict[str, Any]
237
+ ) -> dict[str, Any] | None:
239
238
  """
240
239
  Internal helper: Try to authenticate as demo user if demo mode is enabled.
241
240
 
@@ -318,9 +317,9 @@ async def create_app_session(
318
317
  request: Request,
319
318
  slug_id: str,
320
319
  user_id: str,
321
- config: Optional[Dict[str, Any]] = None,
322
- response: Optional[Response] = None,
323
- get_app_config_func: Optional[Callable[[Request, str, Dict], Awaitable[Dict]]] = None,
320
+ config: dict[str, Any] | None = None,
321
+ response: Response | None = None,
322
+ get_app_config_func: Callable[[Request, str, dict], Awaitable[dict]] | None = None,
324
323
  ) -> str:
325
324
  """
326
325
  Create a app-specific session token and set cookie.
@@ -402,9 +401,9 @@ async def authenticate_app_user(
402
401
  db,
403
402
  email: str,
404
403
  password: str,
405
- store_id: Optional[str] = None,
404
+ store_id: str | None = None,
406
405
  collection_name: str = "users",
407
- ) -> Optional[Dict[str, Any]]:
406
+ ) -> dict[str, Any] | None:
408
407
  """
409
408
  Authenticate a user against app-specific users collection.
410
409
 
@@ -484,9 +483,9 @@ async def create_app_user(
484
483
  email: str,
485
484
  password: str,
486
485
  role: str = "user",
487
- store_id: Optional[str] = None,
486
+ store_id: str | None = None,
488
487
  collection_name: str = "users",
489
- ) -> Optional[Dict[str, Any]]:
488
+ ) -> dict[str, Any] | None:
490
489
  """
491
490
  Create a new user in app-specific users collection.
492
491
 
@@ -573,9 +572,9 @@ async def get_or_create_anonymous_user(
573
572
  request: Request,
574
573
  slug_id: str,
575
574
  db,
576
- config: Optional[Dict[str, Any]] = None,
577
- get_app_config_func: Optional[Callable[[Request, str, Dict], Awaitable[Dict]]] = None,
578
- ) -> Optional[Dict[str, Any]]:
575
+ config: dict[str, Any] | None = None,
576
+ get_app_config_func: Callable[[Request, str, dict], Awaitable[dict]] | None = None,
577
+ ) -> dict[str, Any] | None:
579
578
  """
580
579
  Get or create an anonymous user for anonymous_session strategy.
581
580
 
@@ -640,7 +639,7 @@ async def get_or_create_anonymous_user(
640
639
  return user
641
640
 
642
641
 
643
- async def get_platform_demo_user(mongo_uri: str, db_name: str) -> Optional[Dict[str, Any]]:
642
+ async def get_platform_demo_user(mongo_uri: str, db_name: str) -> dict[str, Any] | None:
644
643
  """
645
644
  Get platform demo user information from top-level database.
646
645
 
@@ -693,7 +692,7 @@ async def get_platform_demo_user(mongo_uri: str, db_name: str) -> Optional[Dict[
693
692
 
694
693
  async def _link_platform_demo_user(
695
694
  db, slug_id: str, collection_name: str, mongo_uri: str, db_name: str
696
- ) -> Optional[Dict[str, Any]]:
695
+ ) -> dict[str, Any] | None:
697
696
  """Link platform demo user to app demo user."""
698
697
  import datetime
699
698
 
@@ -761,7 +760,7 @@ async def _link_platform_demo_user(
761
760
 
762
761
  def _validate_demo_user_config(
763
762
  demo_user_config: Any, slug_id: str
764
- ) -> Tuple[Optional[Dict[str, Any]], Optional[str]]:
763
+ ) -> tuple[dict[str, Any] | None, str | None]:
765
764
  """Validate demo user configuration."""
766
765
  if not isinstance(demo_user_config, dict):
767
766
  return None, f"Invalid demo_user_config entry (not a dict): {demo_user_config}"
@@ -782,12 +781,12 @@ def _validate_demo_user_config(
782
781
 
783
782
 
784
783
  async def _resolve_demo_user_email_password(
785
- email: Optional[str],
786
- password: Optional[str],
787
- mongo_uri: Optional[str],
788
- db_name: Optional[str],
784
+ email: str | None,
785
+ password: str | None,
786
+ mongo_uri: str | None,
787
+ db_name: str | None,
789
788
  slug_id: str,
790
- ) -> Tuple[Optional[str], Optional[str]]:
789
+ ) -> tuple[str | None, str | None]:
791
790
  """Resolve email and password from config or platform demo."""
792
791
  # If email not specified, try platform demo
793
792
  if not email:
@@ -834,11 +833,11 @@ async def _create_demo_user_from_config(
834
833
  email: str,
835
834
  password: str,
836
835
  role: str,
837
- extra_data: Dict[str, Any],
836
+ extra_data: dict[str, Any],
838
837
  link_to_platform: bool,
839
- mongo_uri: Optional[str],
840
- db_name: Optional[str],
841
- ) -> Optional[Dict[str, Any]]:
838
+ mongo_uri: str | None,
839
+ db_name: str | None,
840
+ ) -> dict[str, Any] | None:
842
841
  """Create a demo user from configuration."""
843
842
  import datetime
844
843
 
@@ -933,10 +932,10 @@ async def _create_demo_user_from_config(
933
932
  async def ensure_demo_users_exist(
934
933
  db,
935
934
  slug_id: str,
936
- config: Optional[Dict[str, Any]] = None,
937
- mongo_uri: Optional[str] = None,
938
- db_name: Optional[str] = None,
939
- ) -> List[Dict[str, Any]]:
935
+ config: dict[str, Any] | None = None,
936
+ mongo_uri: str | None = None,
937
+ db_name: str | None = None,
938
+ ) -> list[dict[str, Any]]:
940
939
  """
941
940
  Intelligently ensure demo users exist for a app based on manifest configuration.
942
941
 
@@ -1058,9 +1057,9 @@ async def get_or_create_demo_user_for_request(
1058
1057
  request: Request,
1059
1058
  slug_id: str,
1060
1059
  db,
1061
- config: Optional[Dict[str, Any]] = None,
1062
- get_app_config_func: Optional[Callable[[Request, str, Dict], Awaitable[Dict]]] = None,
1063
- ) -> Optional[Dict[str, Any]]:
1060
+ config: dict[str, Any] | None = None,
1061
+ get_app_config_func: Callable[[Request, str, dict], Awaitable[dict]] | None = None,
1062
+ ) -> dict[str, Any] | None:
1064
1063
  """
1065
1064
  Get or create a demo user for the current request context.
1066
1065
 
@@ -1150,10 +1149,10 @@ async def get_or_create_demo_user_for_request(
1150
1149
  async def get_or_create_demo_user(
1151
1150
  db,
1152
1151
  slug_id: str,
1153
- config: Dict[str, Any],
1154
- mongo_uri: Optional[str] = None,
1155
- db_name: Optional[str] = None,
1156
- ) -> Optional[Dict[str, Any]]:
1152
+ config: dict[str, Any],
1153
+ mongo_uri: str | None = None,
1154
+ db_name: str | None = None,
1155
+ ) -> dict[str, Any] | None:
1157
1156
  """
1158
1157
  Get or create a demo user for an app.
1159
1158
 
@@ -1252,7 +1251,7 @@ async def get_or_create_demo_user(
1252
1251
 
1253
1252
  async def ensure_demo_users_for_actor(
1254
1253
  db, slug_id: str, mongo_uri: str, db_name: str
1255
- ) -> List[Dict[str, Any]]:
1254
+ ) -> list[dict[str, Any]]:
1256
1255
  """
1257
1256
  Convenience function for actors to ensure demo users exist.
1258
1257
 
@@ -1351,10 +1350,10 @@ async def ensure_demo_users_for_actor(
1351
1350
 
1352
1351
 
1353
1352
  async def sync_app_user_to_casbin(
1354
- user: Dict[str, Any],
1353
+ user: dict[str, Any],
1355
1354
  authz_provider,
1356
- role: Optional[str] = None,
1357
- app_slug: Optional[str] = None,
1355
+ role: str | None = None,
1356
+ app_slug: str | None = None,
1358
1357
  ) -> bool:
1359
1358
  """
1360
1359
  Sync app-level user to Casbin by assigning a role.
@@ -1428,7 +1427,7 @@ async def sync_app_user_to_casbin(
1428
1427
  return False
1429
1428
 
1430
1429
 
1431
- def get_app_user_role(user: Dict[str, Any], config: Optional[Dict[str, Any]] = None) -> str:
1430
+ def get_app_user_role(user: dict[str, Any], config: dict[str, Any] | None = None) -> str:
1432
1431
  """
1433
1432
  Determine Casbin role for app-level user.
1434
1433