fastapi-rtk 1.0.5__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.5"
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
@@ -7,9 +7,7 @@ from typing import Annotated, Literal, Type
7
7
 
8
8
  import fastapi
9
9
  import marshmallow_sqlalchemy
10
- import pydantic
11
10
  import sqlalchemy
12
- import sqlalchemy.exc
13
11
  from pydantic import (
14
12
  AfterValidator,
15
13
  BeforeValidator,
@@ -45,7 +43,6 @@ from ...utils import (
45
43
  lazy_import,
46
44
  lazy_self,
47
45
  safe_call,
48
- safe_call_sync,
49
46
  smart_run,
50
47
  )
51
48
  from .db import SQLAQueryBuilder
@@ -682,57 +679,6 @@ class SQLAInterface(AbstractInterface[ModelType, Session | AsyncSession, Column]
682
679
 
683
680
  return result
684
681
 
685
- def _generate_schema_from_dict(self, schema_dict):
686
- # Allow await for deferrable columns
687
- deferrable_columns = [
688
- key for key in schema_dict.keys() if key in self.list_properties
689
- ]
690
- model_name = schema_dict.get("__name__", "UnknownModel")
691
- if deferrable_columns:
692
-
693
- async def fill_deferrable(model: Model, col: str):
694
- try:
695
- return getattr(model, col)
696
- except sqlalchemy.exc.MissingGreenlet:
697
- logger.warning(
698
- f"'MissingGreenlet' error when accessing {model.__class__.__name__}.{col} to create pydantic model {model_name}, trying to await it instead."
699
- )
700
- try:
701
- await getattr(model.awaitable_attrs, col)
702
- except Exception as e:
703
- return_value = (
704
- []
705
- if self.is_relation_one_to_many(col)
706
- or self.is_relation_many_to_many(col)
707
- else None
708
- )
709
- logger.error(
710
- f"Error when awaiting {model.__class__.__name__}.{col} to create pydantic model {model_name}, returning {return_value} instead: {e}"
711
- )
712
- return return_value
713
- return getattr(model, col)
714
-
715
- async def fill_deferables(data: Model):
716
- async with AsyncTaskRunner():
717
- for col in deferrable_columns:
718
- AsyncTaskRunner.add_task(
719
- lambda col=col: fill_deferrable(data, col)
720
- )
721
-
722
- def fill_deferrables_validator(data):
723
- if isinstance(data, Model):
724
- safe_call_sync(fill_deferables(data))
725
- return data
726
-
727
- decorated_func = pydantic.model_validator(mode="before")(
728
- fill_deferrables_validator
729
- )
730
- schema_dict["__validators__"] = schema_dict.get("__validators__", {})
731
- schema_dict["__validators__"][fill_deferrables_validator.__name__] = (
732
- decorated_func
733
- )
734
- return super()._generate_schema_from_dict(schema_dict)
735
-
736
682
  """
737
683
  --------------------------------------------------------------------------------------------------------
738
684
  SQLA RELATED METHODS - ONLY IN SQLAInterface
@@ -13,7 +13,7 @@ from sqlalchemy.util.typing import Literal
13
13
  from ...bases.model import BasicModel
14
14
  from ...const import DEFAULT_METADATA_KEY
15
15
  from ...exceptions import FastAPIReactToolkitException
16
- from ...utils import smart_run_sync
16
+ from ...utils import AsyncTaskRunner, smart_run_sync
17
17
 
18
18
  __all__ = ["Model", "metadata", "metadatas", "Base"]
19
19
 
@@ -125,6 +125,21 @@ 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):
129
+ """
130
+ Asynchronously loads the specified columns of the model instance.
131
+
132
+ Args:
133
+ *cols (list[str] | str): The columns to load. Can be a list of strings or individual string arguments.
134
+ """
135
+ cols = [
136
+ item for col in cols for item in (col if isinstance(col, list) else [col])
137
+ ]
138
+
139
+ async with AsyncTaskRunner() as runner:
140
+ for col in cols:
141
+ runner.add_task(getattr(self.awaitable_attrs, col))
142
+
128
143
 
129
144
  class Table(SA_Table):
130
145
  """
@@ -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"
@@ -9,6 +9,8 @@ from .prettify_dict import prettify_dict
9
9
 
10
10
  __all__ = ["AsyncTaskRunner"]
11
11
 
12
+ T = typing.TypeVar("T")
13
+
12
14
 
13
15
  class CallerInfo(typing.TypedDict):
14
16
  """
@@ -36,26 +38,37 @@ class AsyncTaskException(AsyncTaskRunnerException):
36
38
  self.caller = caller
37
39
 
38
40
 
39
- def wrap_in_async_task_exception(
40
- task: typing.Callable[[], typing.Coroutine | None] | typing.Coroutine,
41
- /,
42
- caller: CallerInfo | None = None,
43
- ):
44
- async def wrapper():
45
- try:
46
- tsk = task
47
- if callable(tsk):
48
- tsk = tsk()
49
- if tsk is None:
50
- return None
51
- return await tsk
52
- except Exception as e:
53
- raise AsyncTaskException(str(e), original_exception=e, caller=caller) from e
41
+ class ResultAccessor(typing.Generic[T]):
42
+ """Helper class that can be awaited OR accessed directly."""
43
+
44
+ def __init__(self, task: "AsyncTask[T]"):
45
+ self._task = task
46
+
47
+ def __await__(self):
48
+ """Allow awaiting: await task.result"""
49
+ return self._task.__await__()
50
+
51
+ def __repr__(self) -> str:
52
+ """Direct access without await - returns value or raises error."""
53
+ return repr(self._ensure_result())
54
54
 
55
- return wrapper
55
+ def __str__(self) -> str:
56
+ """Direct access without await - returns value or raises error."""
57
+ return str(self._ensure_result())
56
58
 
59
+ # Make it behave like the actual value when accessed
60
+ def __getattr__(self, name):
61
+ return getattr(self._ensure_result(), name)
57
62
 
58
- class AsyncTask:
63
+ def _ensure_result(self):
64
+ if not hasattr(self._task, "_result"):
65
+ raise RuntimeError(
66
+ "Task has not been executed yet. Await the task to get the result."
67
+ )
68
+ return self._task._result
69
+
70
+
71
+ class AsyncTask(typing.Generic[T]):
59
72
  """
60
73
  Represents a task to be run asynchronously.
61
74
 
@@ -64,19 +77,51 @@ class AsyncTask:
64
77
 
65
78
  def __init__(
66
79
  self,
67
- task: typing.Callable[[], typing.Coroutine | None] | typing.Coroutine,
80
+ task: typing.Callable[..., typing.Awaitable[T]] | typing.Awaitable[T],
68
81
  /,
69
82
  caller: CallerInfo | None = None,
70
83
  tags: list[str] | None = None,
71
84
  ):
72
- self.task = wrap_in_async_task_exception(task, caller=caller)
85
+ self.task = task
73
86
  self.caller = caller
74
87
  self.tags = tags or []
75
88
 
89
+ @property
90
+ def result(self):
91
+ """
92
+ Provides access to the result of the task.
93
+
94
+ Returns:
95
+ ResultAccessor[T]: An accessor that can be awaited or accessed directly.
96
+ """
97
+ return ResultAccessor(self)
98
+
76
99
  def __call__(self):
77
- if callable(self.task):
78
- return self.task()
79
- return self.task
100
+ return self._run()
101
+
102
+ def __await__(self):
103
+ return self._run().__await__()
104
+
105
+ async def _run(self):
106
+ """
107
+ Runs the task and caches the result.
108
+
109
+ Returns:
110
+ T: The result of the task.
111
+ """
112
+ if hasattr(self, "_result"):
113
+ return self._result
114
+
115
+ try:
116
+ coro = self.task
117
+ if callable(coro):
118
+ coro = coro()
119
+ self._result = await coro
120
+ return self._result
121
+ except Exception as e:
122
+ raise AsyncTaskException(
123
+ str(e), original_exception=e, caller=self.caller
124
+ ) from e
80
125
 
81
126
 
82
127
  class AsyncTaskRunner:
@@ -144,19 +189,59 @@ class AsyncTaskRunner:
144
189
  """
145
190
  self.run_tasks_even_if_exception = run_tasks_even_if_exception
146
191
 
192
+ @typing.overload
147
193
  @classmethod
148
194
  def add_task(
149
195
  cls,
150
- *tasks: typing.Callable[[], typing.Coroutine | None] | typing.Coroutine,
196
+ task: typing.Callable[..., typing.Awaitable[T]]
197
+ | typing.Awaitable[T]
198
+ | AsyncTask[T],
199
+ *,
200
+ tags: list[str] | None = None,
201
+ instance: "AsyncTaskRunner | None" = None,
202
+ ) -> AsyncTask[T]: ...
203
+
204
+ @typing.overload
205
+ @classmethod
206
+ def add_task(
207
+ cls,
208
+ task1: typing.Callable[..., typing.Awaitable[T]]
209
+ | typing.Awaitable[T]
210
+ | AsyncTask[T],
211
+ task2: typing.Callable[..., typing.Awaitable[T]]
212
+ | typing.Awaitable[T]
213
+ | AsyncTask[T],
214
+ *tasks: typing.Callable[..., typing.Awaitable[T]]
215
+ | typing.Awaitable[T]
216
+ | AsyncTask[T],
217
+ tags: list[str] | None = None,
218
+ instance: "AsyncTaskRunner | None" = None,
219
+ ) -> list[AsyncTask[T]]: ...
220
+ @classmethod
221
+ def add_task(
222
+ cls,
223
+ *tasks: typing.Callable[..., typing.Awaitable[T]]
224
+ | typing.Awaitable[T]
225
+ | AsyncTask[T],
151
226
  tags: list[str] | None = None,
152
227
  instance: "AsyncTaskRunner | None" = None,
153
228
  ):
229
+ """
230
+ Adds one or more tasks to the current context's task list. The tasks will be executed when exiting the context.
231
+
232
+ Args:
233
+ tags (list[str] | None, optional): Tags to associate with the tasks. Defaults to None.
234
+ instance (AsyncTaskRunner | None, optional): The AsyncTaskRunner instance to add tasks to. Defaults to None.
235
+
236
+ Returns:
237
+ AsyncTask[T] | list[AsyncTask[T]]: The added task(s).
238
+ """
154
239
  task_list = cls._get_current_task_list("add_task", instance=instance)
155
240
  # Get caller info
156
241
  frame = inspect.currentframe()
157
242
  caller = inspect.getouterframes(frame, 2)[1] if frame else None
158
243
 
159
- async_tasks = [
244
+ async_tasks: list[AsyncTask[T]] = [
160
245
  AsyncTask(
161
246
  task,
162
247
  caller=CallerInfo(
@@ -171,6 +256,7 @@ class AsyncTaskRunner:
171
256
  for task in tasks
172
257
  ]
173
258
  task_list.extend(async_tasks)
259
+ return async_tasks if len(async_tasks) > 1 else async_tasks[0]
174
260
 
175
261
  @classmethod
176
262
  def remove_tasks_by_tag(
@@ -215,16 +301,16 @@ class AsyncTaskRunner:
215
301
  return
216
302
 
217
303
  if tasks:
218
- exceptions: list[AsyncTaskException] = []
304
+ exceptions_with_index = list[tuple[AsyncTaskException, int]]()
219
305
  futures = await asyncio.gather(
220
306
  *(task() for task in tasks), return_exceptions=True
221
307
  )
222
- for future in futures:
308
+ for index, future in enumerate(futures):
223
309
  if isinstance(future, AsyncTaskException):
224
310
  # Handle exceptions from tasks
225
- exceptions.append(future)
311
+ exceptions_with_index.append((future, index))
226
312
 
227
- if exceptions:
313
+ if exceptions_with_index:
228
314
  raise AsyncTaskRunnerException(
229
315
  f"\n{
230
316
  prettify_dict(
@@ -238,7 +324,7 @@ class AsyncTaskRunner:
238
324
  tb.format_exception(exc.original_exception)
239
325
  ),
240
326
  }
241
- for index, exc in enumerate(exceptions)
327
+ for exc, index in exceptions_with_index
242
328
  },
243
329
  }
244
330
  )
@@ -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.5
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=B9kKWJLln1i8LjtkcYecvNWGLTrez4gCUOHtnPlInFo,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
- fastapi_rtk/backends/sqla/interface.py,sha256=7JQ1hK49PDd2_E8KeEiivbWot3zP7ZiZifmVI7-eYZY,30058
52
- fastapi_rtk/backends/sqla/model.py,sha256=dkhnVvpKAqYAqtRYl9JsARCqEqmhc6wgGy9JHj4TNdQ,6612
51
+ fastapi_rtk/backends/sqla/interface.py,sha256=9I2JiShqmyakfgdXE-DA5TW35xdvMs_-pDDccKnX5cA,27678
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
@@ -106,7 +106,7 @@ fastapi_rtk/security/sqla/apis.py,sha256=J3BCqSnaX-w9raTA5LHa7muW24kA09nAm9kN6b3
106
106
  fastapi_rtk/security/sqla/models.py,sha256=dwIZHNU-6b1r0M2swlYyRtSgI1e1hnvav6d6jdmffM8,8096
107
107
  fastapi_rtk/security/sqla/security_manager.py,sha256=eBXhDTjqQYySCKQXnD6wEeHBGw8oytwVvmKpi9btNYI,17371
108
108
  fastapi_rtk/utils/__init__.py,sha256=0X4BwrVDl4S3mB7DLyHaZVednefMjRIjBIDT3yv_CHM,1875
109
- fastapi_rtk/utils/async_task_runner.py,sha256=JYXjVPxkWH1pxXImkjlebAOcn2noEUbX-vwpBWw3WGg,12778
109
+ fastapi_rtk/utils/async_task_runner.py,sha256=HzykQSdeAmNjZfVB5vUDVwrSCVFr8__67ACQk60pSsk,15545
110
110
  fastapi_rtk/utils/class_factory.py,sha256=jlVw8yCh-tYsMnR5Hm8fgxtL0kvXwnhe6DPJA1sUh7k,598
111
111
  fastapi_rtk/utils/csv_json_converter.py,sha256=7szrPiB7DrK5S-s2GaHVCmEP9_OXk9RFwbZmRtAKM5A,14036
112
112
  fastapi_rtk/utils/deep_merge.py,sha256=PHtKJgXfCngOBGVliX9aVlEFcwCxr-GlzU-w6vjgAIs,2426
@@ -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.5.dist-info/METADATA,sha256=4qQ4mOrd3rvE3SU3590DJYKggS00IhLaCPq8DGF8ZPI,1270
130
- fastapi_rtk-1.0.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
131
- fastapi_rtk-1.0.5.dist-info/entry_points.txt,sha256=UuTkxSVIokSlVN28TMhoxzRRUaPxlVRSH3Gsx6yip60,53
132
- fastapi_rtk-1.0.5.dist-info/licenses/LICENSE,sha256=NDrWi4Qwcxal3u1r2lBWGA6TVh3OeW7yMan098mQz98,1073
133
- fastapi_rtk-1.0.5.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,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.27.0
2
+ Generator: hatchling 1.28.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any