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.
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/PKG-INFO +1 -1
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/__init__.py +0 -1
- fastapi_rtk-1.0.9/fastapi_rtk/_version.py +1 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/api/model_rest_api.py +1 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/auth/auth.py +0 -9
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/backends/sqla/db.py +8 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/backends/sqla/filters.py +16 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/backends/sqla/interface.py +11 -8
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/bases/file_manager.py +12 -0
- fastapi_rtk-1.0.9/fastapi_rtk/dependencies.py +256 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/fastapi_react_toolkit.py +101 -157
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/file_managers/s3_file_manager.py +63 -32
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/security/sqla/apis.py +18 -3
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/security/sqla/models.py +8 -27
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/security/sqla/security_manager.py +367 -10
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/utils/hooks.py +7 -4
- fastapi_rtk-1.0.7/fastapi_rtk/_version.py +0 -1
- fastapi_rtk-1.0.7/fastapi_rtk/dependencies.py +0 -210
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/.gitignore +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/LICENSE +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/README.md +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/api/__init__.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/api/base_api.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/apis.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/auth/__init__.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/auth/hashers/__init__.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/auth/hashers/pbkdf2.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/auth/hashers/scrypt.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/auth/hashers/utils.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/auth/password_helpers/__init__.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/auth/password_helpers/fab.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/auth/strategies/__init__.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/auth/strategies/config.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/auth/strategies/db.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/auth/strategies/jwt.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/backends/__init__.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/backends/generic/__init__.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/backends/generic/column.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/backends/generic/db.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/backends/generic/exceptions.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/backends/generic/filters.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/backends/generic/interface.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/backends/generic/model.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/backends/generic/session.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/backends/sqla/__init__.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/backends/sqla/column.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/backends/sqla/exceptions.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/backends/sqla/extensions/__init__.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/backends/sqla/extensions/audit/__init__.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/backends/sqla/extensions/audit/audit.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/backends/sqla/extensions/audit/types.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/backends/sqla/extensions/geoalchemy2/__init__.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/backends/sqla/extensions/geoalchemy2/filters.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/backends/sqla/extensions/geoalchemy2/geometry_converter.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/backends/sqla/model.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/backends/sqla/session.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/bases/__init__.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/bases/db.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/bases/filter.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/bases/interface.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/bases/model.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/bases/session.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/cli/__init__.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/cli/cli.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/cli/commands/__init__.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/cli/commands/db/__init__.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/cli/commands/db/templates/fastapi/README +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/cli/commands/db/templates/fastapi/alembic.ini.mako +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/cli/commands/db/templates/fastapi/env.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/cli/commands/db/templates/fastapi/script.py.mako +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/cli/commands/db/templates/fastapi-multidb/README +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/cli/commands/db/templates/fastapi-multidb/alembic.ini.mako +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/cli/commands/db/templates/fastapi-multidb/env.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/cli/commands/db/templates/fastapi-multidb/script.py.mako +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/cli/commands/export.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/cli/commands/security.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/cli/commands/translate.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/cli/const.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/cli/decorators.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/cli/types.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/cli/utils.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/config.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/const.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/db.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/decorators.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/exceptions.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/file_managers/__init__.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/file_managers/file_manager.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/file_managers/image_manager.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/file_managers/s3_image_manager.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/filters.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/globals.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/lang/__init__.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/lang/babel/__init__.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/lang/babel/cli.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/lang/babel/config.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/lang/babel.cfg +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/lang/lazy_text.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/lang/messages.pot +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/lang/translations/de/LC_MESSAGES/messages.mo +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/lang/translations/de/LC_MESSAGES/messages.po +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/lang/translations/en/LC_MESSAGES/messages.mo +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/lang/translations/en/LC_MESSAGES/messages.po +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/manager.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/middlewares.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/mixins.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/models.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/routers.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/schemas.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/security/__init__.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/security/sqla/__init__.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/setting.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/types.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/utils/__init__.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/utils/async_task_runner.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/utils/class_factory.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/utils/csv_json_converter.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/utils/deep_merge.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/utils/extender_mixin.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/utils/flask_appbuilder_utils.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/utils/lazy.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/utils/merge_schema.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/utils/multiple_async_contexts.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/utils/prettify_dict.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/utils/pydantic.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/utils/run_utils.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/utils/self_dependencies.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/utils/smartdefaultdict.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/utils/sqla.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/utils/timezone.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/utils/update_signature.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/utils/use_default_when_none.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/utils/werkzeug.py +0 -0
- {fastapi_rtk-1.0.7 → fastapi_rtk-1.0.9}/fastapi_rtk/version.py +0 -0
- {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.
|
|
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
|
|
@@ -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
|
|
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 |
|
|
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)
|