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
|
@@ -1,364 +0,0 @@
|
|
|
1
|
-
import collections
|
|
2
|
-
import json
|
|
3
|
-
import typing
|
|
4
|
-
|
|
5
|
-
import fastapi
|
|
6
|
-
import gremlin_python.process.graph_traversal
|
|
7
|
-
import gremlin_python.process.traversal
|
|
8
|
-
import janusgraph_python.process.traversal
|
|
9
|
-
|
|
10
|
-
from ...const import logger
|
|
11
|
-
from ...setting import Setting
|
|
12
|
-
from ...utils import lazy
|
|
13
|
-
from ..generic.exceptions import MultipleColumnsException
|
|
14
|
-
from ..generic.model import GenericModel, Mapper
|
|
15
|
-
from .column import GremlinColumn, GremlinRelationship
|
|
16
|
-
from .exceptions import GremlinMissingLabelException, LKMultipleException
|
|
17
|
-
|
|
18
|
-
__all__ = ["GremlinModel"]
|
|
19
|
-
|
|
20
|
-
UNSPECIFIED_LABEL = "__unspecified_label_"
|
|
21
|
-
|
|
22
|
-
try:
|
|
23
|
-
import janusgraph_python
|
|
24
|
-
|
|
25
|
-
janusgraph_python_exists = True
|
|
26
|
-
except ImportError:
|
|
27
|
-
janusgraph_python_exists = False
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
class GremlinMapper(Mapper[GremlinColumn | GremlinRelationship]):
|
|
31
|
-
lk: str | None = None
|
|
32
|
-
"""
|
|
33
|
-
The label key of the model. Used to set the label from the database to the model. Defaults to `label`.
|
|
34
|
-
"""
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
class GremlinModel(GenericModel):
|
|
38
|
-
"""
|
|
39
|
-
A base class for Gremlin models, which are used to represent vertices in a Gremlin graph.
|
|
40
|
-
|
|
41
|
-
## Example:
|
|
42
|
-
|
|
43
|
-
```python
|
|
44
|
-
from fastapi_rtk import GremlinModel, GremlinColumn, GremlinRelationship, GremlinInterface, ModelRestApi
|
|
45
|
-
from app import toolkit # Assuming you have a `toolkit` instance set up for your FastAPI application.
|
|
46
|
-
|
|
47
|
-
class User(GremlinModel):
|
|
48
|
-
__label__ = "Person"
|
|
49
|
-
|
|
50
|
-
id = GremlinColumn(str, primary_key=True) # Primary key of the model, typically the vertex ID in the graph.
|
|
51
|
-
label = GremlinColumn(str, label_key=True, default="Person") # Label of the vertex. Label from the graph will be stored here.
|
|
52
|
-
firstname = GremlinColumn(str, default="") # A property in the model, which will be stored as a property in the vertex.
|
|
53
|
-
type = GremlinColumn(str, default="user") # A type of the model, which can be used to filter vertices in the graph.
|
|
54
|
-
|
|
55
|
-
tasks = GremlinRelationship(
|
|
56
|
-
list,
|
|
57
|
-
name="related", # The name of the edge in the graph.
|
|
58
|
-
properties={"type": "task"}, # Additional properties to filter the edge.
|
|
59
|
-
uselist=True, # Whether the relationship is a list of related vertices.
|
|
60
|
-
obj="Task", # The model class that this relationship points to. If given, the related object can be added or edited.
|
|
61
|
-
obj_properties={"type": "task"}, # Additional properties to filter the related vertex.
|
|
62
|
-
)
|
|
63
|
-
|
|
64
|
-
friends = GremlinRelationship(
|
|
65
|
-
list,
|
|
66
|
-
name="related",
|
|
67
|
-
properties={"type": "friend"},
|
|
68
|
-
uselist=True,
|
|
69
|
-
with_edge=True, # If True, the column will return a dictionary with two keys: "edge" and the property name. The "edge" key will contain the edge properties. If `obj` is set, the related object needs to have an `edge` property to hold the edge properties.
|
|
70
|
-
direction="both", # The direction of the relationship. Use "in" for incoming relationships, "out" for outgoing relationships, or "both" for both directions.
|
|
71
|
-
obj_properties={"type": "user"}, # Additional properties to filter the related vertex.
|
|
72
|
-
) # Without `obj`, frontend will see the column as a simple dictionary.
|
|
73
|
-
|
|
74
|
-
class Task(GremlinModel):
|
|
75
|
-
__label__ = "Task"
|
|
76
|
-
|
|
77
|
-
... # Similar to User, but represents a task in the graph.
|
|
78
|
-
|
|
79
|
-
class UserApi(ModelRestApi):
|
|
80
|
-
resource_name = "users" # The name of the API resource.
|
|
81
|
-
datamodel = GremlinInterface(User) # The data model for the API, which is the User model.
|
|
82
|
-
|
|
83
|
-
... # Additional API methods and configurations.
|
|
84
|
-
|
|
85
|
-
toolkit.add_api(UserApi) # Register the API with the toolkit.
|
|
86
|
-
```
|
|
87
|
-
"""
|
|
88
|
-
|
|
89
|
-
__ignore_init__ = True
|
|
90
|
-
__model_callbacks__: dict[
|
|
91
|
-
str, list[typing.Callable[[typing.Type["GremlinModel"]], None]]
|
|
92
|
-
] = collections.defaultdict(list)
|
|
93
|
-
|
|
94
|
-
__mapper__: GremlinMapper = lazy(lambda: GremlinMapper(), only_instance=False)
|
|
95
|
-
"""
|
|
96
|
-
A mapper that contains the primary key, properties, and columns of the model.
|
|
97
|
-
"""
|
|
98
|
-
|
|
99
|
-
def __init_subclass__(cls):
|
|
100
|
-
if cls.__label__ is None:
|
|
101
|
-
if not cls.__ignore_label__:
|
|
102
|
-
raise GremlinMissingLabelException(
|
|
103
|
-
f"Gremlin model '{cls.__name__}' must have a label defined. If you want to ignore this, set '__ignore_label__ = True' in the class definition."
|
|
104
|
-
)
|
|
105
|
-
else:
|
|
106
|
-
logger.info(
|
|
107
|
-
f"Gremlin model '{cls.__name__}' does not have a label defined. This will result in query of vertices without any filtering on label."
|
|
108
|
-
)
|
|
109
|
-
|
|
110
|
-
if cls.__name__ in GremlinModel.__model_callbacks__:
|
|
111
|
-
for callback in GremlinModel.__model_callbacks__[cls.__name__]:
|
|
112
|
-
callback(cls)
|
|
113
|
-
del GremlinModel.__model_callbacks__[cls.__name__]
|
|
114
|
-
|
|
115
|
-
# Do not ignore the init for subclasses of GremlinModel
|
|
116
|
-
cls.__ignore_init__ = False
|
|
117
|
-
super().__init_subclass__()
|
|
118
|
-
cls.__mapper__.columns = [
|
|
119
|
-
x
|
|
120
|
-
for x in cls.__mapper__.columns
|
|
121
|
-
if not isinstance(cls.__mapper__.properties[x], GremlinRelationship)
|
|
122
|
-
]
|
|
123
|
-
for key, value in cls.__mapper__.properties.items():
|
|
124
|
-
if value.label_key:
|
|
125
|
-
if cls.__mapper__.lk and cls.__mapper__.lk != key:
|
|
126
|
-
raise LKMultipleException(
|
|
127
|
-
cls.__name__, "Only one label key is allowed"
|
|
128
|
-
)
|
|
129
|
-
cls.__mapper__.lk = key
|
|
130
|
-
|
|
131
|
-
__label__: str | None = None
|
|
132
|
-
"""
|
|
133
|
-
The label of the model, used to filter vertices in Gremlin queries. If not set, the model will not be filtered by label, which may lead to unexpected results.
|
|
134
|
-
"""
|
|
135
|
-
__properties__: dict[str, typing.Any] | None = None
|
|
136
|
-
"""
|
|
137
|
-
Properties of the model, used to filter vertices in Gremlin queries.
|
|
138
|
-
"""
|
|
139
|
-
__ignore_label__ = False
|
|
140
|
-
"""
|
|
141
|
-
If set to True, the model will not require a label. This is useful for models that do not represent a specific vertex type, or when the label is not relevant for the model's functionality.
|
|
142
|
-
"""
|
|
143
|
-
__session__: gremlin_python.process.graph_traversal.GraphTraversalSource = None
|
|
144
|
-
"""
|
|
145
|
-
The session used for executing Gremlin queries. This is typically set by the interface.
|
|
146
|
-
"""
|
|
147
|
-
__data__: dict[str, typing.Any] = None
|
|
148
|
-
"""
|
|
149
|
-
The data of the model instance, typically set during initialization. You can get the whole data from the database by accessing this attribute.
|
|
150
|
-
"""
|
|
151
|
-
__reserved_letters__ = lazy(lambda: Setting.JANUSGRAPH_RESERVED_LETTERS)
|
|
152
|
-
"""
|
|
153
|
-
A list of reserved letters that cannot be used in the primary key of the model. This is to avoid conflicts with JanusGraph's internal representation of vertices and edges. Only used if `janusgraph_python` is available.
|
|
154
|
-
"""
|
|
155
|
-
__janusgraph_python_exists__ = lazy(
|
|
156
|
-
lambda: janusgraph_python_exists, only_instance=False
|
|
157
|
-
)
|
|
158
|
-
"""
|
|
159
|
-
A boolean indicating whether the `janusgraph_python` package is available.
|
|
160
|
-
"""
|
|
161
|
-
|
|
162
|
-
def __init__(self, __table__=None, **kwargs):
|
|
163
|
-
self.__table__ = __table__
|
|
164
|
-
super().__init__(**kwargs)
|
|
165
|
-
self.__data__ = kwargs
|
|
166
|
-
|
|
167
|
-
@classmethod
|
|
168
|
-
def from_gremlinpython(
|
|
169
|
-
cls,
|
|
170
|
-
data: dict[str, typing.Any],
|
|
171
|
-
/,
|
|
172
|
-
preserve_as_list: list[str] | None = None,
|
|
173
|
-
):
|
|
174
|
-
"""
|
|
175
|
-
Create an instance of the model from a dictionary typically returned by a Gremlin query.
|
|
176
|
-
|
|
177
|
-
Args:
|
|
178
|
-
data (dict[str, typing.Any]): The dictionary to parse, typically from a Gremlin query with valueMap.
|
|
179
|
-
preserve_as_list (list[str] | None, optional): A list of keys that should be preserved as lists even if they contain a single item. If None, all keys are treated normally. Defaults to None.
|
|
180
|
-
|
|
181
|
-
Returns:
|
|
182
|
-
GremlinModel: An instance of the model with properties set from the parsed data.
|
|
183
|
-
"""
|
|
184
|
-
params = {"id_key": cls.__mapper__.pk, "preserve_as_list": preserve_as_list}
|
|
185
|
-
if cls.__mapper__.lk:
|
|
186
|
-
params["label_key"] = cls.__mapper__.lk
|
|
187
|
-
parsed_data = cls.parse_from_gremlinpython(data, **params)
|
|
188
|
-
for key in parsed_data.keys():
|
|
189
|
-
column = cls.__mapper__.properties.get(key)
|
|
190
|
-
if (
|
|
191
|
-
not column
|
|
192
|
-
or not isinstance(column, GremlinRelationship)
|
|
193
|
-
or not column.obj
|
|
194
|
-
):
|
|
195
|
-
continue
|
|
196
|
-
value = parsed_data[key]
|
|
197
|
-
if not value:
|
|
198
|
-
continue
|
|
199
|
-
if column.uselist:
|
|
200
|
-
value = [column.obj.from_gremlinpython(v) for v in value]
|
|
201
|
-
else:
|
|
202
|
-
value = column.obj.from_gremlinpython(value)
|
|
203
|
-
parsed_data[key] = value
|
|
204
|
-
return cls(__table__=True, **parsed_data)
|
|
205
|
-
|
|
206
|
-
def to_gremlinpython(self):
|
|
207
|
-
"""
|
|
208
|
-
Convert the model instance to a Gremlin Python compatible dictionary.
|
|
209
|
-
|
|
210
|
-
Returns:
|
|
211
|
-
dict: A dictionary representation of the model instance suitable for Gremlin Python, with keys for the primary key and label, and other properties as they are.
|
|
212
|
-
"""
|
|
213
|
-
return self.parse_to_gremlinpython(
|
|
214
|
-
self.to_json(with_id=False, with_name=False),
|
|
215
|
-
id_key=self.__mapper__.pk,
|
|
216
|
-
label_key=self.__mapper__.lk,
|
|
217
|
-
)
|
|
218
|
-
|
|
219
|
-
def to_json(self, *, with_id=True, with_name=True):
|
|
220
|
-
"""
|
|
221
|
-
Convert the model instance to a JSON-compatible dictionary.
|
|
222
|
-
|
|
223
|
-
- Properties that are not set will be excluded from the output.
|
|
224
|
-
- Properties that are instances of `GremlinModel` will be converted to their JSON representation.
|
|
225
|
-
- Properties that are lists or tuples will have their items converted to JSON representations if they are instances of `GremlinModel`.
|
|
226
|
-
|
|
227
|
-
Args:
|
|
228
|
-
with_id (bool, optional): Whether to include the `id_` field in the output. Defaults to True.
|
|
229
|
-
with_name (bool, optional): Whether to include the `name_` field in the output. Defaults to True.
|
|
230
|
-
|
|
231
|
-
Returns:
|
|
232
|
-
dict: A dictionary representation of the model instance, with keys for each property and the `id_` and `name_` fields if specified.
|
|
233
|
-
"""
|
|
234
|
-
data = dict[str, typing.Any]()
|
|
235
|
-
cls = self.__class__
|
|
236
|
-
for key in self.__mapper__.properties.keys():
|
|
237
|
-
if not getattr(cls, key).is_column_set(self):
|
|
238
|
-
continue
|
|
239
|
-
value = getattr(self, key)
|
|
240
|
-
if isinstance(value, GremlinModel):
|
|
241
|
-
value = value.to_json(with_id=with_id, with_name=with_name)
|
|
242
|
-
elif isinstance(value, (list, tuple)):
|
|
243
|
-
value = [
|
|
244
|
-
v.to_json(with_id=with_id, with_name=with_name)
|
|
245
|
-
if isinstance(v, GremlinModel)
|
|
246
|
-
else v
|
|
247
|
-
for v in value
|
|
248
|
-
]
|
|
249
|
-
data[key] = value
|
|
250
|
-
if with_id:
|
|
251
|
-
data["id_"] = self.id_
|
|
252
|
-
if with_name:
|
|
253
|
-
data["name_"] = self.name_
|
|
254
|
-
return data
|
|
255
|
-
|
|
256
|
-
@staticmethod
|
|
257
|
-
def parse_from_gremlinpython(
|
|
258
|
-
data: dict[str, typing.Any],
|
|
259
|
-
/,
|
|
260
|
-
id_key="id",
|
|
261
|
-
label_key="label",
|
|
262
|
-
preserve_as_list: list[str] | None = None,
|
|
263
|
-
):
|
|
264
|
-
"""
|
|
265
|
-
Parse a dictionary from Gremlin Python into a dictionary with specific keys.
|
|
266
|
-
|
|
267
|
-
Args:
|
|
268
|
-
data (dict[str, typing.Any]): The dictionary to parse, typically from a Gremlin query with valueMap.
|
|
269
|
-
id_key (str, optional): The key for the primary key in the parsed dictionary. Defaults to "id".
|
|
270
|
-
label_key (str, optional): The key for the label in the parsed dictionary. Defaults to "label".
|
|
271
|
-
preserve_as_list (list[str] | None, optional): A list of keys that should be preserved as lists even if they contain a single item. If None, all keys are treated normally. Defaults to None.
|
|
272
|
-
|
|
273
|
-
Returns:
|
|
274
|
-
dict: A dictionary with keys for the primary key and label, and other properties as they are.
|
|
275
|
-
"""
|
|
276
|
-
parsed = dict[str, typing.Any]()
|
|
277
|
-
for key, value in data.items():
|
|
278
|
-
if (
|
|
279
|
-
(isinstance(value, list) or isinstance(value, tuple))
|
|
280
|
-
and len(value) == 1
|
|
281
|
-
and (preserve_as_list is None or key not in preserve_as_list)
|
|
282
|
-
):
|
|
283
|
-
value = value[0]
|
|
284
|
-
|
|
285
|
-
# A dictionary means it is coming from the database directly, and not a stringified JSON.
|
|
286
|
-
if isinstance(value, dict):
|
|
287
|
-
value = GremlinModel.parse_from_gremlinpython(value)
|
|
288
|
-
elif isinstance(
|
|
289
|
-
value, janusgraph_python.process.traversal.RelationIdentifier
|
|
290
|
-
):
|
|
291
|
-
parsed["source"] = value.out_vertex_id
|
|
292
|
-
parsed["target"] = value.in_vertex_id
|
|
293
|
-
value = str(value)
|
|
294
|
-
|
|
295
|
-
try:
|
|
296
|
-
value = json.loads(value)
|
|
297
|
-
except json.JSONDecodeError:
|
|
298
|
-
pass
|
|
299
|
-
except TypeError:
|
|
300
|
-
pass
|
|
301
|
-
if key == gremlin_python.process.traversal.T.id:
|
|
302
|
-
parsed[id_key] = value
|
|
303
|
-
elif key == gremlin_python.process.traversal.T.label:
|
|
304
|
-
parsed[label_key] = value
|
|
305
|
-
else:
|
|
306
|
-
parsed[key] = value
|
|
307
|
-
return parsed
|
|
308
|
-
|
|
309
|
-
@staticmethod
|
|
310
|
-
def parse_to_gremlinpython(
|
|
311
|
-
data: dict[str, typing.Any], /, id_key="id", label_key="label"
|
|
312
|
-
):
|
|
313
|
-
"""
|
|
314
|
-
Convert a dictionary to a Gremlin Python compatible format.
|
|
315
|
-
|
|
316
|
-
Args:
|
|
317
|
-
data (dict[str, typing.Any]): The dictionary to convert.
|
|
318
|
-
id_key (str): The key for the primary key in Gremlin Python.
|
|
319
|
-
label_key (str): The key for the label in Gremlin Python.
|
|
320
|
-
|
|
321
|
-
Returns:
|
|
322
|
-
dict: A dictionary representation suitable for Gremlin Python.
|
|
323
|
-
"""
|
|
324
|
-
result = dict[str, typing.Any]()
|
|
325
|
-
for key, value in data.items():
|
|
326
|
-
if isinstance(value, (list, tuple, dict)):
|
|
327
|
-
value = json.dumps(value)
|
|
328
|
-
if key == id_key:
|
|
329
|
-
key = gremlin_python.process.traversal.T.id
|
|
330
|
-
elif key == label_key:
|
|
331
|
-
key = gremlin_python.process.traversal.T.label
|
|
332
|
-
result[key] = value
|
|
333
|
-
return result
|
|
334
|
-
|
|
335
|
-
@staticmethod
|
|
336
|
-
def _register_model_callback(
|
|
337
|
-
model_name: str, callback: typing.Callable[[typing.Type["GremlinModel"]], None]
|
|
338
|
-
):
|
|
339
|
-
"""
|
|
340
|
-
Register a callback for a model that will be called when the model is loaded.
|
|
341
|
-
|
|
342
|
-
Args:
|
|
343
|
-
model_name (str): The name of the model to register the callback for.
|
|
344
|
-
callback (typing.Callable[[typing.Type["GremlinModel"]], None]): The callback function to register. It should accept a single argument, which is the model class itself.
|
|
345
|
-
"""
|
|
346
|
-
GremlinModel.__model_callbacks__[model_name].append(callback)
|
|
347
|
-
|
|
348
|
-
def is_model_valid(self):
|
|
349
|
-
try:
|
|
350
|
-
super().is_model_valid()
|
|
351
|
-
except MultipleColumnsException:
|
|
352
|
-
if not self.__table__:
|
|
353
|
-
raise
|
|
354
|
-
if not self.__janusgraph_python_exists__:
|
|
355
|
-
return
|
|
356
|
-
pk = self.get_pk()
|
|
357
|
-
if isinstance(pk, str):
|
|
358
|
-
for letter in self.__reserved_letters__:
|
|
359
|
-
if letter in pk:
|
|
360
|
-
raise fastapi.HTTPException(
|
|
361
|
-
fastapi.status.HTTP_400_BAD_REQUEST,
|
|
362
|
-
f"Primary key '{pk}' contains reserved letter '{letter}'. "
|
|
363
|
-
"Please use a different primary key.",
|
|
364
|
-
)
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
from .db import graph_db
|
|
2
|
-
|
|
3
|
-
__all__ = ["get_graph_traversal_factory"]
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
def get_graph_traversal_factory(keep_open=False):
|
|
7
|
-
"""
|
|
8
|
-
Factory function that returns a generator for creating Gremlin graph traversals.
|
|
9
|
-
|
|
10
|
-
Can be used as a dependency in FastAPI routes.
|
|
11
|
-
|
|
12
|
-
Args:
|
|
13
|
-
keep_open (bool, optional): Whether to keep the connection open after use. Defaults to False.
|
|
14
|
-
|
|
15
|
-
Returns:
|
|
16
|
-
typing.Callable[[], typing.Generator[gremlin_python.process.graph_traversal.GraphTraversal, Any, None]]: A generator function that yields a graph traversal object.
|
|
17
|
-
"""
|
|
18
|
-
|
|
19
|
-
def get_graph_traversal():
|
|
20
|
-
with graph_db.session(keep_open=keep_open) as session:
|
|
21
|
-
yield session
|
|
22
|
-
|
|
23
|
-
return get_graph_traversal
|
fastapi_rtk/cli/commands.py
DELETED
|
@@ -1,295 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
from pathlib import Path
|
|
3
|
-
from typing import Literal
|
|
4
|
-
|
|
5
|
-
import sqlalchemy
|
|
6
|
-
import typer
|
|
7
|
-
from alembic import command
|
|
8
|
-
from alembic.config import Config as AlembicConfig
|
|
9
|
-
from rich import print
|
|
10
|
-
|
|
11
|
-
from ..db import db
|
|
12
|
-
from ..globals import g
|
|
13
|
-
from ..security.sqla.models import Role
|
|
14
|
-
from ..utils import safe_call
|
|
15
|
-
from ..version import __version__
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class Config(AlembicConfig):
|
|
19
|
-
def __init__(self, *args, **kwargs):
|
|
20
|
-
self.template_directory = kwargs.pop("template_directory", None)
|
|
21
|
-
super().__init__(*args, **kwargs)
|
|
22
|
-
|
|
23
|
-
def get_template_directory(self):
|
|
24
|
-
if self.template_directory:
|
|
25
|
-
return self.template_directory
|
|
26
|
-
package_dir = os.path.abspath(os.path.dirname(__file__))
|
|
27
|
-
return os.path.join(package_dir, "templates")
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def version_callback(value: bool) -> None:
|
|
31
|
-
if value:
|
|
32
|
-
print(f"FastAPI-RTK CLI version: [green]{__version__}[/green]")
|
|
33
|
-
raise typer.Exit()
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
def path_callback(value: Path | None) -> None:
|
|
37
|
-
g.path = value
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
def list_templates():
|
|
41
|
-
config = Config()
|
|
42
|
-
config.print_stdout("Available templates:\n")
|
|
43
|
-
for tempname in sorted(os.listdir(config.get_template_directory())):
|
|
44
|
-
with open(
|
|
45
|
-
os.path.join(config.get_template_directory(), tempname, "README")
|
|
46
|
-
) as readme:
|
|
47
|
-
synopsis = next(readme).strip()
|
|
48
|
-
config.print_stdout("%s - %s", tempname, synopsis)
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
def init(
|
|
52
|
-
directory: str = "migrations",
|
|
53
|
-
multidb: bool = False,
|
|
54
|
-
template: str = "fastapi",
|
|
55
|
-
package: bool = False,
|
|
56
|
-
):
|
|
57
|
-
template_directory = None
|
|
58
|
-
if "/" in template or "\\" in template:
|
|
59
|
-
template_directory, template = os.path.split(template)
|
|
60
|
-
config = Config(template_directory=template_directory)
|
|
61
|
-
config.set_main_option("script_location", directory)
|
|
62
|
-
config.config_file_name = os.path.join(directory, "alembic.ini")
|
|
63
|
-
# TODO: Fix this
|
|
64
|
-
# config = current_app.extensions["migrate"].migrate.call_configure_callbacks(config)
|
|
65
|
-
if multidb and template == "fastapi":
|
|
66
|
-
template = "fastapi-multidb"
|
|
67
|
-
command.init(config, directory, template=template, package=package)
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
def revision(
|
|
71
|
-
directory: str = "migrations",
|
|
72
|
-
message: str = "",
|
|
73
|
-
autogenerate: bool = False,
|
|
74
|
-
sql: bool = False,
|
|
75
|
-
head: str = "head",
|
|
76
|
-
splice: bool = False,
|
|
77
|
-
branch_label: str = None,
|
|
78
|
-
version_path: str = None,
|
|
79
|
-
rev_id: str = None,
|
|
80
|
-
):
|
|
81
|
-
opts = ["autogenerate"] if autogenerate else None
|
|
82
|
-
config = Config()
|
|
83
|
-
config.set_main_option("script_location", directory)
|
|
84
|
-
config.cmd_opts = opts
|
|
85
|
-
# config = current_app.extensions["migrate"].migrate.get_config(directory, opts=opts)
|
|
86
|
-
command.revision(
|
|
87
|
-
config,
|
|
88
|
-
message,
|
|
89
|
-
autogenerate=autogenerate,
|
|
90
|
-
sql=sql,
|
|
91
|
-
head=head,
|
|
92
|
-
splice=splice,
|
|
93
|
-
branch_label=branch_label,
|
|
94
|
-
version_path=version_path,
|
|
95
|
-
rev_id=rev_id,
|
|
96
|
-
)
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
def migrate(
|
|
100
|
-
directory: str = "migrations",
|
|
101
|
-
message: str = "",
|
|
102
|
-
sql: bool = False,
|
|
103
|
-
head: str = "head",
|
|
104
|
-
splice: bool = False,
|
|
105
|
-
branch_label: str = None,
|
|
106
|
-
version_path: str = None,
|
|
107
|
-
rev_id: str = None,
|
|
108
|
-
):
|
|
109
|
-
return revision(
|
|
110
|
-
directory=directory,
|
|
111
|
-
message=message,
|
|
112
|
-
autogenerate=True,
|
|
113
|
-
sql=sql,
|
|
114
|
-
head=head,
|
|
115
|
-
splice=splice,
|
|
116
|
-
branch_label=branch_label,
|
|
117
|
-
version_path=version_path,
|
|
118
|
-
rev_id=rev_id,
|
|
119
|
-
)
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
def edit(
|
|
123
|
-
directory: str = "migrations",
|
|
124
|
-
revision: str = "current",
|
|
125
|
-
):
|
|
126
|
-
config = Config()
|
|
127
|
-
config.set_main_option("script_location", directory)
|
|
128
|
-
command.edit(config, revision)
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
def merge(
|
|
132
|
-
directory: str = "migrations",
|
|
133
|
-
revisions: str = "",
|
|
134
|
-
message: str = "",
|
|
135
|
-
branch_label: str = None,
|
|
136
|
-
rev_id: str = None,
|
|
137
|
-
):
|
|
138
|
-
config = Config()
|
|
139
|
-
config.set_main_option("script_location", directory)
|
|
140
|
-
command.merge(
|
|
141
|
-
config,
|
|
142
|
-
revisions,
|
|
143
|
-
message=message,
|
|
144
|
-
branch_label=branch_label,
|
|
145
|
-
rev_id=rev_id,
|
|
146
|
-
)
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
def upgrade(
|
|
150
|
-
directory: str = "migrations",
|
|
151
|
-
revision: str = "head",
|
|
152
|
-
sql: bool = False,
|
|
153
|
-
tag: str = None,
|
|
154
|
-
):
|
|
155
|
-
config = Config()
|
|
156
|
-
config.set_main_option("script_location", directory)
|
|
157
|
-
command.upgrade(config, revision, sql=sql, tag=tag)
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
def downgrade(
|
|
161
|
-
directory: str = "migrations",
|
|
162
|
-
revision: str = "-1",
|
|
163
|
-
sql: bool = False,
|
|
164
|
-
tag: str = None,
|
|
165
|
-
):
|
|
166
|
-
config = Config()
|
|
167
|
-
config.set_main_option("script_location", directory)
|
|
168
|
-
command.downgrade(config, revision, sql=sql, tag=tag)
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
def show(directory: str = "migrations", revision: str = "head"):
|
|
172
|
-
config = Config()
|
|
173
|
-
config.set_main_option("script_location", directory)
|
|
174
|
-
command.show(config, revision)
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
def history(
|
|
178
|
-
directory: str = "migrations",
|
|
179
|
-
rev_range: str = None,
|
|
180
|
-
verbose: bool = False,
|
|
181
|
-
indicate_current: bool = False,
|
|
182
|
-
):
|
|
183
|
-
config = Config()
|
|
184
|
-
config.set_main_option("script_location", directory)
|
|
185
|
-
command.history(
|
|
186
|
-
config, rev_range, verbose=verbose, indicate_current=indicate_current
|
|
187
|
-
)
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
def heads(
|
|
191
|
-
directory: str = "migrations",
|
|
192
|
-
verbose: bool = False,
|
|
193
|
-
resolve_dependencies: bool = False,
|
|
194
|
-
):
|
|
195
|
-
config = Config()
|
|
196
|
-
config.set_main_option("script_location", directory)
|
|
197
|
-
command.heads(config, verbose=verbose, resolve_dependencies=resolve_dependencies)
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
def branches(
|
|
201
|
-
directory: str = "migrations",
|
|
202
|
-
verbose: bool = False,
|
|
203
|
-
):
|
|
204
|
-
config = Config()
|
|
205
|
-
config.set_main_option("script_location", directory)
|
|
206
|
-
command.branches(config, verbose=verbose)
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
def current(directory: str = "migrations", verbose: bool = False):
|
|
210
|
-
config = Config()
|
|
211
|
-
config.set_main_option("script_location", directory)
|
|
212
|
-
command.current(config, verbose=verbose)
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
def stamp(
|
|
216
|
-
directory: str = "migrations",
|
|
217
|
-
revision: str = "head",
|
|
218
|
-
sql: bool = False,
|
|
219
|
-
tag: str = None,
|
|
220
|
-
purge: bool = False,
|
|
221
|
-
):
|
|
222
|
-
config = Config()
|
|
223
|
-
config.set_main_option("script_location", directory)
|
|
224
|
-
command.stamp(config, revision, sql=sql, tag=tag, purge=purge)
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
def check(directory: str = "migrations"):
|
|
228
|
-
config = Config()
|
|
229
|
-
config.set_main_option("script_location", directory)
|
|
230
|
-
command.check(config)
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
### RTK CLI ###
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
async def _check_roles(name: str, create: bool = False):
|
|
237
|
-
async with db.session() as session:
|
|
238
|
-
stmt = sqlalchemy.select(Role).where(Role.name == name)
|
|
239
|
-
role = (await safe_call(session.execute(stmt))).scalar_one_or_none()
|
|
240
|
-
if not role:
|
|
241
|
-
if not create:
|
|
242
|
-
raise Exception(f"Role {name} does not exist")
|
|
243
|
-
await g.current_app.security.create_role(name=name, session=session)
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
async def create_user(
|
|
247
|
-
username: str,
|
|
248
|
-
email: str,
|
|
249
|
-
password: str,
|
|
250
|
-
first_name: str = "",
|
|
251
|
-
last_name: str = "",
|
|
252
|
-
role: str = "",
|
|
253
|
-
create_role: bool = False,
|
|
254
|
-
):
|
|
255
|
-
"""
|
|
256
|
-
Create an admin user.
|
|
257
|
-
"""
|
|
258
|
-
if role:
|
|
259
|
-
await _check_roles(role, create=create_role)
|
|
260
|
-
return await g.current_app.security.create_user(
|
|
261
|
-
email=email,
|
|
262
|
-
username=username,
|
|
263
|
-
password=password,
|
|
264
|
-
first_name=first_name,
|
|
265
|
-
last_name=last_name,
|
|
266
|
-
roles=[role] if role else None,
|
|
267
|
-
)
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
async def reset_password(email_or_username: str, password: str):
|
|
271
|
-
"""
|
|
272
|
-
Reset user password.
|
|
273
|
-
"""
|
|
274
|
-
user = await g.current_app.security.get_user(email_or_username)
|
|
275
|
-
return await g.current_app.security.reset_password(user, password)
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
async def export_data(
|
|
279
|
-
file_path: str,
|
|
280
|
-
data: Literal["users", "roles"],
|
|
281
|
-
type: Literal["json", "csv"] = "json",
|
|
282
|
-
):
|
|
283
|
-
"""
|
|
284
|
-
Export data.
|
|
285
|
-
"""
|
|
286
|
-
data = await g.current_app.security.export_data(data, type)
|
|
287
|
-
with open(file_path, "w") as f:
|
|
288
|
-
f.write(data)
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
async def cleanup():
|
|
292
|
-
"""
|
|
293
|
-
Cleanup unused permissions from apis and roles.
|
|
294
|
-
"""
|
|
295
|
-
await g.current_app.security.cleanup()
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: fastapi-rtk
|
|
3
|
-
Version: 0.2.27
|
|
4
|
-
Summary: A package that provides a set of tools to build a FastAPI application with a Class-Based CRUD API.
|
|
5
|
-
Author-email: Matthias Leinweber <m.leinweber@datatactics.de>, Agustinus Nicander Hery <a.hery@datatactics.de>
|
|
6
|
-
Project-URL: Homepage, https://github.com/dttctcs/fastapi-rtk
|
|
7
|
-
Project-URL: Issues, https://github.com/dttctcs/fastapi-rtk/issues
|
|
8
|
-
Classifier: Programming Language :: Python :: 3
|
|
9
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
-
Classifier: Operating System :: OS Independent
|
|
11
|
-
Requires-Python: >=3.10
|
|
12
|
-
Description-Content-Type: text/markdown
|
|
13
|
-
License-File: LICENSE
|
|
14
|
-
Requires-Dist: fastapi[standard]
|
|
15
|
-
Requires-Dist: fastapi-users[oauth,sqlalchemy]
|
|
16
|
-
Requires-Dist: sqlalchemy_utils
|
|
17
|
-
Requires-Dist: alembic
|
|
18
|
-
Requires-Dist: prometheus-fastapi-instrumentator
|
|
19
|
-
Requires-Dist: marshmallow-sqlalchemy
|
|
20
|
-
Requires-Dist: jsonschema2md
|
|
21
|
-
Requires-Dist: Secweb
|
|
22
|
-
Requires-Dist: beautifulsoup4
|
|
23
|
-
Dynamic: license-file
|