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.
Files changed (98) hide show
  1. fastapi_rtk/__init__.py +39 -35
  2. fastapi_rtk/_version.py +1 -0
  3. fastapi_rtk/api/model_rest_api.py +476 -221
  4. fastapi_rtk/auth/auth.py +0 -9
  5. fastapi_rtk/backends/generic/__init__.py +6 -0
  6. fastapi_rtk/backends/generic/column.py +21 -12
  7. fastapi_rtk/backends/generic/db.py +42 -7
  8. fastapi_rtk/backends/generic/filters.py +21 -16
  9. fastapi_rtk/backends/generic/interface.py +14 -8
  10. fastapi_rtk/backends/generic/model.py +19 -11
  11. fastapi_rtk/backends/sqla/__init__.py +1 -0
  12. fastapi_rtk/backends/sqla/db.py +77 -17
  13. fastapi_rtk/backends/sqla/extensions/audit/audit.py +401 -189
  14. fastapi_rtk/backends/sqla/extensions/geoalchemy2/filters.py +15 -12
  15. fastapi_rtk/backends/sqla/filters.py +50 -21
  16. fastapi_rtk/backends/sqla/interface.py +96 -34
  17. fastapi_rtk/backends/sqla/model.py +56 -39
  18. fastapi_rtk/bases/__init__.py +20 -0
  19. fastapi_rtk/bases/db.py +94 -7
  20. fastapi_rtk/bases/file_manager.py +47 -3
  21. fastapi_rtk/bases/filter.py +22 -0
  22. fastapi_rtk/bases/interface.py +49 -5
  23. fastapi_rtk/bases/model.py +3 -0
  24. fastapi_rtk/bases/session.py +2 -0
  25. fastapi_rtk/cli/cli.py +62 -9
  26. fastapi_rtk/cli/commands/__init__.py +23 -0
  27. fastapi_rtk/cli/{db.py → commands/db/__init__.py} +107 -50
  28. fastapi_rtk/cli/{templates → commands/db/templates}/fastapi/env.py +2 -3
  29. fastapi_rtk/cli/{templates → commands/db/templates}/fastapi-multidb/env.py +10 -9
  30. fastapi_rtk/cli/{templates → commands/db/templates}/fastapi-multidb/script.py.mako +3 -1
  31. fastapi_rtk/cli/{export.py → commands/export.py} +12 -10
  32. fastapi_rtk/cli/{security.py → commands/security.py} +73 -7
  33. fastapi_rtk/cli/commands/translate.py +299 -0
  34. fastapi_rtk/cli/decorators.py +9 -4
  35. fastapi_rtk/cli/utils.py +46 -0
  36. fastapi_rtk/config.py +41 -1
  37. fastapi_rtk/const.py +29 -1
  38. fastapi_rtk/db.py +76 -40
  39. fastapi_rtk/decorators.py +1 -1
  40. fastapi_rtk/dependencies.py +134 -62
  41. fastapi_rtk/exceptions.py +51 -1
  42. fastapi_rtk/fastapi_react_toolkit.py +186 -171
  43. fastapi_rtk/file_managers/file_manager.py +8 -6
  44. fastapi_rtk/file_managers/s3_file_manager.py +69 -33
  45. fastapi_rtk/globals.py +22 -12
  46. fastapi_rtk/lang/__init__.py +3 -0
  47. fastapi_rtk/lang/babel/__init__.py +4 -0
  48. fastapi_rtk/lang/babel/cli.py +40 -0
  49. fastapi_rtk/lang/babel/config.py +17 -0
  50. fastapi_rtk/lang/babel.cfg +1 -0
  51. fastapi_rtk/lang/lazy_text.py +120 -0
  52. fastapi_rtk/lang/messages.pot +238 -0
  53. fastapi_rtk/lang/translations/de/LC_MESSAGES/messages.mo +0 -0
  54. fastapi_rtk/lang/translations/de/LC_MESSAGES/messages.po +248 -0
  55. fastapi_rtk/lang/translations/en/LC_MESSAGES/messages.mo +0 -0
  56. fastapi_rtk/lang/translations/en/LC_MESSAGES/messages.po +244 -0
  57. fastapi_rtk/manager.py +355 -37
  58. fastapi_rtk/mixins.py +12 -0
  59. fastapi_rtk/routers.py +208 -72
  60. fastapi_rtk/schemas.py +142 -39
  61. fastapi_rtk/security/sqla/apis.py +39 -13
  62. fastapi_rtk/security/sqla/models.py +8 -23
  63. fastapi_rtk/security/sqla/security_manager.py +369 -11
  64. fastapi_rtk/setting.py +446 -88
  65. fastapi_rtk/types.py +94 -27
  66. fastapi_rtk/utils/__init__.py +8 -0
  67. fastapi_rtk/utils/async_task_runner.py +286 -61
  68. fastapi_rtk/utils/csv_json_converter.py +243 -40
  69. fastapi_rtk/utils/hooks.py +34 -0
  70. fastapi_rtk/utils/merge_schema.py +3 -3
  71. fastapi_rtk/utils/multiple_async_contexts.py +21 -0
  72. fastapi_rtk/utils/pydantic.py +46 -1
  73. fastapi_rtk/utils/run_utils.py +31 -1
  74. fastapi_rtk/utils/self_dependencies.py +1 -1
  75. fastapi_rtk/utils/use_default_when_none.py +1 -1
  76. fastapi_rtk/version.py +6 -1
  77. fastapi_rtk-1.0.13.dist-info/METADATA +28 -0
  78. fastapi_rtk-1.0.13.dist-info/RECORD +133 -0
  79. {fastapi_rtk-0.2.27.dist-info → fastapi_rtk-1.0.13.dist-info}/WHEEL +1 -2
  80. fastapi_rtk/backends/gremlinpython/__init__.py +0 -108
  81. fastapi_rtk/backends/gremlinpython/column.py +0 -208
  82. fastapi_rtk/backends/gremlinpython/db.py +0 -228
  83. fastapi_rtk/backends/gremlinpython/exceptions.py +0 -34
  84. fastapi_rtk/backends/gremlinpython/filters.py +0 -461
  85. fastapi_rtk/backends/gremlinpython/interface.py +0 -734
  86. fastapi_rtk/backends/gremlinpython/model.py +0 -364
  87. fastapi_rtk/backends/gremlinpython/session.py +0 -23
  88. fastapi_rtk/cli/commands.py +0 -295
  89. fastapi_rtk-0.2.27.dist-info/METADATA +0 -23
  90. fastapi_rtk-0.2.27.dist-info/RECORD +0 -126
  91. fastapi_rtk-0.2.27.dist-info/top_level.txt +0 -1
  92. /fastapi_rtk/cli/{templates → commands/db/templates}/fastapi/README +0 -0
  93. /fastapi_rtk/cli/{templates → commands/db/templates}/fastapi/alembic.ini.mako +0 -0
  94. /fastapi_rtk/cli/{templates → commands/db/templates}/fastapi/script.py.mako +0 -0
  95. /fastapi_rtk/cli/{templates → commands/db/templates}/fastapi-multidb/README +0 -0
  96. /fastapi_rtk/cli/{templates → commands/db/templates}/fastapi-multidb/alembic.ini.mako +0 -0
  97. {fastapi_rtk-0.2.27.dist-info → fastapi_rtk-1.0.13.dist-info}/entry_points.txt +0 -0
  98. {fastapi_rtk-0.2.27.dist-info → fastapi_rtk-1.0.13.dist-info}/licenses/LICENSE +0 -0
@@ -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 HTTPException(
157
- status_code=400, detail=f"Invalid filter opr: {filter.opr}"
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, statement, col, value)
313
+ return await smart_run(cls.apply, *params)
277
314
 
278
- return await safe_call(cls.apply(statement, col, value))
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, statement: Select[tuple[T]] | _AbstractLoad, columns: list[str]
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 user column list, else skip
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.get_user_column_list():
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"], [sub_col]
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 user column list or if it is a relation
396
- if sub_col not in interface.get_user_column_list() or interface.is_relation(
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