fastapi-rtk 1.0.8__tar.gz → 1.0.9__tar.gz

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 (135) hide show
  1. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/PKG-INFO +1 -1
  2. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/__init__.py +0 -1
  3. fastapi_rtk-1.0.9/fastapi_rtk/_version.py +1 -0
  4. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/auth/auth.py +0 -9
  5. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/bases/file_manager.py +12 -0
  6. fastapi_rtk-1.0.9/fastapi_rtk/dependencies.py +256 -0
  7. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/fastapi_react_toolkit.py +101 -157
  8. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/file_managers/s3_file_manager.py +63 -32
  9. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/security/sqla/apis.py +18 -3
  10. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/security/sqla/models.py +8 -27
  11. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/security/sqla/security_manager.py +367 -10
  12. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/utils/hooks.py +7 -4
  13. fastapi_rtk-1.0.8/fastapi_rtk/_version.py +0 -1
  14. fastapi_rtk-1.0.8/fastapi_rtk/dependencies.py +0 -210
  15. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/.gitignore +0 -0
  16. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/LICENSE +0 -0
  17. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/README.md +0 -0
  18. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/api/__init__.py +0 -0
  19. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/api/base_api.py +0 -0
  20. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/api/model_rest_api.py +0 -0
  21. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/apis.py +0 -0
  22. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/auth/__init__.py +0 -0
  23. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/auth/hashers/__init__.py +0 -0
  24. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/auth/hashers/pbkdf2.py +0 -0
  25. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/auth/hashers/scrypt.py +0 -0
  26. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/auth/hashers/utils.py +0 -0
  27. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/auth/password_helpers/__init__.py +0 -0
  28. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/auth/password_helpers/fab.py +0 -0
  29. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/auth/strategies/__init__.py +0 -0
  30. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/auth/strategies/config.py +0 -0
  31. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/auth/strategies/db.py +0 -0
  32. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/auth/strategies/jwt.py +0 -0
  33. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/backends/__init__.py +0 -0
  34. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/backends/generic/__init__.py +0 -0
  35. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/backends/generic/column.py +0 -0
  36. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/backends/generic/db.py +0 -0
  37. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/backends/generic/exceptions.py +0 -0
  38. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/backends/generic/filters.py +0 -0
  39. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/backends/generic/interface.py +0 -0
  40. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/backends/generic/model.py +0 -0
  41. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/backends/generic/session.py +0 -0
  42. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/backends/sqla/__init__.py +0 -0
  43. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/backends/sqla/column.py +0 -0
  44. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/backends/sqla/db.py +0 -0
  45. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/backends/sqla/exceptions.py +0 -0
  46. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/backends/sqla/extensions/__init__.py +0 -0
  47. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/backends/sqla/extensions/audit/__init__.py +0 -0
  48. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/backends/sqla/extensions/audit/audit.py +0 -0
  49. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/backends/sqla/extensions/audit/types.py +0 -0
  50. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/backends/sqla/extensions/geoalchemy2/__init__.py +0 -0
  51. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/backends/sqla/extensions/geoalchemy2/filters.py +0 -0
  52. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/backends/sqla/extensions/geoalchemy2/geometry_converter.py +0 -0
  53. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/backends/sqla/filters.py +0 -0
  54. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/backends/sqla/interface.py +0 -0
  55. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/backends/sqla/model.py +0 -0
  56. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/backends/sqla/session.py +0 -0
  57. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/bases/__init__.py +0 -0
  58. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/bases/db.py +0 -0
  59. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/bases/filter.py +0 -0
  60. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/bases/interface.py +0 -0
  61. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/bases/model.py +0 -0
  62. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/bases/session.py +0 -0
  63. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/cli/__init__.py +0 -0
  64. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/cli/cli.py +0 -0
  65. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/cli/commands/__init__.py +0 -0
  66. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/cli/commands/db/__init__.py +0 -0
  67. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/cli/commands/db/templates/fastapi/README +0 -0
  68. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/cli/commands/db/templates/fastapi/alembic.ini.mako +0 -0
  69. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/cli/commands/db/templates/fastapi/env.py +0 -0
  70. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/cli/commands/db/templates/fastapi/script.py.mako +0 -0
  71. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/cli/commands/db/templates/fastapi-multidb/README +0 -0
  72. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/cli/commands/db/templates/fastapi-multidb/alembic.ini.mako +0 -0
  73. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/cli/commands/db/templates/fastapi-multidb/env.py +0 -0
  74. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/cli/commands/db/templates/fastapi-multidb/script.py.mako +0 -0
  75. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/cli/commands/export.py +0 -0
  76. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/cli/commands/security.py +0 -0
  77. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/cli/commands/translate.py +0 -0
  78. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/cli/const.py +0 -0
  79. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/cli/decorators.py +0 -0
  80. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/cli/types.py +0 -0
  81. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/cli/utils.py +0 -0
  82. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/config.py +0 -0
  83. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/const.py +0 -0
  84. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/db.py +0 -0
  85. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/decorators.py +0 -0
  86. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/exceptions.py +0 -0
  87. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/file_managers/__init__.py +0 -0
  88. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/file_managers/file_manager.py +0 -0
  89. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/file_managers/image_manager.py +0 -0
  90. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/file_managers/s3_image_manager.py +0 -0
  91. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/filters.py +0 -0
  92. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/globals.py +0 -0
  93. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/lang/__init__.py +0 -0
  94. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/lang/babel/__init__.py +0 -0
  95. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/lang/babel/cli.py +0 -0
  96. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/lang/babel/config.py +0 -0
  97. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/lang/babel.cfg +0 -0
  98. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/lang/lazy_text.py +0 -0
  99. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/lang/messages.pot +0 -0
  100. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/lang/translations/de/LC_MESSAGES/messages.mo +0 -0
  101. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/lang/translations/de/LC_MESSAGES/messages.po +0 -0
  102. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/lang/translations/en/LC_MESSAGES/messages.mo +0 -0
  103. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/lang/translations/en/LC_MESSAGES/messages.po +0 -0
  104. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/manager.py +0 -0
  105. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/middlewares.py +0 -0
  106. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/mixins.py +0 -0
  107. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/models.py +0 -0
  108. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/routers.py +0 -0
  109. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/schemas.py +0 -0
  110. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/security/__init__.py +0 -0
  111. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/security/sqla/__init__.py +0 -0
  112. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/setting.py +0 -0
  113. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/types.py +0 -0
  114. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/utils/__init__.py +0 -0
  115. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/utils/async_task_runner.py +0 -0
  116. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/utils/class_factory.py +0 -0
  117. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/utils/csv_json_converter.py +0 -0
  118. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/utils/deep_merge.py +0 -0
  119. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/utils/extender_mixin.py +0 -0
  120. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/utils/flask_appbuilder_utils.py +0 -0
  121. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/utils/lazy.py +0 -0
  122. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/utils/merge_schema.py +0 -0
  123. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/utils/multiple_async_contexts.py +0 -0
  124. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/utils/prettify_dict.py +0 -0
  125. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/utils/pydantic.py +0 -0
  126. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/utils/run_utils.py +0 -0
  127. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/utils/self_dependencies.py +0 -0
  128. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/utils/smartdefaultdict.py +0 -0
  129. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/utils/sqla.py +0 -0
  130. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/utils/timezone.py +0 -0
  131. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/utils/update_signature.py +0 -0
  132. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/utils/use_default_when_none.py +0 -0
  133. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/utils/werkzeug.py +0 -0
  134. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/fastapi_rtk/version.py +0 -0
  135. {fastapi_rtk-1.0.8 → fastapi_rtk-1.0.9}/pyproject.toml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastapi-rtk
3
- Version: 1.0.8
3
+ Version: 1.0.9
4
4
  Summary: A package that provides a set of tools to build a FastAPI application with a Class-Based CRUD API.
5
5
  Project-URL: Homepage, https://codeberg.org/datatactics/fastapi-rtk
6
6
  Project-URL: Issues, https://codeberg.org/datatactics/fastapi-rtk/issues
@@ -156,7 +156,6 @@ __all__ = [
156
156
  "docs",
157
157
  # .dependencies
158
158
  "set_global_user",
159
- "permissions",
160
159
  "current_permissions",
161
160
  "has_access_dependency",
162
161
  # .exceptions
@@ -0,0 +1 @@
1
+ __version__ = "1.0.9"
@@ -142,15 +142,6 @@ class Authenticator(BaseAuthenticator):
142
142
  except HTTPException as e:
143
143
  if not default_to_none:
144
144
  raise e
145
-
146
- # Retrieve list of apis, that user has access to
147
- if user:
148
- user.permissions = []
149
- for role in user.roles:
150
- for permission_api in role.permissions:
151
- user.permissions.append(permission_api.api.name)
152
- user.permissions = list(set(user.permissions))
153
-
154
145
  return user, token
155
146
 
156
147
 
@@ -35,6 +35,18 @@ class AbstractFileManager(abc.ABC):
35
35
  namegen: typing.Callable[[str], str] | None = None,
36
36
  permission: int | None = None,
37
37
  ):
38
+ """
39
+ Initializes the AbstractFileManager.
40
+
41
+ Args:
42
+ base_path (str | None, optional): Base path for file storage. Defaults to None.
43
+ allowed_extensions (list[str] | None, optional): Allowed file extensions. Defaults to None.
44
+ namegen (typing.Callable[[str], str] | None, optional): Callable for generating file names. Defaults to None.
45
+ permission (int | None, optional): File permission settings. Defaults to None.
46
+
47
+ Raises:
48
+ ValueError: If `base_path` is not set.
49
+ """
38
50
  if base_path is not None:
39
51
  self.base_path = base_path
40
52
  if allowed_extensions is not None:
@@ -0,0 +1,256 @@
1
+ import fastapi
2
+ import sqlalchemy
3
+ from fastapi import Depends, HTTPException
4
+
5
+ from .api.base_api import BaseApi
6
+ from .const import PERMISSION_PREFIX, ErrorCode, logger
7
+ from .db import db
8
+ from .globals import g
9
+ from .security.sqla.models import Api, Permission, PermissionApi, Role, User
10
+ from .utils import smart_run
11
+
12
+ __all__ = [
13
+ "set_global_user",
14
+ "current_permissions",
15
+ "has_access_dependency",
16
+ ]
17
+
18
+
19
+ def set_global_user():
20
+ """
21
+ A dependency for FastAPI that will set the current user to the global variable `g.user`.
22
+
23
+ Usage:
24
+ ```python
25
+ async def get_info(
26
+ *,
27
+ session: AsyncSession | Session = Depends(get_async_session),
28
+ none: None = Depends(set_global_user()),
29
+ ):
30
+ ...more code
31
+ """
32
+
33
+ async def set_global_user_dependency(
34
+ user: User | None = Depends(
35
+ g.auth.fastapi_users.current_user(active=True, default_to_none=True)
36
+ ),
37
+ ):
38
+ g.user = user
39
+
40
+ return set_global_user_dependency
41
+
42
+
43
+ def check_g_user():
44
+ """
45
+ A dependency for FastAPI that will check if the current user is set to the global variable `g.user`.
46
+
47
+ Usage:
48
+ ```python
49
+ async def get_info(
50
+ *,
51
+ session: AsyncSession | Session = Depends(get_async_session),
52
+ none: None = Depends(check_g_user()),
53
+ ):
54
+ ...more code
55
+ """
56
+
57
+ async def check_g_user_dependency():
58
+ if not g.user:
59
+ raise HTTPException(
60
+ fastapi.status.HTTP_401_UNAUTHORIZED,
61
+ ErrorCode.GET_USER_MISSING_TOKEN_OR_INACTIVE_USER,
62
+ )
63
+
64
+ return check_g_user_dependency
65
+
66
+
67
+ def current_permissions(api: BaseApi):
68
+ """
69
+ A dependency for FastAPI that will return all permissions of the current user for the specified API.
70
+
71
+ Because it will implicitly check whether the user is authenticated, it can return `401 Unauthorized` or `403 Forbidden`.
72
+
73
+ Args:
74
+ api (BaseApi): The API to be checked.
75
+
76
+ Usage:
77
+ ```python
78
+ async def get_info(
79
+ *,
80
+ permissions: List[str] = Depends(current_permissions(self)),
81
+ session: AsyncSession | Session = Depends(get_async_session),
82
+ ):
83
+ ...more code
84
+ ```
85
+ """
86
+
87
+ async def current_permissions_depedency(_=Depends(_ensure_roles)):
88
+ sm = g.current_app.security
89
+ api_name = api.__class__.__name__
90
+ permissions = set[str]()
91
+ db_role_ids = list[int]()
92
+
93
+ # Retrieve permissions from built-in roles
94
+ for role in g.user.roles:
95
+ if role.name not in sm.builtin_roles:
96
+ db_role_ids.append(role.id)
97
+ continue
98
+
99
+ api_permission_tuples = sm.get_api_permission_tuples_from_builtin_roles(
100
+ role.name
101
+ )
102
+ for multi_apis_str, multi_perms_str in api_permission_tuples:
103
+ api_names = multi_apis_str.split("|")
104
+ perm_names = multi_perms_str.split("|")
105
+ if api_name in api_names:
106
+ permissions.update(perm_names)
107
+
108
+ if db_role_ids:
109
+ query = (
110
+ sqlalchemy.select(Permission)
111
+ .join(PermissionApi)
112
+ .join(PermissionApi.roles)
113
+ .join(Api)
114
+ .where(Api.name == api_name, Role.id.in_(db_role_ids))
115
+ )
116
+ if api.base_permissions:
117
+ query = query.where(Permission.name.in_(api.base_permissions))
118
+
119
+ permissions_in_db = await smart_run(db.current_session.scalars, query)
120
+ permissions.update(perm.name for perm in permissions_in_db.all())
121
+
122
+ if api.base_permissions:
123
+ permissions = permissions.intersection(set(api.base_permissions))
124
+
125
+ return list(permissions)
126
+
127
+ return current_permissions_depedency
128
+
129
+
130
+ def has_access_dependency(
131
+ api: BaseApi,
132
+ permission: str,
133
+ ):
134
+ """
135
+ A dependency for FastAPI to check whether current user has access to the specified API and permission.
136
+
137
+ Because it will implicitly check whether the user is authenticated, it can return `401 Unauthorized` or `403 Forbidden`.
138
+
139
+ Usage:
140
+ ```python
141
+ @self.router.get(
142
+ "/_info",
143
+ response_model=self.info_return_schema,
144
+ dependencies=[Depends(has_access(self, "info"))],
145
+ )
146
+ ...more code
147
+ ```
148
+
149
+ Args:
150
+ api (BaseApi): The API to be checked.
151
+ permission (str): The permission to check.
152
+ """
153
+ permission = f"{PERMISSION_PREFIX}{permission}"
154
+
155
+ async def check_permission():
156
+ _ensure_roles(ErrorCode.PERMISSION_DENIED)
157
+
158
+ # First, check built-in roles (avoiding unnecessary DB queries)
159
+ # This also covers the case for API and permission name with pipes
160
+ sm = g.current_app.security
161
+ if any(
162
+ sm.has_access_in_builtin_roles(
163
+ role.name, api.__class__.__name__, permission
164
+ )
165
+ for role in g.user.roles
166
+ ):
167
+ logger.debug(
168
+ f"User {g.user} has access to {api.__class__.__name__} with permission {permission} via built-in roles."
169
+ )
170
+ return
171
+
172
+ db_role_ids = [
173
+ role.id for role in g.user.roles if role.name not in sm.builtin_roles
174
+ ]
175
+ if not db_role_ids:
176
+ raise HTTPException(
177
+ fastapi.status.HTTP_403_FORBIDDEN, ErrorCode.PERMISSION_DENIED
178
+ )
179
+
180
+ api_name = api.__class__.__name__
181
+ exist_query = (
182
+ sqlalchemy.select(Permission)
183
+ .join(PermissionApi)
184
+ .join(PermissionApi.roles)
185
+ .join(Api)
186
+ .where(
187
+ Api.name == api_name,
188
+ Permission.name == permission,
189
+ Role.id.in_(db_role_ids),
190
+ )
191
+ .exists()
192
+ )
193
+ result: bool = await smart_run(
194
+ db.current_session.scalar, sqlalchemy.select(exist_query)
195
+ )
196
+ if result:
197
+ return
198
+
199
+ raise HTTPException(
200
+ fastapi.status.HTTP_403_FORBIDDEN, ErrorCode.PERMISSION_DENIED
201
+ )
202
+
203
+ return check_permission
204
+
205
+
206
+ async def set_global_background_tasks(background_tasks: fastapi.BackgroundTasks):
207
+ """
208
+ A dependency for FastAPI that will set the `background_tasks` to the global variable `g.background_tasks`.
209
+
210
+ Usage:
211
+ ```python
212
+ async def get_info(
213
+ *,
214
+ session: AsyncSession | Session = Depends(get_async_session),
215
+ none: None = Depends(set_global_background_tasks),
216
+ ):
217
+ ...more code
218
+ """
219
+ g.background_tasks = background_tasks
220
+
221
+
222
+ async def set_global_request(request: fastapi.Request):
223
+ """
224
+ A dependency for FastAPI that will set the `request` to the global variable `g.request`.
225
+
226
+ Usage:
227
+ ```python
228
+ async def get_info(
229
+ *,
230
+ session: AsyncSession | Session = Depends(get_async_session),
231
+ none: None = Depends(set_global_request),
232
+ ):
233
+ ...more code
234
+ """
235
+ g.request = request
236
+
237
+
238
+ def _ensure_roles(err_forbidden_message=ErrorCode.GET_USER_NO_ROLES):
239
+ """
240
+ A dependency for FastAPI that will ensure the current user has roles assigned.
241
+
242
+ Args:
243
+ err_forbidden_message (str): The error message to be used when raising `403 Forbidden`. Defaults to `ErrorCode.GET_USER_NO_ROLES`.
244
+
245
+ Raises:
246
+ HTTPException: Raised when the user is not authenticated.
247
+ HTTPException: Raised when the user has no roles assigned.
248
+ """
249
+ if not g.user:
250
+ raise HTTPException(
251
+ fastapi.status.HTTP_401_UNAUTHORIZED,
252
+ ErrorCode.GET_USER_MISSING_TOKEN_OR_INACTIVE_USER,
253
+ )
254
+
255
+ if not g.user.roles:
256
+ raise HTTPException(fastapi.status.HTTP_403_FORBIDDEN, err_forbidden_message)
@@ -16,13 +16,11 @@ from fastapi.templating import Jinja2Templates
16
16
  from fastapi_babel import BabelMiddleware
17
17
  from jinja2 import Environment, TemplateNotFound, select_autoescape
18
18
  from prometheus_fastapi_instrumentator import Instrumentator
19
- from sqlalchemy import and_, select
20
- from sqlalchemy.ext.asyncio import AsyncSession
21
- from sqlalchemy.orm import Session
22
19
  from starlette.routing import _DefaultLifespan
23
20
 
24
21
  from .api.model_rest_api import ModelRestApi
25
22
  from .auth import Auth
23
+ from .backends.sqla.session import SQLASession
26
24
  from .cli.commands.db import upgrade
27
25
  from .cli.commands.translate import init_babel_cli
28
26
  from .const import (
@@ -418,175 +416,121 @@ class FastAPIReactToolkit:
418
416
 
419
417
  async with db.session() as session:
420
418
  logger.info("INITIALIZING DATABASE")
421
- await self._insert_permissions(session)
422
- await self._insert_apis(session)
423
- await self._insert_roles(session)
424
- await self._associate_permission_with_api(session)
425
- await self._associate_permission_api_with_role(session)
419
+ permissions = await self._insert_permissions(session)
420
+ apis = await self._insert_apis(session)
421
+ roles = await self._insert_roles(session)
422
+ assert permissions is not None, "Permissions should not be None"
423
+ assert apis is not None, "APIs should not be None"
424
+ permission_apis = await self._associate_permission_with_api(
425
+ session, permissions, apis
426
+ )
427
+ assert roles is not None, "Roles should not be None"
428
+ assert permission_apis is not None, "PermissionApis should not be None"
429
+ await self._associate_role_with_permission_api(
430
+ session, roles, permission_apis
431
+ )
426
432
  if self.cleanup:
427
433
  await self.security.cleanup()
428
434
  logger.info("DATABASE INITIALIZED")
429
435
 
430
- async def _insert_permissions(self, session: AsyncSession | Session):
431
- new_permissions = self.total_permissions()
432
- stmt = select(Permission).where(Permission.name.in_(new_permissions))
433
- result = await safe_call(session.scalars(stmt))
434
- existing_permissions = [permission.name for permission in result.all()]
435
- if len(new_permissions) == len(existing_permissions):
436
- return
437
-
438
- permission_objs = [
439
- Permission(name=permission)
440
- for permission in new_permissions
441
- if permission not in existing_permissions
436
+ async def _insert_permissions(self, session: SQLASession):
437
+ permissions = self.total_permissions() + [
438
+ x[1] for x in self.security.get_api_permission_tuples_from_builtin_roles()
442
439
  ]
443
- for permission in permission_objs:
444
- logger.info(f"ADDING PERMISSION {permission}")
445
- session.add(permission)
446
- await safe_call(session.commit())
447
-
448
- async def _insert_apis(self, session: AsyncSession | Session):
449
- new_apis = [api.__class__.__name__ for api in self.apis]
450
- stmt = select(Api).where(Api.name.in_(new_apis))
451
- result = await safe_call(session.scalars(stmt))
452
- existing_apis = [api.name for api in result.all()]
453
- if len(new_apis) == len(existing_apis):
454
- return
455
-
456
- api_objs = [Api(name=api) for api in new_apis if api not in existing_apis]
457
- for api in api_objs:
458
- logger.info(f"ADDING API {api}")
459
- session.add(api)
460
- await safe_call(session.commit())
461
-
462
- async def _insert_roles(self, session: AsyncSession | Session):
463
- new_roles = [x for x in [g.admin_role, g.public_role] if x is not None]
464
- stmt = select(Role).where(Role.name.in_(new_roles))
465
- result = await safe_call(session.scalars(stmt))
466
- existing_roles = [role.name for role in result.all()]
467
- if len(new_roles) == len(existing_roles):
468
- return
440
+ permissions = list(dict.fromkeys(permissions))
441
+ return await self.security.create_permissions(permissions, session=session)
469
442
 
470
- role_objs = [
471
- Role(name=role) for role in new_roles if role not in existing_roles
443
+ async def _insert_apis(self, session: SQLASession):
444
+ apis = [api.__class__.__name__ for api in self.apis] + [
445
+ x[0] for x in self.security.get_api_permission_tuples_from_builtin_roles()
472
446
  ]
473
- for role in role_objs:
474
- logger.info(f"ADDING ROLE {role}")
475
- session.add(role)
476
- await safe_call(session.commit())
447
+ apis = list(dict.fromkeys(apis))
448
+ return await self.security.create_apis(apis, session=session)
449
+
450
+ async def _insert_roles(self, session: SQLASession):
451
+ roles = self.security.get_roles_from_builtin_roles()
452
+ if g.admin_role and g.admin_role not in roles:
453
+ roles.append(g.admin_role)
454
+ if g.public_role and g.public_role not in roles:
455
+ roles.append(g.public_role)
456
+ return await self.security.create_roles(roles, session=session)
457
+
458
+ async def _associate_permission_with_api(
459
+ self,
460
+ session: SQLASession,
461
+ permissions: list[Permission],
462
+ apis: list[Api],
463
+ ):
464
+ permission_map = {permission.name: permission for permission in permissions}
465
+ api_map = {api.name: api for api in apis}
466
+ permission_api_tuples = list[tuple[Permission, Api]]()
467
+ added_permission_api = set[tuple[str, str]]()
477
468
 
478
- async def _associate_permission_with_api(self, session: AsyncSession | Session):
479
469
  for api in self.apis:
480
- new_permissions = getattr(api, "permissions", [])
481
- if not new_permissions:
470
+ api_permissions = api.permissions
471
+ if not api_permissions:
482
472
  continue
483
473
 
484
- # Get the api object
485
- api_stmt = select(Api).where(Api.name == api.__class__.__name__)
486
- api_obj = await safe_call(session.scalar(api_stmt))
487
-
488
- if not api_obj:
489
- raise ValueError(f"API {api.__class__.__name__} not found")
490
-
491
- permission_stmt = select(Permission).where(
492
- and_(
493
- Permission.name.in_(new_permissions),
494
- ~Permission.id.in_([p.permission_id for p in api_obj.permissions]),
495
- )
496
- )
497
- permission_result = await safe_call(session.scalars(permission_stmt))
498
- new_permissions = permission_result.all()
499
-
500
- if not new_permissions:
474
+ for permission_name in api_permissions:
475
+ if (permission_name, api.__class__.__name__) in added_permission_api:
476
+ continue
477
+ permission = permission_map[permission_name]
478
+ api_obj = api_map[api.__class__.__name__]
479
+ permission_api_tuples.append((permission, api_obj))
480
+ added_permission_api.add((permission.name, api_obj.name))
481
+
482
+ for (
483
+ api_name,
484
+ perm_name,
485
+ ) in self.security.get_api_permission_tuples_from_builtin_roles():
486
+ if (perm_name, api_name) in added_permission_api:
501
487
  continue
488
+ permission = permission_map[perm_name]
489
+ api_obj = api_map[api_name]
490
+ permission_api_tuples.append((permission, api_obj))
491
+ added_permission_api.add((permission.name, api_obj.name))
502
492
 
503
- for permission in new_permissions:
504
- session.add(
505
- PermissionApi(permission_id=permission.id, api_id=api_obj.id)
506
- )
507
- logger.info(f"ASSOCIATING PERMISSION {permission} WITH API {api_obj}")
508
- await safe_call(session.commit())
493
+ return await self.security.associate_list_of_permission_with_api(
494
+ permission_api_tuples, session=session
495
+ )
509
496
 
510
- async def _associate_permission_api_with_role(
511
- self, session: AsyncSession | Session
497
+ async def _associate_role_with_permission_api(
498
+ self,
499
+ session: SQLASession,
500
+ roles: list[Role],
501
+ permission_apis: list[PermissionApi],
512
502
  ):
513
- # Read config based roles
514
- roles_dict = Setting.ROLES
515
-
516
- for role_name, role_permissions in roles_dict.items():
517
- role_stmt = select(Role).where(Role.name == role_name)
518
- role_result = await safe_call(session.scalars(role_stmt))
519
- role = role_result.first()
520
- if not role:
521
- role = Role(name=role_name)
522
- logger.info(f"ADDING ROLE {role}")
523
- session.add(role)
524
-
525
- for api_name, permission_name in role_permissions:
526
- permission_api_stmt = (
527
- select(PermissionApi)
528
- .where(
529
- and_(Api.name == api_name, Permission.name == permission_name)
530
- )
531
- .join(Permission)
532
- .join(Api)
533
- )
534
- permission_api = await safe_call(session.scalar(permission_api_stmt))
535
- if not permission_api:
536
- permission_stmt = select(Permission).where(
537
- Permission.name == permission_name
538
- )
539
- permission = await safe_call(session.scalar(permission_stmt))
540
- if not permission:
541
- permission = Permission(name=permission_name)
542
- logger.info(f"ADDING PERMISSION {permission}")
543
- session.add(permission)
544
-
545
- stmt = select(Api).where(Api.name == api_name)
546
- api = await safe_call(session.scalar(stmt))
547
- if not api:
548
- api = Api(name=api_name)
549
- logger.info(f"ADDING API {api}")
550
- session.add(api)
551
-
552
- permission_api = PermissionApi(permission=permission, api=api)
553
- logger.info(f"ADDING PERMISSION-API {permission_api}")
554
- session.add(permission_api)
555
-
556
- # Associate role with permission-api
557
- if role not in permission_api.roles:
558
- permission_api.roles.append(role)
559
- logger.info(
560
- f"ASSOCIATING {role} WITH PERMISSION-API {permission_api}"
561
- )
562
-
563
- await safe_call(session.commit())
564
-
565
- # Get admin role
566
- if g.admin_role is None:
567
- logger.warning("Admin role is not set, skipping admin role association")
568
- return
569
-
570
- admin_role_stmt = select(Role).where(Role.name == g.admin_role)
571
- admin_role = await safe_call(session.scalar(admin_role_stmt))
572
-
573
- if admin_role:
574
- # Get list of permission-api.assoc_permission_api_id of the admin role
575
- stmt = (
576
- select(PermissionApi)
577
- .where(~PermissionApi.roles.contains(admin_role))
578
- .join(Api)
579
- )
580
- result = await safe_call(session.scalars(stmt))
581
- existing_assoc_permission_api_roles = result.all()
582
-
583
- # Add admin role to all permission-api objects
584
- for permission_api in existing_assoc_permission_api_roles:
585
- permission_api.roles.append(admin_role)
586
- logger.info(
587
- f"ASSOCIATING {admin_role} WITH PERMISSION-API {permission_api}"
588
- )
589
- await safe_call(session.commit())
503
+ role_map = {role.name: role for role in roles}
504
+ permission_api_map = {
505
+ (pa.permission.name, pa.api.name): pa for pa in permission_apis
506
+ }
507
+ permission_apis_to_exclude_from_admin = list[PermissionApi]()
508
+ role_permission_api_tuples = list[tuple[Role, PermissionApi]]()
509
+
510
+ for (
511
+ role_name,
512
+ role_api_permissions,
513
+ ) in self.security.get_role_and_api_permission_tuples_from_builtin_roles():
514
+ for api_name, permission_name in role_api_permissions:
515
+ role = role_map[role_name]
516
+ permission_api = permission_api_map[(permission_name, api_name)]
517
+ role_permission_api_tuples.append((role, permission_api))
518
+ if "|" in permission_name and role_name != g.public_role:
519
+ # Exclude multi-tenant permissions from admin role if not explicitly set
520
+ permission_apis_to_exclude_from_admin.append(permission_api)
521
+
522
+ if g.admin_role:
523
+ admin_role = role_map[g.admin_role]
524
+ for permission_api in [
525
+ pa
526
+ for pa in permission_apis
527
+ if pa not in permission_apis_to_exclude_from_admin
528
+ ]:
529
+ role_permission_api_tuples.append((admin_role, permission_api))
530
+
531
+ await self.security.associate_list_of_role_with_permission_api(
532
+ role_permission_api_tuples, session=session
533
+ )
590
534
 
591
535
  def _mount_static_folder(self):
592
536
  """