fastapi-rtk 1.0.7__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.7 → fastapi_rtk-1.0.9}/PKG-INFO +1 -1
  2. {fastapi_rtk-1.0.7 → 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.7 → fastapi_rtk-1.0.9}/fastapi_rtk/api/model_rest_api.py +1 -0
  5. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/auth/auth.py +0 -9
  6. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/backends/sqla/db.py +8 -0
  7. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/backends/sqla/filters.py +16 -0
  8. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/backends/sqla/interface.py +11 -8
  9. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/bases/file_manager.py +12 -0
  10. fastapi_rtk-1.0.9/fastapi_rtk/dependencies.py +256 -0
  11. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/fastapi_react_toolkit.py +101 -157
  12. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/file_managers/s3_file_manager.py +63 -32
  13. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/security/sqla/apis.py +18 -3
  14. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/security/sqla/models.py +8 -27
  15. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/security/sqla/security_manager.py +367 -10
  16. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/utils/hooks.py +7 -4
  17. fastapi_rtk-1.0.7/fastapi_rtk/_version.py +0 -1
  18. fastapi_rtk-1.0.7/fastapi_rtk/dependencies.py +0 -210
  19. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/.gitignore +0 -0
  20. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/LICENSE +0 -0
  21. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/README.md +0 -0
  22. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/api/__init__.py +0 -0
  23. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/api/base_api.py +0 -0
  24. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/apis.py +0 -0
  25. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/auth/__init__.py +0 -0
  26. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/auth/hashers/__init__.py +0 -0
  27. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/auth/hashers/pbkdf2.py +0 -0
  28. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/auth/hashers/scrypt.py +0 -0
  29. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/auth/hashers/utils.py +0 -0
  30. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/auth/password_helpers/__init__.py +0 -0
  31. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/auth/password_helpers/fab.py +0 -0
  32. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/auth/strategies/__init__.py +0 -0
  33. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/auth/strategies/config.py +0 -0
  34. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/auth/strategies/db.py +0 -0
  35. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/auth/strategies/jwt.py +0 -0
  36. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/backends/__init__.py +0 -0
  37. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/backends/generic/__init__.py +0 -0
  38. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/backends/generic/column.py +0 -0
  39. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/backends/generic/db.py +0 -0
  40. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/backends/generic/exceptions.py +0 -0
  41. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/backends/generic/filters.py +0 -0
  42. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/backends/generic/interface.py +0 -0
  43. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/backends/generic/model.py +0 -0
  44. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/backends/generic/session.py +0 -0
  45. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/backends/sqla/__init__.py +0 -0
  46. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/backends/sqla/column.py +0 -0
  47. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/backends/sqla/exceptions.py +0 -0
  48. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/backends/sqla/extensions/__init__.py +0 -0
  49. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/backends/sqla/extensions/audit/__init__.py +0 -0
  50. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/backends/sqla/extensions/audit/audit.py +0 -0
  51. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/backends/sqla/extensions/audit/types.py +0 -0
  52. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/backends/sqla/extensions/geoalchemy2/__init__.py +0 -0
  53. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/backends/sqla/extensions/geoalchemy2/filters.py +0 -0
  54. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/backends/sqla/extensions/geoalchemy2/geometry_converter.py +0 -0
  55. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/backends/sqla/model.py +0 -0
  56. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/backends/sqla/session.py +0 -0
  57. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/bases/__init__.py +0 -0
  58. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/bases/db.py +0 -0
  59. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/bases/filter.py +0 -0
  60. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/bases/interface.py +0 -0
  61. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/bases/model.py +0 -0
  62. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/bases/session.py +0 -0
  63. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/cli/__init__.py +0 -0
  64. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/cli/cli.py +0 -0
  65. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/cli/commands/__init__.py +0 -0
  66. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/cli/commands/db/__init__.py +0 -0
  67. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/cli/commands/db/templates/fastapi/README +0 -0
  68. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/cli/commands/db/templates/fastapi/alembic.ini.mako +0 -0
  69. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/cli/commands/db/templates/fastapi/env.py +0 -0
  70. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/cli/commands/db/templates/fastapi/script.py.mako +0 -0
  71. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/cli/commands/db/templates/fastapi-multidb/README +0 -0
  72. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/cli/commands/db/templates/fastapi-multidb/alembic.ini.mako +0 -0
  73. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/cli/commands/db/templates/fastapi-multidb/env.py +0 -0
  74. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/cli/commands/db/templates/fastapi-multidb/script.py.mako +0 -0
  75. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/cli/commands/export.py +0 -0
  76. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/cli/commands/security.py +0 -0
  77. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/cli/commands/translate.py +0 -0
  78. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/cli/const.py +0 -0
  79. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/cli/decorators.py +0 -0
  80. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/cli/types.py +0 -0
  81. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/cli/utils.py +0 -0
  82. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/config.py +0 -0
  83. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/const.py +0 -0
  84. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/db.py +0 -0
  85. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/decorators.py +0 -0
  86. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/exceptions.py +0 -0
  87. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/file_managers/__init__.py +0 -0
  88. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/file_managers/file_manager.py +0 -0
  89. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/file_managers/image_manager.py +0 -0
  90. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/file_managers/s3_image_manager.py +0 -0
  91. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/filters.py +0 -0
  92. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/globals.py +0 -0
  93. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/lang/__init__.py +0 -0
  94. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/lang/babel/__init__.py +0 -0
  95. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/lang/babel/cli.py +0 -0
  96. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/lang/babel/config.py +0 -0
  97. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/lang/babel.cfg +0 -0
  98. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/lang/lazy_text.py +0 -0
  99. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/lang/messages.pot +0 -0
  100. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/lang/translations/de/LC_MESSAGES/messages.mo +0 -0
  101. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/lang/translations/de/LC_MESSAGES/messages.po +0 -0
  102. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/lang/translations/en/LC_MESSAGES/messages.mo +0 -0
  103. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/lang/translations/en/LC_MESSAGES/messages.po +0 -0
  104. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/manager.py +0 -0
  105. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/middlewares.py +0 -0
  106. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/mixins.py +0 -0
  107. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/models.py +0 -0
  108. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/routers.py +0 -0
  109. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/schemas.py +0 -0
  110. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/security/__init__.py +0 -0
  111. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/security/sqla/__init__.py +0 -0
  112. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/setting.py +0 -0
  113. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/types.py +0 -0
  114. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/utils/__init__.py +0 -0
  115. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/utils/async_task_runner.py +0 -0
  116. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/utils/class_factory.py +0 -0
  117. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/utils/csv_json_converter.py +0 -0
  118. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/utils/deep_merge.py +0 -0
  119. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/utils/extender_mixin.py +0 -0
  120. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/utils/flask_appbuilder_utils.py +0 -0
  121. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/utils/lazy.py +0 -0
  122. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/utils/merge_schema.py +0 -0
  123. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/utils/multiple_async_contexts.py +0 -0
  124. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/utils/prettify_dict.py +0 -0
  125. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/utils/pydantic.py +0 -0
  126. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/utils/run_utils.py +0 -0
  127. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/utils/self_dependencies.py +0 -0
  128. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/utils/smartdefaultdict.py +0 -0
  129. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/utils/sqla.py +0 -0
  130. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/utils/timezone.py +0 -0
  131. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/utils/update_signature.py +0 -0
  132. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/utils/use_default_when_none.py +0 -0
  133. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/utils/werkzeug.py +0 -0
  134. {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/version.py +0 -0
  135. {fastapi_rtk-1.0.7 → 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.7
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"
@@ -2328,6 +2328,7 @@ class ModelRestApi(BaseApi):
2328
2328
  )
2329
2329
 
2330
2330
  if self.datamodel.is_files(key) or self.datamodel.is_images(key):
2331
+ value = [x for x in value if x] # Remove None values
2331
2332
  old_filenames = (
2332
2333
  [x for x in value if isinstance(x, str)] if value else []
2333
2334
  )
@@ -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
 
@@ -454,3 +454,11 @@ class SQLAQueryBuilder(AbstractQueryBuilder[Select[tuple[T]]]):
454
454
  load_column["related_columns"][col]["type"] = "some"
455
455
 
456
456
  return load_column
457
+
458
+ def _convert_id_into_dict(self, id):
459
+ # Cast the id into the right type based on the pk column type
460
+ pk_dict = super()._convert_id_into_dict(id)
461
+ for pk in pk_dict:
462
+ col_type = self.datamodel.list_columns[pk].type.python_type
463
+ pk_dict[pk] = col_type(pk_dict[pk])
464
+ return pk_dict
@@ -617,6 +617,22 @@ class SQLAFilterConverter:
617
617
  FilterIn,
618
618
  ],
619
619
  ),
620
+ (
621
+ "is_files",
622
+ [
623
+ FilterTextContains,
624
+ # TODO: Make compatible filters
625
+ # FilterEqual,
626
+ # FilterNotEqual,
627
+ # FilterStartsWith,
628
+ # FilterNotStartsWith,
629
+ # FilterEndsWith,
630
+ # FilterNotEndsWith,
631
+ # FilterContains,
632
+ # FilterNotContains,
633
+ # FilterIn,
634
+ ],
635
+ ),
620
636
  (
621
637
  "is_integer",
622
638
  [
@@ -255,7 +255,12 @@ class SQLAInterface(AbstractInterface[ModelType, Session | AsyncSession, Column]
255
255
  unique_order_columns.update(
256
256
  [f"{col_name}.{sub_col}" for sub_col in sub_order_columns]
257
257
  )
258
- elif self.is_property(col_name) and not self.is_hybrid_property(col_name):
258
+ elif (
259
+ self.is_property(col_name)
260
+ and not self.is_hybrid_property(col_name)
261
+ or self.is_files(col_name)
262
+ or self.is_images(col_name)
263
+ ):
259
264
  continue
260
265
 
261
266
  # Allow the column to be used for ordering by default
@@ -353,14 +358,12 @@ class SQLAInterface(AbstractInterface[ModelType, Session | AsyncSession, Column]
353
358
  ]
354
359
  elif self.is_file(col) or self.is_image(col):
355
360
  value_type = fastapi.UploadFile
361
+ annotated_str_type = typing.Annotated[
362
+ str | None, BeforeValidator(lambda x: None if x == "null" else x)
363
+ ]
356
364
  if self.is_files(col) or self.is_images(col):
357
- value_type = list[value_type | str]
358
- return (
359
- value_type
360
- | typing.Annotated[
361
- str | None, BeforeValidator(lambda x: None if x == "null" else x)
362
- ]
363
- )
365
+ value_type = list[value_type | annotated_str_type]
366
+ return value_type | annotated_str_type
364
367
  elif self.is_date(col):
365
368
  return date
366
369
  elif self.is_datetime(col):
@@ -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)