fastapi-rtk 1.0.6__py3-none-any.whl → 1.0.7__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/_version.py CHANGED
@@ -1 +1 @@
1
- __version__ = "1.0.6"
1
+ __version__ = "1.0.7"
@@ -1,5 +1,4 @@
1
1
  import asyncio
2
- import copy
3
2
  import csv
4
3
  import enum
5
4
  import re
@@ -256,9 +255,7 @@ class ModelRestApi(BaseApi):
256
255
 
257
256
  Example:
258
257
  ```python
259
- opr_filters = [
260
- [FilterEqualOnNameAndAge],
261
- ]
258
+ opr_filters = [FilterEqualOnNameAndAge]
262
259
  ```
263
260
  """
264
261
  label_columns = lazy(lambda: dict[str, str]())
@@ -2591,12 +2588,18 @@ class ModelRestApi(BaseApi):
2591
2588
  Returns:
2592
2589
  dict: The generated JSONForms schema.
2593
2590
  """
2594
- jsonforms_schema = schema.model_json_schema()
2591
+ cache_key = f"jsonforms_schema_{schema.__name__}"
2592
+ jsonforms_schema = self.cache.get(cache_key)
2593
+ if not jsonforms_schema:
2594
+ self.cache[cache_key] = jsonforms_schema = schema.model_json_schema()
2595
2595
 
2596
2596
  # Remove unused vars
2597
2597
  jsonforms_schema.pop("$defs", None)
2598
2598
 
2599
- for key, value in jsonforms_schema["properties"].items():
2599
+ result = jsonforms_schema.copy()
2600
+ result["properties"] = jsonforms_schema["properties"].copy()
2601
+ for key, value in result["properties"].items():
2602
+ value = value.copy()
2600
2603
  label = self.label_columns.get(key)
2601
2604
  if label:
2602
2605
  value["title"] = label
@@ -2621,7 +2624,7 @@ class ModelRestApi(BaseApi):
2621
2624
  ]
2622
2625
  value["contentMediaType"] = ", ".join(allowed_extensions)
2623
2626
  if self.datamodel.is_files(key) or self.datamodel.is_images(key):
2624
- current_value = copy.deepcopy(value)
2627
+ current_value = value.copy()
2625
2628
  value["type"] = "array"
2626
2629
  value["items"] = current_value
2627
2630
  elif self.datamodel.is_boolean(key):
@@ -2688,9 +2691,9 @@ class ModelRestApi(BaseApi):
2688
2691
  if key in g.sensitive_data.get(self.datamodel.obj.__name__, []):
2689
2692
  value["format"] = "password"
2690
2693
 
2691
- jsonforms_schema["properties"][key] = value
2694
+ result["properties"][key] = value
2692
2695
 
2693
- return jsonforms_schema
2696
+ return result
2694
2697
 
2695
2698
  async def _export_data(
2696
2699
  self,
@@ -34,6 +34,7 @@ LOAD_TYPE_MAPPING = {
34
34
 
35
35
  class LoadColumn(typing.TypedDict):
36
36
  statement: Select[tuple[T]] | _AbstractLoad
37
+ statement_type: typing.Literal["select", "joinedload", "selectinload"] | None
37
38
  type: typing.Literal["defer", "some", "all"]
38
39
  columns: list[str]
39
40
  related_columns: collections.defaultdict[str, "LoadColumn"]
@@ -42,6 +43,7 @@ class LoadColumn(typing.TypedDict):
42
43
  def create_load_column(statement: Select[tuple[T]] | _AbstractLoad | None = None):
43
44
  return LoadColumn(
44
45
  statement=statement,
46
+ statement_type=None,
45
47
  type="defer",
46
48
  columns=[],
47
49
  related_columns=collections.defaultdict(create_load_column),
@@ -104,7 +106,7 @@ class SQLAQueryBuilder(AbstractQueryBuilder[Select[tuple[T]]]):
104
106
  statement = statement.options(cache_option)
105
107
  return statement
106
108
 
107
- load_column = self._load_columns_recursively(statement, list_columns)
109
+ load_column = self._load_columns_recursively(statement, "select", list_columns)
108
110
  logger.debug(f"Load Column:\n{prettify_dict(load_column)}")
109
111
  return self._load_columns_from_dictionary(statement, load_column, list_columns)
110
112
 
@@ -365,27 +367,32 @@ class SQLAQueryBuilder(AbstractQueryBuilder[Select[tuple[T]]]):
365
367
  return statement
366
368
 
367
369
  def _load_columns_recursively(
368
- 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],
369
374
  ):
370
375
  """
371
376
  Load specified columns into the given SQLAlchemy statement. This returns a dictionary that can be used with the `load_columns_from_dictionary` method.
372
377
 
373
378
  Args:
374
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.
375
381
  columns (list[str]): A list of column names to be loaded.
376
382
 
377
383
  Returns:
378
384
  dict: A dictionary that can be used with the `load_columns_from_dictionary` method.
379
385
  """
380
386
  load_column = create_load_column(statement)
387
+ load_column["statement_type"] = statement_type
381
388
  for col in columns:
382
389
  sub_col = ""
383
390
  if "." in col:
384
391
  col, sub_col = col.split(".", 1)
385
392
 
386
- # 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
387
394
  if not self.datamodel.is_relation(col):
388
- if col in self.datamodel.get_user_column_list():
395
+ if col in self.datamodel.get_column_list():
389
396
  load_column["columns"].append(col)
390
397
  load_column["type"] = "some"
391
398
  continue
@@ -393,10 +400,18 @@ class SQLAQueryBuilder(AbstractQueryBuilder[Select[tuple[T]]]):
393
400
  if self.datamodel.is_relation_one_to_one(
394
401
  col
395
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
+ )
396
407
  load_column["related_columns"][col]["statement"] = load_column[
397
408
  "related_columns"
398
409
  ][col]["statement"] or joinedload(self.datamodel.obj.load_options(col))
399
410
  else:
411
+ load_column["related_columns"][col]["statement_type"] = (
412
+ load_column["related_columns"][col]["statement_type"]
413
+ or "selectinload"
414
+ )
400
415
  load_column["related_columns"][col]["statement"] = load_column[
401
416
  "related_columns"
402
417
  ][col]["statement"] or selectinload(
@@ -410,7 +425,9 @@ class SQLAQueryBuilder(AbstractQueryBuilder[Select[tuple[T]]]):
410
425
  load_column["related_columns"][col] = deep_merge(
411
426
  load_column["related_columns"][col],
412
427
  interface.query._load_columns_recursively(
413
- 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],
414
431
  ),
415
432
  rules={
416
433
  "type": lambda x1, x2: LOAD_TYPE_MAPPING[x2]
@@ -427,8 +444,8 @@ class SQLAQueryBuilder(AbstractQueryBuilder[Select[tuple[T]]]):
427
444
  load_column["related_columns"][col]["type"] = "all"
428
445
  continue
429
446
 
430
- # Skip if the sub column is not in the user column list or if it is a relation
431
- 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(
432
449
  sub_col
433
450
  ):
434
451
  continue
@@ -125,16 +125,18 @@ class Model(sqlalchemy.ext.asyncio.AsyncAttrs, BasicModel, DeclarativeBase):
125
125
  def get_pk_attrs(cls):
126
126
  return [col.name for col in cls.__mapper__.primary_key]
127
127
 
128
- async def load(self, cols: list[str] | str):
128
+ async def load(self, *cols: list[str] | str):
129
129
  """
130
- Asynchronously load the specified columns for the current instance.
130
+ Asynchronously loads the specified columns of the model instance.
131
131
 
132
132
  Args:
133
- cols (list[str] | str): A list of column names to load or a single column name.
133
+ *cols (list[str] | str): The columns to load. Can be a list of strings or individual string arguments.
134
134
  """
135
+ cols = [
136
+ item for col in cols for item in (col if isinstance(col, list) else [col])
137
+ ]
138
+
135
139
  async with AsyncTaskRunner() as runner:
136
- if isinstance(cols, str):
137
- cols = [cols]
138
140
  for col in cols:
139
141
  runner.add_task(getattr(self.awaitable_attrs, col))
140
142
 
@@ -51,7 +51,7 @@ class AbstractBaseOprFilter(AbstractBaseFilter[T]):
51
51
  """
52
52
 
53
53
  @abc.abstractmethod
54
- def apply(self, statement, value) -> T:
54
+ def apply(self, statement: T, value) -> T:
55
55
  """
56
56
  Apply the filter to the given SQLAlchemy Select statement.
57
57
 
fastapi_rtk/const.py CHANGED
@@ -76,7 +76,7 @@ DEFAULT_COOKIE_NAME = "dataTactics"
76
76
  DEFAULT_BASEDIR = "app"
77
77
  DEFAULT_STATIC_FOLDER = DEFAULT_BASEDIR + "/static"
78
78
  DEFAULT_TEMPLATE_FOLDER = DEFAULT_BASEDIR + "/templates"
79
- DEFAULT_PROFILER_FOLDER = DEFAULT_STATIC_FOLDER + "/profiles"
79
+ DEFAULT_PROFILER_FOLDER = DEFAULT_STATIC_FOLDER + "/profiler"
80
80
  DEFAULT_LANG_FOLDER = DEFAULT_BASEDIR + "/lang"
81
81
  DEFAULT_LANGUAGES = "en,de"
82
82
  DEFAULT_TRANSLATIONS_KEY = "translations"
@@ -1,6 +1,6 @@
1
1
  import fastapi
2
2
 
3
- __al__ = ["SelfDepends", "SelfType"]
3
+ __all__ = ["SelfDepends", "SelfType"]
4
4
 
5
5
 
6
6
  class BaseSelf:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastapi-rtk
3
- Version: 1.0.6
3
+ Version: 1.0.7
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
@@ -1,8 +1,8 @@
1
1
  fastapi_rtk/__init__.py,sha256=acLIihNMCZI3prFTq0cru1-k3kPjSZb2hhcqNrW7xJo,6203
2
- fastapi_rtk/_version.py,sha256=mqMuQB3aqJVPrHHqJMLjqiMKUiJjozc7EPLcX5DpKHg,22
2
+ fastapi_rtk/_version.py,sha256=BW7SWRpHoxuOQZ67pS20yog2LWYl-nK7-BEFBNrHGgA,22
3
3
  fastapi_rtk/apis.py,sha256=6X_Lhl98m7lKrDRybg2Oe24pLFLJ29eCOQSwCAvpKhY,172
4
4
  fastapi_rtk/config.py,sha256=9PZF9E5i1gxmnsZEprZZKxVHSk0dFEklJSplX9NEqdo,14036
5
- fastapi_rtk/const.py,sha256=huvh4Zor77fgUkhU4-x6LgkOglSxeKXOlXdhnai_5CQ,4905
5
+ fastapi_rtk/const.py,sha256=sEj_cYeerj9pVwbCu0k5Sy1EYpdr1EHzUjqqbnporgc,4905
6
6
  fastapi_rtk/db.py,sha256=nAPIibCCyaGm7Kw9updiVx5LWASkdfX01GxG5Gp12hM,24505
7
7
  fastapi_rtk/decorators.py,sha256=HqAFSiO0l5_M0idWs0IcY24FdzbAcDQDQoifM_WgZAQ,14515
8
8
  fastapi_rtk/dependencies.py,sha256=H31uMZRA0FdZZXmzIVS2j9gBAovJDYdb_5ZgmrKd9qc,6070
@@ -21,7 +21,7 @@ fastapi_rtk/types.py,sha256=-LPnTIbHvqJW81__gab3EWrhjNmznHhptz0BtXkEAHQ,3612
21
21
  fastapi_rtk/version.py,sha256=D2cmQf2LNeHOiEfcNzVOOfcAmuLvPEmGEtZv5G54D0c,195
22
22
  fastapi_rtk/api/__init__.py,sha256=MwFR7HHppnhbjZGg3sOdQ6nqy9uxnHHXvicpswNFMNA,245
23
23
  fastapi_rtk/api/base_api.py,sha256=42I9v3b25lqxNAMDGEtajA5-btIDSyUWF0xMDgGkA8c,8078
24
- fastapi_rtk/api/model_rest_api.py,sha256=PQwR_ya4rrzlZlHDl8lr3z7y1SH8mRbgiVRX2TrjsGk,104924
24
+ fastapi_rtk/api/model_rest_api.py,sha256=8ztCFUSYMAn74QcVKN9lNYDOSWixsXHCpLHtc9RAShA,105175
25
25
  fastapi_rtk/auth/__init__.py,sha256=iX7O41NivBYDfdomEaqm4lUx9KD17wI4g3EFLF6kUTw,336
26
26
  fastapi_rtk/auth/auth.py,sha256=MZmuueioiMbSHjd_F3frKEqCA3yjtanRWyKOy6CnOd0,20994
27
27
  fastapi_rtk/auth/hashers/__init__.py,sha256=uBThFj2VPPSMSioxYTktNiM4-mVgtDAjTpKA3ZzWxxs,110
@@ -45,11 +45,11 @@ fastapi_rtk/backends/generic/model.py,sha256=olRvHD57CCKnZoyia7EcvecZfXjN-ku17gx
45
45
  fastapi_rtk/backends/generic/session.py,sha256=J8OJSuGcFkZiOg7SJIbkKnMUlo_njQjRJ1vNl4k8Ve4,19053
46
46
  fastapi_rtk/backends/sqla/__init__.py,sha256=p4TRI65-R6TM6G_-OAnwYV7Hk9WVkKsnhN2sFta1-cU,1795
47
47
  fastapi_rtk/backends/sqla/column.py,sha256=KutGcyFr3ZeHFTL8c313I4CNxosGq54neLLB7N53GZs,240
48
- fastapi_rtk/backends/sqla/db.py,sha256=hnE1jQFZai96IaQ7jWqAsiEK0PXnGpMgvgZIGSlPBsM,17245
48
+ fastapi_rtk/backends/sqla/db.py,sha256=8VUU-S4W2WWpkROR178dj0ahCirP5CKP7fPyoz25Ui0,18135
49
49
  fastapi_rtk/backends/sqla/exceptions.py,sha256=rRtvgXyDwGSbjvJIPJIOjOBYytvNv9VXkZOBy6p2J80,61
50
50
  fastapi_rtk/backends/sqla/filters.py,sha256=H2eholy7VTfVwcBTAV0BWO6NXJYA-eDp_YvptLFKO3E,20927
51
51
  fastapi_rtk/backends/sqla/interface.py,sha256=9I2JiShqmyakfgdXE-DA5TW35xdvMs_-pDDccKnX5cA,27678
52
- fastapi_rtk/backends/sqla/model.py,sha256=GqWJyb4uq7AK58_zZw5jx4BQ6xWi5thwNqr8WGfnJ6o,7099
52
+ fastapi_rtk/backends/sqla/model.py,sha256=N0fxuAHbw_RCfFPRw-cjWQdBEanfogLRXOUEPqZ1Qac,7168
53
53
  fastapi_rtk/backends/sqla/session.py,sha256=di52RhRWzUchcquWMU9KKd0F1N5te1GfAtcfCnekiwI,412
54
54
  fastapi_rtk/backends/sqla/extensions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
55
55
  fastapi_rtk/backends/sqla/extensions/audit/__init__.py,sha256=CaVc9fV584PVazvGGMOKwX4xC3OvIae9kXBx6xQAj5g,131
@@ -61,7 +61,7 @@ fastapi_rtk/backends/sqla/extensions/geoalchemy2/geometry_converter.py,sha256=sc
61
61
  fastapi_rtk/bases/__init__.py,sha256=v3tMVuX20UbvjI_mTWpDAePWAA38e3pjlYEiICgY4j8,440
62
62
  fastapi_rtk/bases/db.py,sha256=D27BhF89J0OaLHjALDCa85eNf35lBaTz6VV7EDa4wuM,18711
63
63
  fastapi_rtk/bases/file_manager.py,sha256=d1ZZaY-OgDxjSWQ49DNQNSDHKznzr-03wki_eR2iU2A,8230
64
- fastapi_rtk/bases/filter.py,sha256=b9DNgW9M6pLv0eMTnaGUaYpPX1ZSloo6OCvdxGpCNNE,1837
64
+ fastapi_rtk/bases/filter.py,sha256=XmWTcLaIcBj9pKF1PMAKdwSnZNpdT8Df3uLeUIOGUDE,1840
65
65
  fastapi_rtk/bases/interface.py,sha256=Cq9Duxa3w-tw342P424h88fc0_X1DoxCdTa3rAN-6jM,45380
66
66
  fastapi_rtk/bases/model.py,sha256=nUZf0AVs0Mzqh2u_ALiRNYN1bfOU9PzYLvEFHDQ57Y0,1692
67
67
  fastapi_rtk/bases/session.py,sha256=Q92cRW0nI5IYSsBXLkTJjK5ztMSBNRt-olMwxTIeel0,173
@@ -119,15 +119,15 @@ fastapi_rtk/utils/multiple_async_contexts.py,sha256=-juAliUeG4XI1J-p31KknsJvzvM5
119
119
  fastapi_rtk/utils/prettify_dict.py,sha256=VKve-52fE9usIzflD3mNtvql_p5F2jq9HYBTJF_1MX0,662
120
120
  fastapi_rtk/utils/pydantic.py,sha256=c4TGu6fP5qmsMJcEerhgq3R1iTpJn_1oA91d43GaVlo,2408
121
121
  fastapi_rtk/utils/run_utils.py,sha256=2aieVAGp4e7vByzkwnmXk5c-crLl-Ia1rwdHl6LRQ34,6237
122
- fastapi_rtk/utils/self_dependencies.py,sha256=ZTpJZFFnh3MhhS5PwsgJ9TeVgcV6gwSS962c7udLzds,4101
122
+ fastapi_rtk/utils/self_dependencies.py,sha256=SjX8BOC3UvuLxfElo1MMgugCX8ZEgsCgLy0g011a4UU,4102
123
123
  fastapi_rtk/utils/smartdefaultdict.py,sha256=UdA_N1eQ3TooE9_ci0O_5QKUjGkQfo-c2BwEX5OkXfY,645
124
124
  fastapi_rtk/utils/sqla.py,sha256=To4PhsO5orPJVqjdLh5C9y_xPgiy8-zhrJdSqhR_tsc,690
125
125
  fastapi_rtk/utils/timezone.py,sha256=62S0pPWuDFFXxV1YTFCsc4uKiSP_Ba36Fv7S3gYjfhs,570
126
126
  fastapi_rtk/utils/update_signature.py,sha256=PIzZgNpGEwvDNgQ3G51Zi-QhVV3mbxvUvSwDwf_-yYs,2209
127
127
  fastapi_rtk/utils/use_default_when_none.py,sha256=H2HqhKy_8eYk3i1xijEjuaKak0oWkMIkrdz6T7DK9QU,469
128
128
  fastapi_rtk/utils/werkzeug.py,sha256=1Gv-oyqSmhVGrmNbB9fDqpqJzPpANOzWf4zMMrhW9UA,3245
129
- fastapi_rtk-1.0.6.dist-info/METADATA,sha256=M9gJt_FXPypoq_6Q-U2L6mE9m-9Y_MZpqcorsYAy9iI,1270
130
- fastapi_rtk-1.0.6.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
131
- fastapi_rtk-1.0.6.dist-info/entry_points.txt,sha256=UuTkxSVIokSlVN28TMhoxzRRUaPxlVRSH3Gsx6yip60,53
132
- fastapi_rtk-1.0.6.dist-info/licenses/LICENSE,sha256=NDrWi4Qwcxal3u1r2lBWGA6TVh3OeW7yMan098mQz98,1073
133
- fastapi_rtk-1.0.6.dist-info/RECORD,,
129
+ fastapi_rtk-1.0.7.dist-info/METADATA,sha256=yuKVrabzSpcffX7GiyKHtzOgZQLY4HHwFnaVp8hP-UQ,1270
130
+ fastapi_rtk-1.0.7.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
131
+ fastapi_rtk-1.0.7.dist-info/entry_points.txt,sha256=UuTkxSVIokSlVN28TMhoxzRRUaPxlVRSH3Gsx6yip60,53
132
+ fastapi_rtk-1.0.7.dist-info/licenses/LICENSE,sha256=NDrWi4Qwcxal3u1r2lBWGA6TVh3OeW7yMan098mQz98,1073
133
+ fastapi_rtk-1.0.7.dist-info/RECORD,,