mdb-engine 0.2.1__py3-none-any.whl → 0.2.4__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 (70) hide show
  1. mdb_engine/__init__.py +7 -1
  2. mdb_engine/auth/README.md +6 -0
  3. mdb_engine/auth/audit.py +40 -40
  4. mdb_engine/auth/base.py +3 -3
  5. mdb_engine/auth/casbin_factory.py +6 -6
  6. mdb_engine/auth/config_defaults.py +5 -5
  7. mdb_engine/auth/config_helpers.py +12 -12
  8. mdb_engine/auth/cookie_utils.py +9 -9
  9. mdb_engine/auth/csrf.py +9 -8
  10. mdb_engine/auth/decorators.py +7 -6
  11. mdb_engine/auth/dependencies.py +22 -21
  12. mdb_engine/auth/integration.py +9 -9
  13. mdb_engine/auth/jwt.py +9 -9
  14. mdb_engine/auth/middleware.py +4 -3
  15. mdb_engine/auth/oso_factory.py +6 -6
  16. mdb_engine/auth/provider.py +4 -4
  17. mdb_engine/auth/rate_limiter.py +12 -11
  18. mdb_engine/auth/restrictions.py +16 -15
  19. mdb_engine/auth/session_manager.py +11 -13
  20. mdb_engine/auth/shared_middleware.py +344 -132
  21. mdb_engine/auth/shared_users.py +20 -20
  22. mdb_engine/auth/token_lifecycle.py +10 -12
  23. mdb_engine/auth/token_store.py +4 -5
  24. mdb_engine/auth/users.py +51 -52
  25. mdb_engine/auth/utils.py +29 -33
  26. mdb_engine/cli/commands/generate.py +6 -6
  27. mdb_engine/cli/utils.py +4 -4
  28. mdb_engine/config.py +6 -7
  29. mdb_engine/core/app_registration.py +12 -12
  30. mdb_engine/core/app_secrets.py +1 -2
  31. mdb_engine/core/connection.py +3 -4
  32. mdb_engine/core/encryption.py +1 -2
  33. mdb_engine/core/engine.py +43 -44
  34. mdb_engine/core/manifest.py +80 -58
  35. mdb_engine/core/ray_integration.py +10 -9
  36. mdb_engine/core/seeding.py +3 -3
  37. mdb_engine/core/service_initialization.py +10 -9
  38. mdb_engine/core/types.py +40 -40
  39. mdb_engine/database/abstraction.py +15 -16
  40. mdb_engine/database/connection.py +40 -12
  41. mdb_engine/database/query_validator.py +8 -8
  42. mdb_engine/database/resource_limiter.py +7 -7
  43. mdb_engine/database/scoped_wrapper.py +51 -58
  44. mdb_engine/dependencies.py +14 -13
  45. mdb_engine/di/container.py +12 -13
  46. mdb_engine/di/providers.py +14 -13
  47. mdb_engine/di/scopes.py +5 -5
  48. mdb_engine/embeddings/dependencies.py +2 -2
  49. mdb_engine/embeddings/service.py +67 -50
  50. mdb_engine/exceptions.py +20 -20
  51. mdb_engine/indexes/helpers.py +11 -11
  52. mdb_engine/indexes/manager.py +9 -9
  53. mdb_engine/memory/README.md +93 -2
  54. mdb_engine/memory/service.py +361 -1109
  55. mdb_engine/observability/health.py +10 -9
  56. mdb_engine/observability/logging.py +10 -10
  57. mdb_engine/observability/metrics.py +8 -7
  58. mdb_engine/repositories/base.py +25 -25
  59. mdb_engine/repositories/mongo.py +17 -17
  60. mdb_engine/repositories/unit_of_work.py +6 -6
  61. mdb_engine/routing/websockets.py +19 -18
  62. mdb_engine/utils/__init__.py +3 -1
  63. mdb_engine/utils/mongo.py +117 -0
  64. {mdb_engine-0.2.1.dist-info → mdb_engine-0.2.4.dist-info}/METADATA +88 -13
  65. mdb_engine-0.2.4.dist-info/RECORD +97 -0
  66. {mdb_engine-0.2.1.dist-info → mdb_engine-0.2.4.dist-info}/WHEEL +1 -1
  67. mdb_engine-0.2.1.dist-info/RECORD +0 -96
  68. {mdb_engine-0.2.1.dist-info → mdb_engine-0.2.4.dist-info}/entry_points.txt +0 -0
  69. {mdb_engine-0.2.1.dist-info → mdb_engine-0.2.4.dist-info}/licenses/LICENSE +0 -0
  70. {mdb_engine-0.2.1.dist-info → mdb_engine-0.2.4.dist-info}/top_level.txt +0 -0
@@ -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
 
mdb_engine/auth/utils.py CHANGED
@@ -11,7 +11,7 @@ import logging
11
11
  import re
12
12
  import uuid
13
13
  from datetime import datetime
14
- from typing import Any, Dict, List, Optional, Tuple
14
+ from typing import Any
15
15
 
16
16
  import bcrypt
17
17
  from fastapi import Request, Response
@@ -43,7 +43,7 @@ def _detect_browser(user_agent: str) -> str:
43
43
  return "unknown"
44
44
 
45
45
 
46
- def _detect_os_and_device_type(user_agent: str) -> Tuple[str, str]:
46
+ def _detect_os_and_device_type(user_agent: str) -> tuple[str, str]:
47
47
  """Detect OS and device type from user agent string."""
48
48
  if not user_agent:
49
49
  return "unknown", "desktop"
@@ -64,7 +64,7 @@ def _detect_os_and_device_type(user_agent: str) -> Tuple[str, str]:
64
64
  return "unknown", "desktop"
65
65
 
66
66
 
67
- def get_device_info(request: Request) -> Dict[str, Any]:
67
+ def get_device_info(request: Request) -> dict[str, Any]:
68
68
  """
69
69
  Extract device information from request.
70
70
 
@@ -236,15 +236,15 @@ async def check_password_breach(password: str) -> bool:
236
236
 
237
237
  def validate_password_strength(
238
238
  password: str,
239
- min_length: Optional[int] = None,
240
- require_uppercase: Optional[bool] = None,
241
- require_lowercase: Optional[bool] = None,
242
- require_numbers: Optional[bool] = None,
243
- require_special: Optional[bool] = None,
244
- min_entropy_bits: Optional[int] = None,
245
- check_common_passwords: Optional[bool] = None,
246
- config: Optional[Dict[str, Any]] = None,
247
- ) -> Tuple[bool, List[str]]:
239
+ min_length: int | None = None,
240
+ require_uppercase: bool | None = None,
241
+ require_lowercase: bool | None = None,
242
+ require_numbers: bool | None = None,
243
+ require_special: bool | None = None,
244
+ min_entropy_bits: int | None = None,
245
+ check_common_passwords: bool | None = None,
246
+ config: dict[str, Any] | None = None,
247
+ ) -> tuple[bool, list[str]]:
248
248
  """
249
249
  Validate password strength with configurable rules.
250
250
 
@@ -338,9 +338,9 @@ def validate_password_strength(
338
338
 
339
339
  async def validate_password_strength_async(
340
340
  password: str,
341
- config: Optional[Dict[str, Any]] = None,
342
- check_breaches: Optional[bool] = None,
343
- ) -> Tuple[bool, List[str]]:
341
+ config: dict[str, Any] | None = None,
342
+ check_breaches: bool | None = None,
343
+ ) -> tuple[bool, list[str]]:
344
344
  """
345
345
  Async version of validate_password_strength with breach checking.
346
346
 
@@ -404,10 +404,10 @@ async def login_user(
404
404
  email: str,
405
405
  password: str,
406
406
  db,
407
- config: Optional[Dict[str, Any]] = None,
407
+ config: dict[str, Any] | None = None,
408
408
  remember_me: bool = False,
409
- redirect_url: Optional[str] = None,
410
- ) -> Dict[str, Any]:
409
+ redirect_url: str | None = None,
410
+ ) -> dict[str, Any]:
411
411
  """
412
412
  Handle user login with automatic token generation and cookie setting.
413
413
 
@@ -571,8 +571,8 @@ def _validate_email_format(email: str) -> bool:
571
571
 
572
572
 
573
573
  def _get_password_policy_from_config(
574
- request: Request, config: Optional[Dict[str, Any]]
575
- ) -> Optional[Dict[str, Any]]:
574
+ request: Request, config: dict[str, Any] | None
575
+ ) -> dict[str, Any] | None:
576
576
  """Get password policy from config or request."""
577
577
  if config:
578
578
  security = config.get("security", {})
@@ -585,8 +585,8 @@ def _get_password_policy_from_config(
585
585
 
586
586
 
587
587
  async def _create_user_document(
588
- email: str, password: str, extra_data: Optional[Dict[str, Any]]
589
- ) -> Dict[str, Any]:
588
+ email: str, password: str, extra_data: dict[str, Any] | None
589
+ ) -> dict[str, Any]:
590
590
  """Create user document with hashed password."""
591
591
  password_hash = bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt())
592
592
  user_doc = {
@@ -600,9 +600,7 @@ async def _create_user_document(
600
600
  return user_doc
601
601
 
602
602
 
603
- def _create_registration_response(
604
- user_doc: Dict[str, Any], redirect_url: Optional[str]
605
- ) -> Response:
603
+ def _create_registration_response(user_doc: dict[str, Any], redirect_url: str | None) -> Response:
606
604
  """Create response for registration."""
607
605
  if redirect_url:
608
606
  return RedirectResponse(url=redirect_url, status_code=302)
@@ -622,10 +620,10 @@ async def register_user(
622
620
  email: str,
623
621
  password: str,
624
622
  db,
625
- config: Optional[Dict[str, Any]] = None,
626
- extra_data: Optional[Dict[str, Any]] = None,
627
- redirect_url: Optional[str] = None,
628
- ) -> Dict[str, Any]:
623
+ config: dict[str, Any] | None = None,
624
+ extra_data: dict[str, Any] | None = None,
625
+ redirect_url: str | None = None,
626
+ ) -> dict[str, Any]:
629
627
  """
630
628
  Handle user registration with automatic token generation.
631
629
 
@@ -710,7 +708,7 @@ async def register_user(
710
708
  return {"success": False, "error": "Registration failed. Please try again."}
711
709
 
712
710
 
713
- async def _get_user_id_from_request(request: Request, user_id: Optional[str]) -> Optional[str]:
711
+ async def _get_user_id_from_request(request: Request, user_id: str | None) -> str | None:
714
712
  """Extract user_id from request if not provided."""
715
713
  if user_id:
716
714
  return user_id
@@ -773,9 +771,7 @@ async def _revoke_session(request: Request) -> None:
773
771
  await session_mgr.revoke_session_by_refresh_token(refresh_jti)
774
772
 
775
773
 
776
- async def logout_user(
777
- request: Request, response: Response, user_id: Optional[str] = None
778
- ) -> Response:
774
+ async def logout_user(request: Request, response: Response, user_id: str | None = None) -> Response:
779
775
  """
780
776
  Handle user logout with token revocation and cookie clearing.
781
777