mdb-engine 0.1.6__py3-none-any.whl → 0.4.12__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 (92) hide show
  1. mdb_engine/__init__.py +116 -11
  2. mdb_engine/auth/ARCHITECTURE.md +112 -0
  3. mdb_engine/auth/README.md +654 -11
  4. mdb_engine/auth/__init__.py +136 -29
  5. mdb_engine/auth/audit.py +592 -0
  6. mdb_engine/auth/base.py +252 -0
  7. mdb_engine/auth/casbin_factory.py +265 -70
  8. mdb_engine/auth/config_defaults.py +5 -5
  9. mdb_engine/auth/config_helpers.py +19 -18
  10. mdb_engine/auth/cookie_utils.py +12 -16
  11. mdb_engine/auth/csrf.py +483 -0
  12. mdb_engine/auth/decorators.py +10 -16
  13. mdb_engine/auth/dependencies.py +69 -71
  14. mdb_engine/auth/helpers.py +3 -3
  15. mdb_engine/auth/integration.py +61 -88
  16. mdb_engine/auth/jwt.py +11 -15
  17. mdb_engine/auth/middleware.py +79 -35
  18. mdb_engine/auth/oso_factory.py +21 -41
  19. mdb_engine/auth/provider.py +270 -171
  20. mdb_engine/auth/rate_limiter.py +505 -0
  21. mdb_engine/auth/restrictions.py +21 -36
  22. mdb_engine/auth/session_manager.py +24 -41
  23. mdb_engine/auth/shared_middleware.py +977 -0
  24. mdb_engine/auth/shared_users.py +775 -0
  25. mdb_engine/auth/token_lifecycle.py +10 -12
  26. mdb_engine/auth/token_store.py +17 -32
  27. mdb_engine/auth/users.py +99 -159
  28. mdb_engine/auth/utils.py +236 -42
  29. mdb_engine/cli/commands/generate.py +546 -10
  30. mdb_engine/cli/commands/validate.py +3 -7
  31. mdb_engine/cli/utils.py +7 -7
  32. mdb_engine/config.py +13 -28
  33. mdb_engine/constants.py +65 -0
  34. mdb_engine/core/README.md +117 -6
  35. mdb_engine/core/__init__.py +39 -7
  36. mdb_engine/core/app_registration.py +31 -50
  37. mdb_engine/core/app_secrets.py +289 -0
  38. mdb_engine/core/connection.py +20 -12
  39. mdb_engine/core/encryption.py +222 -0
  40. mdb_engine/core/engine.py +2862 -115
  41. mdb_engine/core/index_management.py +12 -16
  42. mdb_engine/core/manifest.py +628 -204
  43. mdb_engine/core/ray_integration.py +436 -0
  44. mdb_engine/core/seeding.py +13 -21
  45. mdb_engine/core/service_initialization.py +20 -30
  46. mdb_engine/core/types.py +40 -43
  47. mdb_engine/database/README.md +140 -17
  48. mdb_engine/database/__init__.py +17 -6
  49. mdb_engine/database/abstraction.py +37 -50
  50. mdb_engine/database/connection.py +51 -30
  51. mdb_engine/database/query_validator.py +367 -0
  52. mdb_engine/database/resource_limiter.py +204 -0
  53. mdb_engine/database/scoped_wrapper.py +747 -237
  54. mdb_engine/dependencies.py +427 -0
  55. mdb_engine/di/__init__.py +34 -0
  56. mdb_engine/di/container.py +247 -0
  57. mdb_engine/di/providers.py +206 -0
  58. mdb_engine/di/scopes.py +139 -0
  59. mdb_engine/embeddings/README.md +54 -24
  60. mdb_engine/embeddings/__init__.py +31 -24
  61. mdb_engine/embeddings/dependencies.py +38 -155
  62. mdb_engine/embeddings/service.py +78 -75
  63. mdb_engine/exceptions.py +104 -12
  64. mdb_engine/indexes/README.md +30 -13
  65. mdb_engine/indexes/__init__.py +1 -0
  66. mdb_engine/indexes/helpers.py +11 -11
  67. mdb_engine/indexes/manager.py +59 -123
  68. mdb_engine/memory/README.md +95 -4
  69. mdb_engine/memory/__init__.py +1 -2
  70. mdb_engine/memory/service.py +363 -1168
  71. mdb_engine/observability/README.md +4 -2
  72. mdb_engine/observability/__init__.py +26 -9
  73. mdb_engine/observability/health.py +17 -17
  74. mdb_engine/observability/logging.py +10 -10
  75. mdb_engine/observability/metrics.py +40 -19
  76. mdb_engine/repositories/__init__.py +34 -0
  77. mdb_engine/repositories/base.py +325 -0
  78. mdb_engine/repositories/mongo.py +233 -0
  79. mdb_engine/repositories/unit_of_work.py +166 -0
  80. mdb_engine/routing/README.md +1 -1
  81. mdb_engine/routing/__init__.py +1 -3
  82. mdb_engine/routing/websockets.py +41 -75
  83. mdb_engine/utils/__init__.py +3 -1
  84. mdb_engine/utils/mongo.py +117 -0
  85. mdb_engine-0.4.12.dist-info/METADATA +492 -0
  86. mdb_engine-0.4.12.dist-info/RECORD +97 -0
  87. {mdb_engine-0.1.6.dist-info → mdb_engine-0.4.12.dist-info}/WHEEL +1 -1
  88. mdb_engine-0.1.6.dist-info/METADATA +0 -213
  89. mdb_engine-0.1.6.dist-info/RECORD +0 -75
  90. {mdb_engine-0.1.6.dist-info → mdb_engine-0.4.12.dist-info}/entry_points.txt +0 -0
  91. {mdb_engine-0.1.6.dist-info → mdb_engine-0.4.12.dist-info}/licenses/LICENSE +0 -0
  92. {mdb_engine-0.1.6.dist-info → mdb_engine-0.4.12.dist-info}/top_level.txt +0 -0
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
@@ -22,9 +23,15 @@ from fastapi import Request
22
23
  from fastapi.responses import Response
23
24
 
24
25
  try:
25
- from pymongo.errors import (ConnectionFailure, OperationFailure,
26
- ServerSelectionTimeoutError)
26
+ from bson.errors import InvalidId as BsonInvalidId
27
+ from pymongo.errors import (
28
+ ConnectionFailure,
29
+ OperationFailure,
30
+ PyMongoError,
31
+ ServerSelectionTimeoutError,
32
+ )
27
33
  except ImportError:
34
+ BsonInvalidId = ValueError # Fallback if bson not available
28
35
  ConnectionFailure = Exception
29
36
  OperationFailure = Exception
30
37
  ServerSelectionTimeoutError = Exception
@@ -43,9 +50,9 @@ def _is_auth_route(request_path: str) -> bool:
43
50
  async def _get_app_user_config(
44
51
  request: Request,
45
52
  slug_id: str,
46
- config: Optional[Dict[str, Any]],
47
- get_app_config_func: Optional[Callable[[Request, str, Dict], Awaitable[Dict]]],
48
- ) -> 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:
49
56
  """Fetch and validate app user config."""
50
57
  if config is None:
51
58
  if not get_app_config_func:
@@ -66,7 +73,7 @@ async def _get_app_user_config(
66
73
  return config
67
74
 
68
75
 
69
- 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]:
70
77
  """
71
78
  Convert user_id to ObjectId if valid, otherwise keep as string.
72
79
 
@@ -90,18 +97,15 @@ def _convert_user_id_to_objectid(user_id: Any) -> Tuple[Any, Optional[str]]:
90
97
  # If it's not a string or ObjectId, try to convert (for backward compatibility)
91
98
  try:
92
99
  return ObjectId(user_id), None
93
- except (TypeError, ValueError) as e:
100
+ except (TypeError, ValueError, BsonInvalidId) as e:
94
101
  return None, f"Invalid user_id format: {user_id}: {e}"
95
- except Exception:
96
- logger.exception("Unexpected error converting user_id to ObjectId")
97
- raise
98
102
 
99
103
  return user_id, None
100
104
 
101
105
 
102
106
  async def _validate_and_decode_session_token(
103
107
  session_token: str, slug_id: str
104
- ) -> Tuple[Optional[Dict[str, Any]], Optional[Exception]]:
108
+ ) -> tuple[dict[str, Any] | None, Exception | None]:
105
109
  """Validate and decode session token."""
106
110
  try:
107
111
  from .jwt import decode_jwt_token
@@ -132,9 +136,7 @@ async def _validate_and_decode_session_token(
132
136
  return None, e
133
137
 
134
138
 
135
- async def _fetch_app_user_from_db(
136
- db, collection_name: str, user_id: Any
137
- ) -> Optional[Dict[str, Any]]:
139
+ async def _fetch_app_user_from_db(db, collection_name: str, user_id: Any) -> dict[str, Any] | None:
138
140
  """Fetch user from database."""
139
141
  # Use getattr for attribute access (works with both AppDB and ScopedMongoWrapper)
140
142
  collection = getattr(db, collection_name)
@@ -152,12 +154,10 @@ async def get_app_user(
152
154
  request: Request,
153
155
  slug_id: str,
154
156
  db,
155
- config: Optional[Dict[str, Any]] = None,
157
+ config: dict[str, Any] | None = None,
156
158
  allow_demo_fallback: bool = False,
157
- get_app_config_func: Optional[
158
- Callable[[Request, str, Dict], Awaitable[Dict]]
159
- ] = None,
160
- ) -> Optional[Dict[str, Any]]:
159
+ get_app_config_func: Callable[[Request, str, dict], Awaitable[dict]] | None = None,
160
+ ) -> dict[str, Any] | None:
161
161
  """
162
162
  Get app-level user from session cookie.
163
163
 
@@ -233,8 +233,8 @@ async def get_app_user(
233
233
 
234
234
 
235
235
  async def _try_demo_mode(
236
- request: Request, slug_id: str, db, config: Dict[str, Any]
237
- ) -> Optional[Dict[str, Any]]:
236
+ request: Request, slug_id: str, db, config: dict[str, Any]
237
+ ) -> dict[str, Any] | None:
238
238
  """
239
239
  Internal helper: Try to authenticate as demo user if demo mode is enabled.
240
240
 
@@ -279,9 +279,7 @@ async def _try_demo_mode(
279
279
  f"(MONGO_URI={MONGO_URI}, DB_NAME={DB_NAME})"
280
280
  )
281
281
 
282
- demo_user = await get_or_create_demo_user(
283
- db, slug_id, config, MONGO_URI, DB_NAME
284
- )
282
+ demo_user = await get_or_create_demo_user(db, slug_id, config, MONGO_URI, DB_NAME)
285
283
 
286
284
  if not demo_user:
287
285
  logger.warning(
@@ -319,11 +317,9 @@ async def create_app_session(
319
317
  request: Request,
320
318
  slug_id: str,
321
319
  user_id: str,
322
- config: Optional[Dict[str, Any]] = None,
323
- response: Optional[Response] = None,
324
- get_app_config_func: Optional[
325
- Callable[[Request, str, Dict], Awaitable[Dict]]
326
- ] = 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,
327
323
  ) -> str:
328
324
  """
329
325
  Create a app-specific session token and set cookie.
@@ -387,9 +383,7 @@ async def create_app_session(
387
383
  cookie_name = f"{session_cookie_name}_{slug_id}"
388
384
 
389
385
  # Determine secure cookie setting
390
- should_use_secure = (
391
- request.url.scheme == "https" or os.getenv("G_NOME_ENV") == "production"
392
- )
386
+ should_use_secure = request.url.scheme == "https" or os.getenv("G_NOME_ENV") == "production"
393
387
 
394
388
  response.set_cookie(
395
389
  key=cookie_name,
@@ -407,9 +401,9 @@ async def authenticate_app_user(
407
401
  db,
408
402
  email: str,
409
403
  password: str,
410
- store_id: Optional[str] = None,
404
+ store_id: str | None = None,
411
405
  collection_name: str = "users",
412
- ) -> Optional[Dict[str, Any]]:
406
+ ) -> dict[str, Any] | None:
413
407
  """
414
408
  Authenticate a user against app-specific users collection.
415
409
 
@@ -436,12 +430,9 @@ async def authenticate_app_user(
436
430
  from bson.objectid import ObjectId
437
431
 
438
432
  query["store_id"] = ObjectId(store_id)
439
- except (TypeError, ValueError) as e:
433
+ except (TypeError, ValueError, BsonInvalidId) as e:
440
434
  logger.warning(f"Invalid store_id format: {store_id}: {e}")
441
435
  return None
442
- except Exception:
443
- logger.exception("Unexpected error converting store_id to ObjectId")
444
- raise
445
436
 
446
437
  # Find user
447
438
  # Use getattr to access collection (works with ScopedMongoWrapper and AppDB)
@@ -479,10 +470,12 @@ async def authenticate_app_user(
479
470
  except (ValueError, TypeError):
480
471
  logger.exception("Validation error authenticating app user")
481
472
  return None
482
- except Exception:
483
- logger.exception("Unexpected error authenticating app user")
484
- # Re-raise unexpected errors for debugging
485
- raise
473
+ except PyMongoError:
474
+ logger.exception("Database error authenticating app user")
475
+ return None
476
+ except (AttributeError, KeyError):
477
+ logger.exception("Attribute access error authenticating app user")
478
+ return None
486
479
 
487
480
 
488
481
  async def create_app_user(
@@ -490,9 +483,9 @@ async def create_app_user(
490
483
  email: str,
491
484
  password: str,
492
485
  role: str = "user",
493
- store_id: Optional[str] = None,
486
+ store_id: str | None = None,
494
487
  collection_name: str = "users",
495
- ) -> Optional[Dict[str, Any]]:
488
+ ) -> dict[str, Any] | None:
496
489
  """
497
490
  Create a new user in app-specific users collection.
498
491
 
@@ -513,12 +506,7 @@ async def create_app_user(
513
506
  from bson.objectid import ObjectId
514
507
 
515
508
  # Validate email format
516
- if (
517
- not email
518
- or not isinstance(email, str)
519
- or "@" not in email
520
- or "." not in email
521
- ):
509
+ if not email or not isinstance(email, str) or "@" not in email or "." not in email:
522
510
  logger.warning(f"Invalid email format: {email}")
523
511
  return None
524
512
 
@@ -545,13 +533,9 @@ async def create_app_user(
545
533
  # Always hash password (plain text support removed for security)
546
534
  try:
547
535
  password_hash = bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt())
548
- except ValueError:
536
+ except (ValueError, TypeError, UnicodeEncodeError):
549
537
  logger.exception("Error encoding password for hashing")
550
538
  return None
551
- except Exception:
552
- logger.exception("Unexpected error hashing password")
553
- # Re-raise unexpected errors
554
- raise
555
539
 
556
540
  # Create user document
557
541
  user_doc = {
@@ -573,24 +557,24 @@ async def create_app_user(
573
557
 
574
558
  return user_doc
575
559
 
576
- except (ValueError, TypeError):
560
+ except (ValueError, TypeError, BsonInvalidId):
577
561
  logger.exception("Validation error creating app user")
578
562
  return None
579
- except Exception:
580
- logger.exception("Unexpected error creating app user")
581
- # Re-raise unexpected errors for debugging
582
- raise
563
+ except PyMongoError:
564
+ logger.exception("Database error creating app user")
565
+ return None
566
+ except (AttributeError, KeyError):
567
+ logger.exception("Attribute access error creating app user")
568
+ return None
583
569
 
584
570
 
585
571
  async def get_or_create_anonymous_user(
586
572
  request: Request,
587
573
  slug_id: str,
588
574
  db,
589
- config: Optional[Dict[str, Any]] = None,
590
- get_app_config_func: Optional[
591
- Callable[[Request, str, Dict], Awaitable[Dict]]
592
- ] = None,
593
- ) -> 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:
594
578
  """
595
579
  Get or create an anonymous user for anonymous_session strategy.
596
580
 
@@ -655,9 +639,7 @@ async def get_or_create_anonymous_user(
655
639
  return user
656
640
 
657
641
 
658
- async def get_platform_demo_user(
659
- mongo_uri: str, db_name: str
660
- ) -> Optional[Dict[str, Any]]:
642
+ async def get_platform_demo_user(mongo_uri: str, db_name: str) -> dict[str, Any] | None:
661
643
  """
662
644
  Get platform demo user information from top-level database.
663
645
 
@@ -669,8 +651,7 @@ async def get_platform_demo_user(
669
651
  Dict with demo user info (email, password from config, user_id) or None if not available
670
652
  """
671
653
  try:
672
- from ..config import (DEMO_EMAIL_DEFAULT, DEMO_ENABLED,
673
- DEMO_PASSWORD_DEFAULT)
654
+ from ..config import DEMO_EMAIL_DEFAULT, DEMO_ENABLED, DEMO_PASSWORD_DEFAULT
674
655
 
675
656
  if not DEMO_ENABLED or not DEMO_EMAIL_DEFAULT:
676
657
  return None
@@ -711,19 +692,15 @@ async def get_platform_demo_user(
711
692
 
712
693
  async def _link_platform_demo_user(
713
694
  db, slug_id: str, collection_name: str, mongo_uri: str, db_name: str
714
- ) -> Optional[Dict[str, Any]]:
695
+ ) -> dict[str, Any] | None:
715
696
  """Link platform demo user to app demo user."""
716
697
  import datetime
717
698
 
718
699
  try:
719
- logger.debug(
720
- f"ensure_demo_users_exist: Auto-linking platform demo user for '{slug_id}'"
721
- )
700
+ logger.debug(f"ensure_demo_users_exist: Auto-linking platform demo user for '{slug_id}'")
722
701
  platform_demo = await get_platform_demo_user(mongo_uri, db_name)
723
702
  if not platform_demo:
724
- logger.warning(
725
- f"ensure_demo_users_exist: Platform demo user not found for '{slug_id}'"
726
- )
703
+ logger.warning(f"ensure_demo_users_exist: Platform demo user not found for '{slug_id}'")
727
704
  return None
728
705
 
729
706
  # Check if app demo user already exists for platform demo
@@ -737,13 +714,10 @@ async def _link_platform_demo_user(
737
714
  platform_password = platform_demo.get("password", "demo123")
738
715
  password_hash = None
739
716
  try:
740
- password_hash = bcrypt.hashpw(
741
- platform_password.encode("utf-8"), bcrypt.gensalt()
742
- )
717
+ password_hash = bcrypt.hashpw(platform_password.encode("utf-8"), bcrypt.gensalt())
743
718
  except (ValueError, TypeError, AttributeError) as e:
744
719
  logger.error(
745
- f"Error hashing password for platform demo user "
746
- f"{platform_demo['email']}: {e}",
720
+ f"Error hashing password for platform demo user " f"{platform_demo['email']}: {e}",
747
721
  exc_info=True,
748
722
  )
749
723
  return None
@@ -786,16 +760,14 @@ async def _link_platform_demo_user(
786
760
 
787
761
  def _validate_demo_user_config(
788
762
  demo_user_config: Any, slug_id: str
789
- ) -> Tuple[Optional[Dict[str, Any]], Optional[str]]:
763
+ ) -> tuple[dict[str, Any] | None, str | None]:
790
764
  """Validate demo user configuration."""
791
765
  if not isinstance(demo_user_config, dict):
792
766
  return None, f"Invalid demo_user_config entry (not a dict): {demo_user_config}"
793
767
 
794
768
  extra_data = demo_user_config.get("extra_data", {})
795
769
  if not isinstance(extra_data, dict):
796
- logger.warning(
797
- f"Invalid extra_data for demo user config (not a dict): {extra_data}"
798
- )
770
+ logger.warning(f"Invalid extra_data for demo user config (not a dict): {extra_data}")
799
771
  extra_data = {}
800
772
 
801
773
  return {
@@ -809,12 +781,12 @@ def _validate_demo_user_config(
809
781
 
810
782
 
811
783
  async def _resolve_demo_user_email_password(
812
- email: Optional[str],
813
- password: Optional[str],
814
- mongo_uri: Optional[str],
815
- db_name: Optional[str],
784
+ email: str | None,
785
+ password: str | None,
786
+ mongo_uri: str | None,
787
+ db_name: str | None,
816
788
  slug_id: str,
817
- ) -> Tuple[Optional[str], Optional[str]]:
789
+ ) -> tuple[str | None, str | None]:
818
790
  """Resolve email and password from config or platform demo."""
819
791
  # If email not specified, try platform demo
820
792
  if not email:
@@ -825,14 +797,10 @@ async def _resolve_demo_user_email_password(
825
797
  if not password:
826
798
  password = platform_demo["password"]
827
799
  else:
828
- logger.warning(
829
- f"No email specified and platform demo not available for {slug_id}"
830
- )
800
+ logger.warning(f"No email specified and platform demo not available for {slug_id}")
831
801
  return None, None
832
802
  else:
833
- logger.warning(
834
- f"No email specified and cannot access platform demo for {slug_id}"
835
- )
803
+ logger.warning(f"No email specified and cannot access platform demo for {slug_id}")
836
804
  return None, None
837
805
 
838
806
  # Validate email format
@@ -865,11 +833,11 @@ async def _create_demo_user_from_config(
865
833
  email: str,
866
834
  password: str,
867
835
  role: str,
868
- extra_data: Dict[str, Any],
836
+ extra_data: dict[str, Any],
869
837
  link_to_platform: bool,
870
- mongo_uri: Optional[str],
871
- db_name: Optional[str],
872
- ) -> Optional[Dict[str, Any]]:
838
+ mongo_uri: str | None,
839
+ db_name: str | None,
840
+ ) -> dict[str, Any] | None:
873
841
  """Create a demo user from configuration."""
874
842
  import datetime
875
843
 
@@ -877,9 +845,7 @@ async def _create_demo_user_from_config(
877
845
  try:
878
846
  password_hash = bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt())
879
847
  except (ValueError, TypeError, AttributeError) as e:
880
- logger.error(
881
- f"Error hashing password for demo user {email}: {e}", exc_info=True
882
- )
848
+ logger.error(f"Error hashing password for demo user {email}: {e}", exc_info=True)
883
849
  return None
884
850
 
885
851
  # Create user document
@@ -945,9 +911,7 @@ async def _create_demo_user_from_config(
945
911
 
946
912
  user_doc["_id"] = result.inserted_id if not custom_id else custom_id
947
913
  user_doc["app_user_id"] = str(user_doc["_id"])
948
- logger.info(
949
- f"Created demo user {email} for {slug_id} with _id={user_doc['_id']}"
950
- )
914
+ logger.info(f"Created demo user {email} for {slug_id} with _id={user_doc['_id']}")
951
915
  return user_doc
952
916
  except (
953
917
  OperationFailure,
@@ -968,10 +932,10 @@ async def _create_demo_user_from_config(
968
932
  async def ensure_demo_users_exist(
969
933
  db,
970
934
  slug_id: str,
971
- config: Optional[Dict[str, Any]] = None,
972
- mongo_uri: Optional[str] = None,
973
- db_name: Optional[str] = None,
974
- ) -> 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]]:
975
939
  """
976
940
  Intelligently ensure demo users exist for a app based on manifest configuration.
977
941
 
@@ -1030,9 +994,7 @@ async def ensure_demo_users_exist(
1030
994
  for demo_user_config in demo_users_config:
1031
995
  try:
1032
996
  # Validate config structure
1033
- validated_config, error = _validate_demo_user_config(
1034
- demo_user_config, slug_id
1035
- )
997
+ validated_config, error = _validate_demo_user_config(demo_user_config, slug_id)
1036
998
  if not validated_config:
1037
999
  if error:
1038
1000
  logger.warning(error)
@@ -1095,11 +1057,9 @@ async def get_or_create_demo_user_for_request(
1095
1057
  request: Request,
1096
1058
  slug_id: str,
1097
1059
  db,
1098
- config: Optional[Dict[str, Any]] = None,
1099
- get_app_config_func: Optional[
1100
- Callable[[Request, str, Dict], Awaitable[Dict]]
1101
- ] = None,
1102
- ) -> 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:
1103
1063
  """
1104
1064
  Get or create a demo user for the current request context.
1105
1065
 
@@ -1146,9 +1106,7 @@ async def get_or_create_demo_user_for_request(
1146
1106
  # Check if app demo user exists
1147
1107
  # Use getattr to access collection (works with ScopedMongoWrapper and AppDB)
1148
1108
  collection = getattr(db, collection_name)
1149
- app_demo = await collection.find_one(
1150
- {"email": DEMO_EMAIL_DEFAULT}
1151
- )
1109
+ app_demo = await collection.find_one({"email": DEMO_EMAIL_DEFAULT})
1152
1110
 
1153
1111
  if app_demo:
1154
1112
  app_demo["app_user_id"] = str(app_demo["_id"])
@@ -1158,15 +1116,11 @@ async def get_or_create_demo_user_for_request(
1158
1116
  try:
1159
1117
  from ..config import DB_NAME, MONGO_URI
1160
1118
 
1161
- await ensure_demo_users_exist(
1162
- db, slug_id, config, MONGO_URI, DB_NAME
1163
- )
1119
+ await ensure_demo_users_exist(db, slug_id, config, MONGO_URI, DB_NAME)
1164
1120
  # Use getattr to access collection (works with
1165
1121
  # ScopedMongoWrapper and AppDB)
1166
1122
  collection = getattr(db, collection_name)
1167
- app_demo = await collection.find_one(
1168
- {"email": DEMO_EMAIL_DEFAULT}
1169
- )
1123
+ app_demo = await collection.find_one({"email": DEMO_EMAIL_DEFAULT})
1170
1124
  if app_demo:
1171
1125
  app_demo["app_user_id"] = str(app_demo["_id"])
1172
1126
  return app_demo
@@ -1195,10 +1149,10 @@ async def get_or_create_demo_user_for_request(
1195
1149
  async def get_or_create_demo_user(
1196
1150
  db,
1197
1151
  slug_id: str,
1198
- config: Dict[str, Any],
1199
- mongo_uri: Optional[str] = None,
1200
- db_name: Optional[str] = None,
1201
- ) -> 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:
1202
1156
  """
1203
1157
  Get or create a demo user for an app.
1204
1158
 
@@ -1232,9 +1186,7 @@ async def get_or_create_demo_user(
1232
1186
  f"db_name={'provided' if db_name else 'not provided'})"
1233
1187
  )
1234
1188
 
1235
- demo_users = await ensure_demo_users_exist(
1236
- db, slug_id, config, mongo_uri, db_name
1237
- )
1189
+ demo_users = await ensure_demo_users_exist(db, slug_id, config, mongo_uri, db_name)
1238
1190
 
1239
1191
  if demo_users and len(demo_users) > 0:
1240
1192
  # Return the first demo user (usually the primary one)
@@ -1299,7 +1251,7 @@ async def get_or_create_demo_user(
1299
1251
 
1300
1252
  async def ensure_demo_users_for_actor(
1301
1253
  db, slug_id: str, mongo_uri: str, db_name: str
1302
- ) -> List[Dict[str, Any]]:
1254
+ ) -> list[dict[str, Any]]:
1303
1255
  """
1304
1256
  Convenience function for actors to ensure demo users exist.
1305
1257
 
@@ -1366,12 +1318,10 @@ async def ensure_demo_users_for_actor(
1366
1318
  return []
1367
1319
 
1368
1320
  try:
1369
- with open(manifest_path, "r") as f:
1321
+ with open(manifest_path) as f:
1370
1322
  config = json.load(f)
1371
1323
  except json.JSONDecodeError as e:
1372
- logger.error(
1373
- f"Invalid JSON in manifest.json for {slug_id}: {e}", exc_info=True
1374
- )
1324
+ logger.error(f"Invalid JSON in manifest.json for {slug_id}: {e}", exc_info=True)
1375
1325
  return []
1376
1326
 
1377
1327
  # Ensure demo users exist
@@ -1395,17 +1345,15 @@ async def ensure_demo_users_for_actor(
1395
1345
  KeyError,
1396
1346
  PermissionError,
1397
1347
  ) as e:
1398
- logger.error(
1399
- f"Error ensuring demo users for actor {slug_id}: {e}", exc_info=True
1400
- )
1348
+ logger.error(f"Error ensuring demo users for actor {slug_id}: {e}", exc_info=True)
1401
1349
  return []
1402
1350
 
1403
1351
 
1404
1352
  async def sync_app_user_to_casbin(
1405
- user: Dict[str, Any],
1353
+ user: dict[str, Any],
1406
1354
  authz_provider,
1407
- role: Optional[str] = None,
1408
- app_slug: Optional[str] = None,
1355
+ role: str | None = None,
1356
+ app_slug: str | None = None,
1409
1357
  ) -> bool:
1410
1358
  """
1411
1359
  Sync app-level user to Casbin by assigning a role.
@@ -1425,9 +1373,7 @@ async def sync_app_user_to_casbin(
1425
1373
  try:
1426
1374
  # Check if provider is CasbinAdapter
1427
1375
  if not hasattr(authz_provider, "_enforcer"):
1428
- logger.debug(
1429
- "sync_app_user_to_casbin: Provider is not CasbinAdapter, skipping"
1430
- )
1376
+ logger.debug("sync_app_user_to_casbin: Provider is not CasbinAdapter, skipping")
1431
1377
  return False
1432
1378
 
1433
1379
  enforcer = authz_provider._enforcer
@@ -1448,9 +1394,7 @@ async def sync_app_user_to_casbin(
1448
1394
  # Check if role assignment already exists
1449
1395
  existing_roles = await enforcer.get_roles_for_user(subject)
1450
1396
  if role in existing_roles:
1451
- logger.debug(
1452
- f"sync_app_user_to_casbin: User {subject} already has role {role}"
1453
- )
1397
+ logger.debug(f"sync_app_user_to_casbin: User {subject} already has role {role}")
1454
1398
  return True
1455
1399
 
1456
1400
  # Add grouping policy: user -> role
@@ -1479,15 +1423,11 @@ async def sync_app_user_to_casbin(
1479
1423
  ConnectionError,
1480
1424
  KeyError,
1481
1425
  ) as e:
1482
- logger.error(
1483
- f"sync_app_user_to_casbin: Error syncing user to Casbin: {e}", exc_info=True
1484
- )
1426
+ logger.error(f"sync_app_user_to_casbin: Error syncing user to Casbin: {e}", exc_info=True)
1485
1427
  return False
1486
1428
 
1487
1429
 
1488
- def get_app_user_role(
1489
- user: Dict[str, Any], config: Optional[Dict[str, Any]] = None
1490
- ) -> str:
1430
+ def get_app_user_role(user: dict[str, Any], config: dict[str, Any] | None = None) -> str:
1491
1431
  """
1492
1432
  Determine Casbin role for app-level user.
1493
1433