fastapi-rtk 0.2.27__py3-none-any.whl → 1.0.13__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.
- fastapi_rtk/__init__.py +39 -35
- fastapi_rtk/_version.py +1 -0
- fastapi_rtk/api/model_rest_api.py +476 -221
- fastapi_rtk/auth/auth.py +0 -9
- fastapi_rtk/backends/generic/__init__.py +6 -0
- fastapi_rtk/backends/generic/column.py +21 -12
- fastapi_rtk/backends/generic/db.py +42 -7
- fastapi_rtk/backends/generic/filters.py +21 -16
- fastapi_rtk/backends/generic/interface.py +14 -8
- fastapi_rtk/backends/generic/model.py +19 -11
- fastapi_rtk/backends/sqla/__init__.py +1 -0
- fastapi_rtk/backends/sqla/db.py +77 -17
- fastapi_rtk/backends/sqla/extensions/audit/audit.py +401 -189
- fastapi_rtk/backends/sqla/extensions/geoalchemy2/filters.py +15 -12
- fastapi_rtk/backends/sqla/filters.py +50 -21
- fastapi_rtk/backends/sqla/interface.py +96 -34
- fastapi_rtk/backends/sqla/model.py +56 -39
- fastapi_rtk/bases/__init__.py +20 -0
- fastapi_rtk/bases/db.py +94 -7
- fastapi_rtk/bases/file_manager.py +47 -3
- fastapi_rtk/bases/filter.py +22 -0
- fastapi_rtk/bases/interface.py +49 -5
- fastapi_rtk/bases/model.py +3 -0
- fastapi_rtk/bases/session.py +2 -0
- fastapi_rtk/cli/cli.py +62 -9
- fastapi_rtk/cli/commands/__init__.py +23 -0
- fastapi_rtk/cli/{db.py → commands/db/__init__.py} +107 -50
- fastapi_rtk/cli/{templates → commands/db/templates}/fastapi/env.py +2 -3
- fastapi_rtk/cli/{templates → commands/db/templates}/fastapi-multidb/env.py +10 -9
- fastapi_rtk/cli/{templates → commands/db/templates}/fastapi-multidb/script.py.mako +3 -1
- fastapi_rtk/cli/{export.py → commands/export.py} +12 -10
- fastapi_rtk/cli/{security.py → commands/security.py} +73 -7
- fastapi_rtk/cli/commands/translate.py +299 -0
- fastapi_rtk/cli/decorators.py +9 -4
- fastapi_rtk/cli/utils.py +46 -0
- fastapi_rtk/config.py +41 -1
- fastapi_rtk/const.py +29 -1
- fastapi_rtk/db.py +76 -40
- fastapi_rtk/decorators.py +1 -1
- fastapi_rtk/dependencies.py +134 -62
- fastapi_rtk/exceptions.py +51 -1
- fastapi_rtk/fastapi_react_toolkit.py +186 -171
- fastapi_rtk/file_managers/file_manager.py +8 -6
- fastapi_rtk/file_managers/s3_file_manager.py +69 -33
- fastapi_rtk/globals.py +22 -12
- fastapi_rtk/lang/__init__.py +3 -0
- fastapi_rtk/lang/babel/__init__.py +4 -0
- fastapi_rtk/lang/babel/cli.py +40 -0
- fastapi_rtk/lang/babel/config.py +17 -0
- fastapi_rtk/lang/babel.cfg +1 -0
- fastapi_rtk/lang/lazy_text.py +120 -0
- fastapi_rtk/lang/messages.pot +238 -0
- fastapi_rtk/lang/translations/de/LC_MESSAGES/messages.mo +0 -0
- fastapi_rtk/lang/translations/de/LC_MESSAGES/messages.po +248 -0
- fastapi_rtk/lang/translations/en/LC_MESSAGES/messages.mo +0 -0
- fastapi_rtk/lang/translations/en/LC_MESSAGES/messages.po +244 -0
- fastapi_rtk/manager.py +355 -37
- fastapi_rtk/mixins.py +12 -0
- fastapi_rtk/routers.py +208 -72
- fastapi_rtk/schemas.py +142 -39
- fastapi_rtk/security/sqla/apis.py +39 -13
- fastapi_rtk/security/sqla/models.py +8 -23
- fastapi_rtk/security/sqla/security_manager.py +369 -11
- fastapi_rtk/setting.py +446 -88
- fastapi_rtk/types.py +94 -27
- fastapi_rtk/utils/__init__.py +8 -0
- fastapi_rtk/utils/async_task_runner.py +286 -61
- fastapi_rtk/utils/csv_json_converter.py +243 -40
- fastapi_rtk/utils/hooks.py +34 -0
- fastapi_rtk/utils/merge_schema.py +3 -3
- fastapi_rtk/utils/multiple_async_contexts.py +21 -0
- fastapi_rtk/utils/pydantic.py +46 -1
- fastapi_rtk/utils/run_utils.py +31 -1
- fastapi_rtk/utils/self_dependencies.py +1 -1
- fastapi_rtk/utils/use_default_when_none.py +1 -1
- fastapi_rtk/version.py +6 -1
- fastapi_rtk-1.0.13.dist-info/METADATA +28 -0
- fastapi_rtk-1.0.13.dist-info/RECORD +133 -0
- {fastapi_rtk-0.2.27.dist-info → fastapi_rtk-1.0.13.dist-info}/WHEEL +1 -2
- fastapi_rtk/backends/gremlinpython/__init__.py +0 -108
- fastapi_rtk/backends/gremlinpython/column.py +0 -208
- fastapi_rtk/backends/gremlinpython/db.py +0 -228
- fastapi_rtk/backends/gremlinpython/exceptions.py +0 -34
- fastapi_rtk/backends/gremlinpython/filters.py +0 -461
- fastapi_rtk/backends/gremlinpython/interface.py +0 -734
- fastapi_rtk/backends/gremlinpython/model.py +0 -364
- fastapi_rtk/backends/gremlinpython/session.py +0 -23
- fastapi_rtk/cli/commands.py +0 -295
- fastapi_rtk-0.2.27.dist-info/METADATA +0 -23
- fastapi_rtk-0.2.27.dist-info/RECORD +0 -126
- fastapi_rtk-0.2.27.dist-info/top_level.txt +0 -1
- /fastapi_rtk/cli/{templates → commands/db/templates}/fastapi/README +0 -0
- /fastapi_rtk/cli/{templates → commands/db/templates}/fastapi/alembic.ini.mako +0 -0
- /fastapi_rtk/cli/{templates → commands/db/templates}/fastapi/script.py.mako +0 -0
- /fastapi_rtk/cli/{templates → commands/db/templates}/fastapi-multidb/README +0 -0
- /fastapi_rtk/cli/{templates → commands/db/templates}/fastapi-multidb/alembic.ini.mako +0 -0
- {fastapi_rtk-0.2.27.dist-info → fastapi_rtk-1.0.13.dist-info}/entry_points.txt +0 -0
- {fastapi_rtk-0.2.27.dist-info → fastapi_rtk-1.0.13.dist-info}/licenses/LICENSE +0 -0
fastapi_rtk/backends/sqla/db.py
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import collections
|
|
2
2
|
import typing
|
|
3
3
|
|
|
4
|
+
import fastapi
|
|
4
5
|
import sqlalchemy
|
|
5
|
-
from fastapi import HTTPException
|
|
6
6
|
from sqlalchemy import (
|
|
7
7
|
Column,
|
|
8
8
|
Select,
|
|
@@ -18,8 +18,10 @@ from sqlalchemy.orm.strategy_options import _AbstractLoad
|
|
|
18
18
|
|
|
19
19
|
from ...bases.db import AbstractQueryBuilder
|
|
20
20
|
from ...const import logger
|
|
21
|
+
from ...exceptions import HTTPWithValidationException
|
|
22
|
+
from ...lang import translate
|
|
21
23
|
from ...utils import T, deep_merge, prettify_dict, safe_call, smart_run
|
|
22
|
-
from .filters import BaseFilter
|
|
24
|
+
from .filters import BaseFilter, BaseOprFilter
|
|
23
25
|
|
|
24
26
|
__all__ = ["SQLAQueryBuilder"]
|
|
25
27
|
|
|
@@ -32,6 +34,7 @@ LOAD_TYPE_MAPPING = {
|
|
|
32
34
|
|
|
33
35
|
class LoadColumn(typing.TypedDict):
|
|
34
36
|
statement: Select[tuple[T]] | _AbstractLoad
|
|
37
|
+
statement_type: typing.Literal["select", "joinedload", "selectinload"] | None
|
|
35
38
|
type: typing.Literal["defer", "some", "all"]
|
|
36
39
|
columns: list[str]
|
|
37
40
|
related_columns: collections.defaultdict[str, "LoadColumn"]
|
|
@@ -40,6 +43,7 @@ class LoadColumn(typing.TypedDict):
|
|
|
40
43
|
def create_load_column(statement: Select[tuple[T]] | _AbstractLoad | None = None):
|
|
41
44
|
return LoadColumn(
|
|
42
45
|
statement=statement,
|
|
46
|
+
statement_type=None,
|
|
43
47
|
type="defer",
|
|
44
48
|
columns=[],
|
|
45
49
|
related_columns=collections.defaultdict(create_load_column),
|
|
@@ -102,7 +106,7 @@ class SQLAQueryBuilder(AbstractQueryBuilder[Select[tuple[T]]]):
|
|
|
102
106
|
statement = statement.options(cache_option)
|
|
103
107
|
return statement
|
|
104
108
|
|
|
105
|
-
load_column = self._load_columns_recursively(statement, list_columns)
|
|
109
|
+
load_column = self._load_columns_recursively(statement, "select", list_columns)
|
|
106
110
|
logger.debug(f"Load Column:\n{prettify_dict(load_column)}")
|
|
107
111
|
return self._load_columns_from_dictionary(statement, load_column, list_columns)
|
|
108
112
|
|
|
@@ -153,8 +157,13 @@ class SQLAQueryBuilder(AbstractQueryBuilder[Select[tuple[T]]]):
|
|
|
153
157
|
filter_class = f
|
|
154
158
|
break
|
|
155
159
|
if not filter_class:
|
|
156
|
-
raise
|
|
157
|
-
|
|
160
|
+
raise HTTPWithValidationException(
|
|
161
|
+
fastapi.status.HTTP_400_BAD_REQUEST,
|
|
162
|
+
"invalid_key",
|
|
163
|
+
"query",
|
|
164
|
+
"filters",
|
|
165
|
+
translate("Invalid filter operator: {operator}", operator=filter.opr),
|
|
166
|
+
filter.model_dump(mode="json"),
|
|
158
167
|
)
|
|
159
168
|
|
|
160
169
|
if "." in filter.col:
|
|
@@ -171,6 +180,29 @@ class SQLAQueryBuilder(AbstractQueryBuilder[Select[tuple[T]]]):
|
|
|
171
180
|
|
|
172
181
|
return await self._filter(filter_class, statement, col, value)
|
|
173
182
|
|
|
183
|
+
async def apply_opr_filter(self, statement, filter):
|
|
184
|
+
filter_class = None
|
|
185
|
+
for f in self.datamodel.opr_filters:
|
|
186
|
+
if f.arg_name == filter.opr:
|
|
187
|
+
filter_class = f
|
|
188
|
+
break
|
|
189
|
+
if not filter_class:
|
|
190
|
+
raise HTTPWithValidationException(
|
|
191
|
+
fastapi.status.HTTP_400_BAD_REQUEST,
|
|
192
|
+
"invalid_key",
|
|
193
|
+
"query",
|
|
194
|
+
"opr_filters",
|
|
195
|
+
translate(
|
|
196
|
+
"Invalid opr_filter operator: {operator}", operator=filter.opr
|
|
197
|
+
),
|
|
198
|
+
filter.model_dump(mode="json"),
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
return await self._filter(filter_class, statement, None, filter.value)
|
|
202
|
+
|
|
203
|
+
async def apply_opr_filter_class(self, statement, filter_class, value):
|
|
204
|
+
return await self._filter(filter_class, statement, None, value)
|
|
205
|
+
|
|
174
206
|
async def apply_global_filter(self, statement, columns, global_filter):
|
|
175
207
|
return await self.apply_filter_class(
|
|
176
208
|
statement,
|
|
@@ -255,27 +287,32 @@ class SQLAQueryBuilder(AbstractQueryBuilder[Select[tuple[T]]]):
|
|
|
255
287
|
|
|
256
288
|
async def _filter(
|
|
257
289
|
self,
|
|
258
|
-
cls: BaseFilter,
|
|
290
|
+
cls: BaseFilter | BaseOprFilter,
|
|
259
291
|
statement: Select[tuple[T]],
|
|
260
|
-
col: str,
|
|
292
|
+
col: str | None,
|
|
261
293
|
value: typing.Any,
|
|
262
294
|
):
|
|
263
295
|
"""
|
|
264
296
|
Helper method to apply a filter class.
|
|
265
297
|
|
|
266
298
|
Args:
|
|
267
|
-
cls (BaseFilter): The filter class to apply.
|
|
299
|
+
cls (BaseFilter | BaseOprFilter): The filter class to apply.
|
|
268
300
|
statement (Select[tuple[T]]): The SQLAlchemy Select statement to which the filter will be applied.
|
|
269
|
-
col (str): The column name to apply the filter on.
|
|
301
|
+
col (str | None): The column name to apply the filter on. None if the filter is an OprFilter.
|
|
270
302
|
value (typing.Any): The value to filter by.
|
|
271
303
|
|
|
272
304
|
Returns:
|
|
273
305
|
Select[tuple[T]]: The SQLAlchemy Select statement with the filter applied.
|
|
274
306
|
"""
|
|
307
|
+
params = [statement]
|
|
308
|
+
if isinstance(cls, BaseOprFilter):
|
|
309
|
+
params.append(value)
|
|
310
|
+
else:
|
|
311
|
+
params.extend([col, value])
|
|
275
312
|
if cls.is_heavy:
|
|
276
|
-
return await smart_run(cls.apply,
|
|
313
|
+
return await smart_run(cls.apply, *params)
|
|
277
314
|
|
|
278
|
-
return await safe_call(cls.apply(
|
|
315
|
+
return await safe_call(cls.apply(*params))
|
|
279
316
|
|
|
280
317
|
def _load_columns_from_dictionary(
|
|
281
318
|
self,
|
|
@@ -330,27 +367,32 @@ class SQLAQueryBuilder(AbstractQueryBuilder[Select[tuple[T]]]):
|
|
|
330
367
|
return statement
|
|
331
368
|
|
|
332
369
|
def _load_columns_recursively(
|
|
333
|
-
self,
|
|
370
|
+
self,
|
|
371
|
+
statement: Select[tuple[T]] | _AbstractLoad,
|
|
372
|
+
statement_type: typing.Literal["select", "joinedload", "selectinload"],
|
|
373
|
+
columns: list[str],
|
|
334
374
|
):
|
|
335
375
|
"""
|
|
336
376
|
Load specified columns into the given SQLAlchemy statement. This returns a dictionary that can be used with the `load_columns_from_dictionary` method.
|
|
337
377
|
|
|
338
378
|
Args:
|
|
339
379
|
statement (Select[tuple[T]] | _AbstractLoad): The SQLAlchemy statement to which the columns will be loaded.
|
|
380
|
+
statement_type (typing.Literal["select", "joinedload", "selectinload"]): The type of statement to use for loading.
|
|
340
381
|
columns (list[str]): A list of column names to be loaded.
|
|
341
382
|
|
|
342
383
|
Returns:
|
|
343
384
|
dict: A dictionary that can be used with the `load_columns_from_dictionary` method.
|
|
344
385
|
"""
|
|
345
386
|
load_column = create_load_column(statement)
|
|
387
|
+
load_column["statement_type"] = statement_type
|
|
346
388
|
for col in columns:
|
|
347
389
|
sub_col = ""
|
|
348
390
|
if "." in col:
|
|
349
391
|
col, sub_col = col.split(".", 1)
|
|
350
392
|
|
|
351
|
-
# If it is not a relation, load only the column if it is in the
|
|
393
|
+
# If it is not a relation, load only the column if it is in the column list, else skip
|
|
352
394
|
if not self.datamodel.is_relation(col):
|
|
353
|
-
if col in self.datamodel.
|
|
395
|
+
if col in self.datamodel.get_column_list():
|
|
354
396
|
load_column["columns"].append(col)
|
|
355
397
|
load_column["type"] = "some"
|
|
356
398
|
continue
|
|
@@ -358,10 +400,18 @@ class SQLAQueryBuilder(AbstractQueryBuilder[Select[tuple[T]]]):
|
|
|
358
400
|
if self.datamodel.is_relation_one_to_one(
|
|
359
401
|
col
|
|
360
402
|
) or self.datamodel.is_relation_many_to_one(col):
|
|
403
|
+
load_column["related_columns"][col]["statement_type"] = (
|
|
404
|
+
load_column["related_columns"][col]["statement_type"]
|
|
405
|
+
or "joinedload"
|
|
406
|
+
)
|
|
361
407
|
load_column["related_columns"][col]["statement"] = load_column[
|
|
362
408
|
"related_columns"
|
|
363
409
|
][col]["statement"] or joinedload(self.datamodel.obj.load_options(col))
|
|
364
410
|
else:
|
|
411
|
+
load_column["related_columns"][col]["statement_type"] = (
|
|
412
|
+
load_column["related_columns"][col]["statement_type"]
|
|
413
|
+
or "selectinload"
|
|
414
|
+
)
|
|
365
415
|
load_column["related_columns"][col]["statement"] = load_column[
|
|
366
416
|
"related_columns"
|
|
367
417
|
][col]["statement"] or selectinload(
|
|
@@ -375,7 +425,9 @@ class SQLAQueryBuilder(AbstractQueryBuilder[Select[tuple[T]]]):
|
|
|
375
425
|
load_column["related_columns"][col] = deep_merge(
|
|
376
426
|
load_column["related_columns"][col],
|
|
377
427
|
interface.query._load_columns_recursively(
|
|
378
|
-
load_column["related_columns"][col]["statement"],
|
|
428
|
+
load_column["related_columns"][col]["statement"],
|
|
429
|
+
load_column["related_columns"][col]["statement_type"],
|
|
430
|
+
[sub_col],
|
|
379
431
|
),
|
|
380
432
|
rules={
|
|
381
433
|
"type": lambda x1, x2: LOAD_TYPE_MAPPING[x2]
|
|
@@ -392,8 +444,8 @@ class SQLAQueryBuilder(AbstractQueryBuilder[Select[tuple[T]]]):
|
|
|
392
444
|
load_column["related_columns"][col]["type"] = "all"
|
|
393
445
|
continue
|
|
394
446
|
|
|
395
|
-
# Skip if the sub column is not in the
|
|
396
|
-
if sub_col not in interface.
|
|
447
|
+
# Skip if the sub column is not in the column list or if it is a relation
|
|
448
|
+
if sub_col not in interface.get_column_list() or interface.is_relation(
|
|
397
449
|
sub_col
|
|
398
450
|
):
|
|
399
451
|
continue
|
|
@@ -402,3 +454,11 @@ class SQLAQueryBuilder(AbstractQueryBuilder[Select[tuple[T]]]):
|
|
|
402
454
|
load_column["related_columns"][col]["type"] = "some"
|
|
403
455
|
|
|
404
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
|