fastapi-rtk 1.0.4__py3-none-any.whl → 1.0.6__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 +1 -1
- fastapi_rtk/backends/sqla/interface.py +0 -54
- fastapi_rtk/backends/sqla/model.py +14 -1
- fastapi_rtk/utils/async_task_runner.py +119 -30
- {fastapi_rtk-1.0.4.dist-info → fastapi_rtk-1.0.6.dist-info}/METADATA +1 -1
- {fastapi_rtk-1.0.4.dist-info → fastapi_rtk-1.0.6.dist-info}/RECORD +9 -9
- {fastapi_rtk-1.0.4.dist-info → fastapi_rtk-1.0.6.dist-info}/WHEEL +1 -1
- {fastapi_rtk-1.0.4.dist-info → fastapi_rtk-1.0.6.dist-info}/entry_points.txt +0 -0
- {fastapi_rtk-1.0.4.dist-info → fastapi_rtk-1.0.6.dist-info}/licenses/LICENSE +0 -0
fastapi_rtk/_version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "1.0.
|
|
1
|
+
__version__ = "1.0.6"
|
|
@@ -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,19 @@ 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 load the specified columns for the current instance.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
cols (list[str] | str): A list of column names to load or a single column name.
|
|
134
|
+
"""
|
|
135
|
+
async with AsyncTaskRunner() as runner:
|
|
136
|
+
if isinstance(cols, str):
|
|
137
|
+
cols = [cols]
|
|
138
|
+
for col in cols:
|
|
139
|
+
runner.add_task(getattr(self.awaitable_attrs, col))
|
|
140
|
+
|
|
128
141
|
|
|
129
142
|
class Table(SA_Table):
|
|
130
143
|
"""
|
|
@@ -2,12 +2,15 @@ import asyncio
|
|
|
2
2
|
import contextvars
|
|
3
3
|
import functools
|
|
4
4
|
import inspect
|
|
5
|
+
import traceback as tb
|
|
5
6
|
import typing
|
|
6
7
|
|
|
7
8
|
from .prettify_dict import prettify_dict
|
|
8
9
|
|
|
9
10
|
__all__ = ["AsyncTaskRunner"]
|
|
10
11
|
|
|
12
|
+
T = typing.TypeVar("T")
|
|
13
|
+
|
|
11
14
|
|
|
12
15
|
class CallerInfo(typing.TypedDict):
|
|
13
16
|
"""
|
|
@@ -35,26 +38,37 @@ class AsyncTaskException(AsyncTaskRunnerException):
|
|
|
35
38
|
self.caller = caller
|
|
36
39
|
|
|
37
40
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
except Exception as e:
|
|
52
|
-
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())
|
|
53
54
|
|
|
54
|
-
|
|
55
|
+
def __str__(self) -> str:
|
|
56
|
+
"""Direct access without await - returns value or raises error."""
|
|
57
|
+
return str(self._ensure_result())
|
|
55
58
|
|
|
59
|
+
# Make it behave like the actual value when accessed
|
|
60
|
+
def __getattr__(self, name):
|
|
61
|
+
return getattr(self._ensure_result(), name)
|
|
56
62
|
|
|
57
|
-
|
|
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]):
|
|
58
72
|
"""
|
|
59
73
|
Represents a task to be run asynchronously.
|
|
60
74
|
|
|
@@ -63,19 +77,51 @@ class AsyncTask:
|
|
|
63
77
|
|
|
64
78
|
def __init__(
|
|
65
79
|
self,
|
|
66
|
-
task: typing.Callable[
|
|
80
|
+
task: typing.Callable[..., typing.Awaitable[T]] | typing.Awaitable[T],
|
|
67
81
|
/,
|
|
68
82
|
caller: CallerInfo | None = None,
|
|
69
83
|
tags: list[str] | None = None,
|
|
70
84
|
):
|
|
71
|
-
self.task =
|
|
85
|
+
self.task = task
|
|
72
86
|
self.caller = caller
|
|
73
87
|
self.tags = tags or []
|
|
74
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
|
+
|
|
75
99
|
def __call__(self):
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
|
79
125
|
|
|
80
126
|
|
|
81
127
|
class AsyncTaskRunner:
|
|
@@ -143,19 +189,59 @@ class AsyncTaskRunner:
|
|
|
143
189
|
"""
|
|
144
190
|
self.run_tasks_even_if_exception = run_tasks_even_if_exception
|
|
145
191
|
|
|
192
|
+
@typing.overload
|
|
146
193
|
@classmethod
|
|
147
194
|
def add_task(
|
|
148
195
|
cls,
|
|
149
|
-
|
|
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],
|
|
150
226
|
tags: list[str] | None = None,
|
|
151
227
|
instance: "AsyncTaskRunner | None" = None,
|
|
152
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
|
+
"""
|
|
153
239
|
task_list = cls._get_current_task_list("add_task", instance=instance)
|
|
154
240
|
# Get caller info
|
|
155
241
|
frame = inspect.currentframe()
|
|
156
242
|
caller = inspect.getouterframes(frame, 2)[1] if frame else None
|
|
157
243
|
|
|
158
|
-
async_tasks = [
|
|
244
|
+
async_tasks: list[AsyncTask[T]] = [
|
|
159
245
|
AsyncTask(
|
|
160
246
|
task,
|
|
161
247
|
caller=CallerInfo(
|
|
@@ -170,6 +256,7 @@ class AsyncTaskRunner:
|
|
|
170
256
|
for task in tasks
|
|
171
257
|
]
|
|
172
258
|
task_list.extend(async_tasks)
|
|
259
|
+
return async_tasks if len(async_tasks) > 1 else async_tasks[0]
|
|
173
260
|
|
|
174
261
|
@classmethod
|
|
175
262
|
def remove_tasks_by_tag(
|
|
@@ -214,16 +301,16 @@ class AsyncTaskRunner:
|
|
|
214
301
|
return
|
|
215
302
|
|
|
216
303
|
if tasks:
|
|
217
|
-
|
|
304
|
+
exceptions_with_index = list[tuple[AsyncTaskException, int]]()
|
|
218
305
|
futures = await asyncio.gather(
|
|
219
306
|
*(task() for task in tasks), return_exceptions=True
|
|
220
307
|
)
|
|
221
|
-
for future in futures:
|
|
308
|
+
for index, future in enumerate(futures):
|
|
222
309
|
if isinstance(future, AsyncTaskException):
|
|
223
310
|
# Handle exceptions from tasks
|
|
224
|
-
|
|
311
|
+
exceptions_with_index.append((future, index))
|
|
225
312
|
|
|
226
|
-
if
|
|
313
|
+
if exceptions_with_index:
|
|
227
314
|
raise AsyncTaskRunnerException(
|
|
228
315
|
f"\n{
|
|
229
316
|
prettify_dict(
|
|
@@ -233,9 +320,11 @@ class AsyncTaskRunner:
|
|
|
233
320
|
f'Task {index + 1}': {
|
|
234
321
|
'message': str(exc),
|
|
235
322
|
'caller': exc.caller,
|
|
236
|
-
'traceback':
|
|
323
|
+
'traceback': ''.join(
|
|
324
|
+
tb.format_exception(exc.original_exception)
|
|
325
|
+
),
|
|
237
326
|
}
|
|
238
|
-
for
|
|
327
|
+
for exc, index in exceptions_with_index
|
|
239
328
|
},
|
|
240
329
|
}
|
|
241
330
|
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fastapi-rtk
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.6
|
|
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,5 +1,5 @@
|
|
|
1
1
|
fastapi_rtk/__init__.py,sha256=acLIihNMCZI3prFTq0cru1-k3kPjSZb2hhcqNrW7xJo,6203
|
|
2
|
-
fastapi_rtk/_version.py,sha256=
|
|
2
|
+
fastapi_rtk/_version.py,sha256=mqMuQB3aqJVPrHHqJMLjqiMKUiJjozc7EPLcX5DpKHg,22
|
|
3
3
|
fastapi_rtk/apis.py,sha256=6X_Lhl98m7lKrDRybg2Oe24pLFLJ29eCOQSwCAvpKhY,172
|
|
4
4
|
fastapi_rtk/config.py,sha256=9PZF9E5i1gxmnsZEprZZKxVHSk0dFEklJSplX9NEqdo,14036
|
|
5
5
|
fastapi_rtk/const.py,sha256=huvh4Zor77fgUkhU4-x6LgkOglSxeKXOlXdhnai_5CQ,4905
|
|
@@ -48,8 +48,8 @@ fastapi_rtk/backends/sqla/column.py,sha256=KutGcyFr3ZeHFTL8c313I4CNxosGq54neLLB7
|
|
|
48
48
|
fastapi_rtk/backends/sqla/db.py,sha256=hnE1jQFZai96IaQ7jWqAsiEK0PXnGpMgvgZIGSlPBsM,17245
|
|
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=
|
|
52
|
-
fastapi_rtk/backends/sqla/model.py,sha256=
|
|
51
|
+
fastapi_rtk/backends/sqla/interface.py,sha256=9I2JiShqmyakfgdXE-DA5TW35xdvMs_-pDDccKnX5cA,27678
|
|
52
|
+
fastapi_rtk/backends/sqla/model.py,sha256=GqWJyb4uq7AK58_zZw5jx4BQ6xWi5thwNqr8WGfnJ6o,7099
|
|
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
|
|
@@ -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=
|
|
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
|
|
@@ -126,8 +126,8 @@ fastapi_rtk/utils/timezone.py,sha256=62S0pPWuDFFXxV1YTFCsc4uKiSP_Ba36Fv7S3gYjfhs
|
|
|
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.
|
|
130
|
-
fastapi_rtk-1.0.
|
|
131
|
-
fastapi_rtk-1.0.
|
|
132
|
-
fastapi_rtk-1.0.
|
|
133
|
-
fastapi_rtk-1.0.
|
|
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,,
|
|
File without changes
|
|
File without changes
|