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,734 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
import typing
|
|
3
|
-
|
|
4
|
-
import fastapi
|
|
5
|
-
import gremlin_python.process.graph_traversal
|
|
6
|
-
import gremlin_python.process.traversal
|
|
7
|
-
|
|
8
|
-
from ...bases.db import DBQueryParams
|
|
9
|
-
from ...bases.filter import AbstractBaseFilter
|
|
10
|
-
from ...bases.interface import AbstractInterface
|
|
11
|
-
from ...const import logger
|
|
12
|
-
from ...exceptions import raise_exception
|
|
13
|
-
from ...utils import T, lazy_self, safe_call_sync, smart_run, use_default_when_none
|
|
14
|
-
from ..generic.interface import GenericInterface
|
|
15
|
-
from .column import GremlinColumn, GremlinRelationship, OnBeforeCreateParams
|
|
16
|
-
from .db import GremlinQueryBuilder
|
|
17
|
-
from .filters import GremlinFilterConverter
|
|
18
|
-
from .model import UNSPECIFIED_LABEL, GremlinModel
|
|
19
|
-
from .session import get_graph_traversal_factory
|
|
20
|
-
|
|
21
|
-
__all__ = ["GremlinInterface"]
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
class lazy(lazy_self["GremlinInterfaceMixin", T]): ...
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
class GremlinInterfaceMixin(
|
|
28
|
-
AbstractInterface[
|
|
29
|
-
GremlinModel,
|
|
30
|
-
gremlin_python.process.graph_traversal.GraphTraversalSource,
|
|
31
|
-
GremlinColumn | GremlinRelationship,
|
|
32
|
-
],
|
|
33
|
-
):
|
|
34
|
-
"""
|
|
35
|
-
Mixins for GremlinInterface
|
|
36
|
-
"""
|
|
37
|
-
|
|
38
|
-
filter_converter = GremlinFilterConverter()
|
|
39
|
-
global_filter_class = lazy(
|
|
40
|
-
lambda: raise_exception(
|
|
41
|
-
"global_filter_class for GremlinInterface is not yet available",
|
|
42
|
-
AbstractBaseFilter,
|
|
43
|
-
)
|
|
44
|
-
)
|
|
45
|
-
|
|
46
|
-
# Query builders
|
|
47
|
-
query = lazy(lambda self: GremlinQueryBuilder(self))
|
|
48
|
-
query_count = lazy(lambda self: GremlinQueryBuilder(self))
|
|
49
|
-
|
|
50
|
-
def __init__(self, obj, with_fk=True):
|
|
51
|
-
AbstractInterface.__init__(self, obj, with_fk)
|
|
52
|
-
|
|
53
|
-
"""
|
|
54
|
-
--------------------------------------------------------------------------------------------------------
|
|
55
|
-
DEPENDENCIES METHODS
|
|
56
|
-
--------------------------------------------------------------------------------------------------------
|
|
57
|
-
"""
|
|
58
|
-
|
|
59
|
-
def get_session_factory(self):
|
|
60
|
-
def get_graph_traversal_interface(
|
|
61
|
-
traversal=fastapi.Depends(get_graph_traversal_factory()),
|
|
62
|
-
):
|
|
63
|
-
self.obj.__session__ = traversal
|
|
64
|
-
return traversal
|
|
65
|
-
|
|
66
|
-
return get_graph_traversal_interface
|
|
67
|
-
|
|
68
|
-
def get_download_session_factory(self):
|
|
69
|
-
def get_graph_traversal_interface(
|
|
70
|
-
traversal=fastapi.Depends(get_graph_traversal_factory(True)),
|
|
71
|
-
):
|
|
72
|
-
self.obj.__session__ = traversal
|
|
73
|
-
return traversal
|
|
74
|
-
|
|
75
|
-
return get_graph_traversal_interface
|
|
76
|
-
|
|
77
|
-
"""
|
|
78
|
-
--------------------------------------------------------------------------------------------------------
|
|
79
|
-
COUNT AND CRUD METHODS
|
|
80
|
-
--------------------------------------------------------------------------------------------------------
|
|
81
|
-
"""
|
|
82
|
-
|
|
83
|
-
async def count(self, session, params=None) -> int:
|
|
84
|
-
relevant_params = params.copy() if params else DBQueryParams()
|
|
85
|
-
relevant_params.pop("list_columns", None)
|
|
86
|
-
relevant_params.pop("page", None)
|
|
87
|
-
relevant_params.pop("page_size", None)
|
|
88
|
-
relevant_params.pop("order_column", None)
|
|
89
|
-
relevant_params.pop("order_direction", None)
|
|
90
|
-
self.query_count.statement = self._init_traversal(session)
|
|
91
|
-
statement = await self.query_count.build_query(relevant_params)
|
|
92
|
-
return await smart_run(statement.count().next)
|
|
93
|
-
|
|
94
|
-
async def get_many(self, session, params=None) -> list[GremlinModel]:
|
|
95
|
-
self.query.statement = self._init_traversal(session)
|
|
96
|
-
statement = await self.query.build_query(params)
|
|
97
|
-
if "list_columns" not in params:
|
|
98
|
-
statement = self.query.apply_list_columns(statement, [])
|
|
99
|
-
result = await smart_run(statement.toList)
|
|
100
|
-
items = []
|
|
101
|
-
for res in result:
|
|
102
|
-
item = self._handle_data_from_gremlinpython(res, self._get_rel_parser())
|
|
103
|
-
items.append(item)
|
|
104
|
-
items = [
|
|
105
|
-
self.obj.from_gremlinpython(
|
|
106
|
-
item,
|
|
107
|
-
preserve_as_list=[
|
|
108
|
-
x
|
|
109
|
-
for x in self.get_relation_columns()
|
|
110
|
-
if self.list_properties[x].uselist
|
|
111
|
-
],
|
|
112
|
-
)
|
|
113
|
-
for item in items
|
|
114
|
-
]
|
|
115
|
-
return items
|
|
116
|
-
|
|
117
|
-
async def get_one(self, session, params=None):
|
|
118
|
-
self.query.statement = self._init_traversal(session)
|
|
119
|
-
statement = await self.query.build_query(params)
|
|
120
|
-
if "list_columns" not in params:
|
|
121
|
-
statement = self.query.apply_list_columns(statement, [])
|
|
122
|
-
has_next = await smart_run(statement.hasNext)
|
|
123
|
-
result = await smart_run(statement.next) if has_next else None
|
|
124
|
-
if result is not None:
|
|
125
|
-
return self.obj.from_gremlinpython(
|
|
126
|
-
self._handle_data_from_gremlinpython(result, self._get_rel_parser()),
|
|
127
|
-
preserve_as_list=[
|
|
128
|
-
x
|
|
129
|
-
for x in self.get_relation_columns()
|
|
130
|
-
if self.list_properties[x].uselist
|
|
131
|
-
],
|
|
132
|
-
)
|
|
133
|
-
|
|
134
|
-
async def yield_per(self, session, params=None):
|
|
135
|
-
relevant_params = params.copy() if params else DBQueryParams()
|
|
136
|
-
relevant_params.pop("page", None)
|
|
137
|
-
page_size = relevant_params.pop("page_size", 100)
|
|
138
|
-
items = await self.get_many(session, relevant_params)
|
|
139
|
-
while True:
|
|
140
|
-
chunk = items[:page_size]
|
|
141
|
-
items = items[page_size:]
|
|
142
|
-
if not chunk:
|
|
143
|
-
break
|
|
144
|
-
yield chunk
|
|
145
|
-
await smart_run(self.close, session)
|
|
146
|
-
|
|
147
|
-
def add(self, session, item, *, flush=True, commit=True, refresh=True):
|
|
148
|
-
original_session = session
|
|
149
|
-
callbacks: list[typing.Callable] = []
|
|
150
|
-
statement = self._init_traversal(session, type="add")
|
|
151
|
-
data = item.to_gremlinpython()
|
|
152
|
-
for key, value in data.items():
|
|
153
|
-
if value is None:
|
|
154
|
-
continue
|
|
155
|
-
elif key is gremlin_python.process.traversal.T.label and item.__label__:
|
|
156
|
-
continue
|
|
157
|
-
elif self.is_relation(key):
|
|
158
|
-
value = json.loads(value) if value else []
|
|
159
|
-
if not isinstance(value, list):
|
|
160
|
-
value = [value]
|
|
161
|
-
model_cls = self.list_properties[key].obj
|
|
162
|
-
for index, val in enumerate(value):
|
|
163
|
-
source = data[gremlin_python.process.traversal.T.id]
|
|
164
|
-
target = val[model_cls.__mapper__.pk]
|
|
165
|
-
properties = self.list_properties[key].properties
|
|
166
|
-
if self.list_properties[key].direction != "out":
|
|
167
|
-
source, target = target, source
|
|
168
|
-
if self.list_properties[key].on_before_create:
|
|
169
|
-
source_model = item
|
|
170
|
-
target_model = getattr(item, key)[index]
|
|
171
|
-
if self.list_properties[key].direction != "out":
|
|
172
|
-
source_model, target_model = target_model, source_model
|
|
173
|
-
params = OnBeforeCreateParams(
|
|
174
|
-
session=session,
|
|
175
|
-
source=source,
|
|
176
|
-
target=target,
|
|
177
|
-
edge_name=self.list_properties[key].name,
|
|
178
|
-
properties=self.list_properties[key].properties,
|
|
179
|
-
)
|
|
180
|
-
additional_properties = safe_call_sync(
|
|
181
|
-
self.list_properties[key].on_before_create(
|
|
182
|
-
source_model, target_model, params
|
|
183
|
-
)
|
|
184
|
-
)
|
|
185
|
-
if additional_properties:
|
|
186
|
-
properties = {**(properties or {}), **additional_properties}
|
|
187
|
-
callbacks.append(
|
|
188
|
-
lambda s,
|
|
189
|
-
source=source,
|
|
190
|
-
target=target,
|
|
191
|
-
edge_name=self.list_properties[key].name,
|
|
192
|
-
properties=properties: self.create_edge(
|
|
193
|
-
s, source, target, edge_name, properties
|
|
194
|
-
)
|
|
195
|
-
)
|
|
196
|
-
continue
|
|
197
|
-
statement = statement.property(key, value)
|
|
198
|
-
item = item.from_gremlinpython(statement.valueMap(True).next())
|
|
199
|
-
for callback in callbacks:
|
|
200
|
-
callback(original_session)
|
|
201
|
-
return item
|
|
202
|
-
|
|
203
|
-
def edit(self, session, item, *, flush=True, commit=True, refresh=False):
|
|
204
|
-
original_session = session
|
|
205
|
-
callbacks: list[typing.Callable] = []
|
|
206
|
-
data = item.to_gremlinpython()
|
|
207
|
-
statement = self._init_traversal(session, item=item)
|
|
208
|
-
for key, value in data.items():
|
|
209
|
-
if key in self.get_pk_attrs():
|
|
210
|
-
raise ValueError(
|
|
211
|
-
"Primary key cannot be updated in Gremlin. Use a new instance instead."
|
|
212
|
-
)
|
|
213
|
-
elif value is None and not self.is_relation(key):
|
|
214
|
-
statement = statement.properties(key).drop()
|
|
215
|
-
continue
|
|
216
|
-
elif (
|
|
217
|
-
key is gremlin_python.process.traversal.T.id
|
|
218
|
-
or key is gremlin_python.process.traversal.T.label
|
|
219
|
-
):
|
|
220
|
-
continue
|
|
221
|
-
elif self.is_relation(key):
|
|
222
|
-
value = json.loads(value) if value else []
|
|
223
|
-
if not isinstance(value, list):
|
|
224
|
-
value = [value]
|
|
225
|
-
model_cls = self.list_properties[key].obj
|
|
226
|
-
direction = self.list_properties[key].direction
|
|
227
|
-
targets = [val[model_cls.__mapper__.pk] for val in value]
|
|
228
|
-
properties_mapping = {}
|
|
229
|
-
if self.list_properties[key].on_before_create:
|
|
230
|
-
for target_model in getattr(item, key, []):
|
|
231
|
-
source_model = item
|
|
232
|
-
if direction != "out":
|
|
233
|
-
source_model, target_model = target_model, source_model
|
|
234
|
-
params = OnBeforeCreateParams(
|
|
235
|
-
session=session,
|
|
236
|
-
source=source_model.get_pk(),
|
|
237
|
-
target=target_model.get_pk(),
|
|
238
|
-
edge_name=self.list_properties[key].name,
|
|
239
|
-
properties=self.list_properties[key].properties,
|
|
240
|
-
)
|
|
241
|
-
additional_properties = safe_call_sync(
|
|
242
|
-
self.list_properties[key].on_before_create(
|
|
243
|
-
source_model, target_model, params
|
|
244
|
-
)
|
|
245
|
-
)
|
|
246
|
-
if additional_properties:
|
|
247
|
-
properties_mapping[target_model.get_pk()] = (
|
|
248
|
-
additional_properties
|
|
249
|
-
)
|
|
250
|
-
callbacks.append(
|
|
251
|
-
lambda s,
|
|
252
|
-
source=item.get_pk(),
|
|
253
|
-
targets=targets,
|
|
254
|
-
edge_name=self.list_properties[key].name,
|
|
255
|
-
properties=self.list_properties[key].properties,
|
|
256
|
-
direction=direction,
|
|
257
|
-
properties_mapping=properties_mapping: self.sync_edges(
|
|
258
|
-
s,
|
|
259
|
-
source,
|
|
260
|
-
targets,
|
|
261
|
-
edge_name,
|
|
262
|
-
properties,
|
|
263
|
-
direction,
|
|
264
|
-
properties_mapping=properties_mapping,
|
|
265
|
-
)
|
|
266
|
-
)
|
|
267
|
-
continue
|
|
268
|
-
statement = statement.property(key, value)
|
|
269
|
-
item = item.from_gremlinpython(statement.valueMap(True).next())
|
|
270
|
-
for callback in callbacks:
|
|
271
|
-
callback(original_session)
|
|
272
|
-
return item
|
|
273
|
-
|
|
274
|
-
def delete(self, session, item, *, flush=True, commit=True):
|
|
275
|
-
statement = self._init_traversal(session, item=item)
|
|
276
|
-
statement.drop().iterate()
|
|
277
|
-
|
|
278
|
-
"""
|
|
279
|
-
--------------------------------------------------------------------------------------------------------
|
|
280
|
-
SESSION METHODS - to be implemented
|
|
281
|
-
--------------------------------------------------------------------------------------------------------
|
|
282
|
-
"""
|
|
283
|
-
|
|
284
|
-
async def commit(self, session):
|
|
285
|
-
pass
|
|
286
|
-
|
|
287
|
-
def close(self, session):
|
|
288
|
-
session.remote_connection.close()
|
|
289
|
-
logger.info(f"Connection {session.remote_connection} closed.")
|
|
290
|
-
|
|
291
|
-
"""
|
|
292
|
-
--------------------------------------------------------------------------------------------------------
|
|
293
|
-
GET METHODS
|
|
294
|
-
--------------------------------------------------------------------------------------------------------
|
|
295
|
-
"""
|
|
296
|
-
|
|
297
|
-
def get_add_column_list(self):
|
|
298
|
-
return [
|
|
299
|
-
x
|
|
300
|
-
for x in self.get_user_column_list()
|
|
301
|
-
if x != self.obj.__mapper__.lk and self.is_valid_column(x)
|
|
302
|
-
]
|
|
303
|
-
|
|
304
|
-
def get_edit_column_list(self):
|
|
305
|
-
return [
|
|
306
|
-
x
|
|
307
|
-
for x in self.get_user_column_list()
|
|
308
|
-
if x != self.obj.__mapper__.lk and self.is_valid_column(x)
|
|
309
|
-
]
|
|
310
|
-
|
|
311
|
-
def get_order_column_list(self, list_columns):
|
|
312
|
-
return [x for x in list_columns if x in self.list_columns]
|
|
313
|
-
|
|
314
|
-
def get_search_column_list(self, list_columns):
|
|
315
|
-
return [x for x in list_columns if x in self.list_properties]
|
|
316
|
-
|
|
317
|
-
def get_type_name(self, col):
|
|
318
|
-
if self.is_relation_one_to_one(col) or self.is_relation_many_to_one(col):
|
|
319
|
-
return "Related"
|
|
320
|
-
elif self.is_relation_one_to_many(col) or self.is_relation_many_to_many(col):
|
|
321
|
-
return "RelatedList"
|
|
322
|
-
return super().get_type_name(col)
|
|
323
|
-
|
|
324
|
-
async def get_column_info(
|
|
325
|
-
self,
|
|
326
|
-
col,
|
|
327
|
-
session,
|
|
328
|
-
session_count,
|
|
329
|
-
*,
|
|
330
|
-
params=None,
|
|
331
|
-
description_columns=None,
|
|
332
|
-
label_columns=None,
|
|
333
|
-
):
|
|
334
|
-
if self.is_relation(col) and self.list_properties[col].obj_properties:
|
|
335
|
-
params = params or DBQueryParams()
|
|
336
|
-
params["where"] = params.get("where", [])
|
|
337
|
-
if not isinstance(params["where"], list):
|
|
338
|
-
params["where"] = [params["where"]]
|
|
339
|
-
for key, value in self.list_properties[col].obj_properties.items():
|
|
340
|
-
params["where"].append((key, value))
|
|
341
|
-
|
|
342
|
-
return await super().get_column_info(
|
|
343
|
-
col,
|
|
344
|
-
session,
|
|
345
|
-
session_count,
|
|
346
|
-
params=params,
|
|
347
|
-
description_columns=description_columns,
|
|
348
|
-
label_columns=label_columns,
|
|
349
|
-
)
|
|
350
|
-
|
|
351
|
-
"""
|
|
352
|
-
--------------------------------------------------------------------------------------------------------
|
|
353
|
-
LIFESPAN METHODS
|
|
354
|
-
--------------------------------------------------------------------------------------------------------
|
|
355
|
-
"""
|
|
356
|
-
|
|
357
|
-
async def on_shutdown(self):
|
|
358
|
-
pass
|
|
359
|
-
|
|
360
|
-
"""
|
|
361
|
-
--------------------------------------------------------------------------------------------------------
|
|
362
|
-
RELATED MODEL METHODS
|
|
363
|
-
--------------------------------------------------------------------------------------------------------
|
|
364
|
-
"""
|
|
365
|
-
|
|
366
|
-
def get_related_model(self, col_name):
|
|
367
|
-
if self.is_relation(col_name):
|
|
368
|
-
return self.list_properties[col_name].obj
|
|
369
|
-
|
|
370
|
-
raise ValueError(f"{col_name} is not a relation")
|
|
371
|
-
|
|
372
|
-
"""
|
|
373
|
-
--------------------------------------------------------------------------------------------------------
|
|
374
|
-
IS METHODS
|
|
375
|
-
--------------------------------------------------------------------------------------------------------
|
|
376
|
-
"""
|
|
377
|
-
|
|
378
|
-
def is_relation(self, col_name):
|
|
379
|
-
try:
|
|
380
|
-
return (
|
|
381
|
-
col_name in self.get_relation_columns()
|
|
382
|
-
and self.list_properties[col_name].obj is not None
|
|
383
|
-
and self.list_properties[col_name].direction != "both"
|
|
384
|
-
)
|
|
385
|
-
except Exception:
|
|
386
|
-
return False
|
|
387
|
-
|
|
388
|
-
def is_relation_one_to_one(self, col_name):
|
|
389
|
-
if not self.is_relation(col_name):
|
|
390
|
-
return False
|
|
391
|
-
try:
|
|
392
|
-
return not self.list_properties[col_name].uselist
|
|
393
|
-
except Exception:
|
|
394
|
-
return False
|
|
395
|
-
|
|
396
|
-
def is_relation_one_to_many(self, col_name):
|
|
397
|
-
if not self.is_relation(col_name):
|
|
398
|
-
return False
|
|
399
|
-
try:
|
|
400
|
-
return self.list_properties[col_name].uselist
|
|
401
|
-
except Exception:
|
|
402
|
-
return False
|
|
403
|
-
|
|
404
|
-
def is_relation_many_to_one(self, col_name):
|
|
405
|
-
return self.is_relation_one_to_one(col_name)
|
|
406
|
-
|
|
407
|
-
def is_relation_many_to_many(self, col_name):
|
|
408
|
-
return self.is_relation_one_to_many(col_name)
|
|
409
|
-
|
|
410
|
-
"""
|
|
411
|
-
--------------------------------------------------------------------------------------------------------
|
|
412
|
-
IS METHODS - ONLY IN GremlinInterface
|
|
413
|
-
--------------------------------------------------------------------------------------------------------
|
|
414
|
-
"""
|
|
415
|
-
|
|
416
|
-
def is_valid_column(self, col_name: str):
|
|
417
|
-
"""
|
|
418
|
-
Checks if the given column name is a valid column for the Gremlin model.
|
|
419
|
-
|
|
420
|
-
- If the column is an instance of GremlinColumn and not a GremlinRelationship, it is valid.
|
|
421
|
-
- If the column is a GremlinRelationship and it is a relation, it is valid.
|
|
422
|
-
|
|
423
|
-
Args:
|
|
424
|
-
col_name (str): The name of the column to check.
|
|
425
|
-
|
|
426
|
-
Returns:
|
|
427
|
-
bool: True if the column is valid, False otherwise.
|
|
428
|
-
"""
|
|
429
|
-
col = self.list_properties[col_name]
|
|
430
|
-
if isinstance(col, GremlinColumn) and not isinstance(col, GremlinRelationship):
|
|
431
|
-
return True
|
|
432
|
-
elif isinstance(col, GremlinRelationship) and self.is_relation(col_name):
|
|
433
|
-
return True
|
|
434
|
-
return False
|
|
435
|
-
|
|
436
|
-
"""
|
|
437
|
-
--------------------------------------------------------------------------------------------------------
|
|
438
|
-
GET METHODS - ONLY IN GremlinInterface
|
|
439
|
-
--------------------------------------------------------------------------------------------------------
|
|
440
|
-
"""
|
|
441
|
-
|
|
442
|
-
def get_relation_columns(self):
|
|
443
|
-
return [
|
|
444
|
-
k
|
|
445
|
-
for k, v in self.list_properties.items()
|
|
446
|
-
if isinstance(v, GremlinRelationship)
|
|
447
|
-
]
|
|
448
|
-
|
|
449
|
-
"""
|
|
450
|
-
--------------------------------------------------------------------------------------------------------
|
|
451
|
-
HELPER METHODS
|
|
452
|
-
--------------------------------------------------------------------------------------------------------
|
|
453
|
-
"""
|
|
454
|
-
|
|
455
|
-
@staticmethod
|
|
456
|
-
def get_edges(
|
|
457
|
-
session: gremlin_python.process.graph_traversal.GraphTraversalSource,
|
|
458
|
-
source: str,
|
|
459
|
-
edge_name: str,
|
|
460
|
-
properties: dict[str, typing.Any] | None = None,
|
|
461
|
-
direction: typing.Literal["out", "in", "both"] = "out",
|
|
462
|
-
) -> list[dict[str, typing.Any]]:
|
|
463
|
-
"""
|
|
464
|
-
Retrieves edges from a vertex in the Gremlin graph.
|
|
465
|
-
|
|
466
|
-
Args:
|
|
467
|
-
session (GraphTraversalSource): The Gremlin session to use for the operation.
|
|
468
|
-
source (str): The ID of the source vertex.
|
|
469
|
-
edge_name (str): The name of the edge to retrieve.
|
|
470
|
-
properties (dict[str, typing.Any], optional): Properties to filter the edges.
|
|
471
|
-
direction (str, optional): The direction of the edges. Can be 'out', 'in', or 'both'. Defaults to 'out'.
|
|
472
|
-
|
|
473
|
-
Returns:
|
|
474
|
-
list[dict[str, typing.Any]]: A list of edges with their properties.
|
|
475
|
-
"""
|
|
476
|
-
traversal = session.V(source)
|
|
477
|
-
if direction == "out":
|
|
478
|
-
traversal = traversal.outE(edge_name)
|
|
479
|
-
elif direction == "in":
|
|
480
|
-
traversal = traversal.inE(edge_name)
|
|
481
|
-
elif direction == "both":
|
|
482
|
-
traversal = traversal.bothE(edge_name)
|
|
483
|
-
else:
|
|
484
|
-
raise ValueError("Direction must be 'out', 'in', or 'both'.")
|
|
485
|
-
if properties:
|
|
486
|
-
for key, value in properties.items():
|
|
487
|
-
traversal = traversal.has(key, value)
|
|
488
|
-
edges = traversal.valueMap(True).toList()
|
|
489
|
-
return [
|
|
490
|
-
GremlinModel.parse_from_gremlinpython({"edge": x})["edge"] for x in edges
|
|
491
|
-
]
|
|
492
|
-
|
|
493
|
-
@staticmethod
|
|
494
|
-
def create_edge(
|
|
495
|
-
session: gremlin_python.process.graph_traversal.GraphTraversalSource,
|
|
496
|
-
source: str,
|
|
497
|
-
target: str,
|
|
498
|
-
edge_name: str,
|
|
499
|
-
properties: dict[str, typing.Any] | None = None,
|
|
500
|
-
):
|
|
501
|
-
"""
|
|
502
|
-
Creates an edge between two vertices in the Gremlin graph.
|
|
503
|
-
|
|
504
|
-
Args:
|
|
505
|
-
session (GraphTraversalSource): The Gremlin session to use for the operation.
|
|
506
|
-
source (str): The ID of the source vertex.
|
|
507
|
-
target (str): The ID of the target vertex.
|
|
508
|
-
edge_name (str): The name of the edge to create.
|
|
509
|
-
properties (dict[str, typing.Any], optional): Properties to set on the edge.
|
|
510
|
-
|
|
511
|
-
Returns:
|
|
512
|
-
None
|
|
513
|
-
"""
|
|
514
|
-
traversal = (
|
|
515
|
-
session.V(source)
|
|
516
|
-
.addE(edge_name)
|
|
517
|
-
.to(gremlin_python.process.graph_traversal.__.V(target))
|
|
518
|
-
)
|
|
519
|
-
if properties:
|
|
520
|
-
for key, value in properties.items():
|
|
521
|
-
traversal = traversal.property(key, value)
|
|
522
|
-
traversal.iterate()
|
|
523
|
-
|
|
524
|
-
@staticmethod
|
|
525
|
-
def delete_edge(
|
|
526
|
-
session: gremlin_python.process.graph_traversal.GraphTraversalSource,
|
|
527
|
-
source: str,
|
|
528
|
-
target: str,
|
|
529
|
-
edge_name: str,
|
|
530
|
-
properties: dict[str, typing.Any] | None = None,
|
|
531
|
-
):
|
|
532
|
-
"""
|
|
533
|
-
Deletes an edge between two vertices in the Gremlin graph.
|
|
534
|
-
|
|
535
|
-
Args:
|
|
536
|
-
session (GraphTraversalSource): The Gremlin session to use for the operation.
|
|
537
|
-
source (str): The ID of the source vertex.
|
|
538
|
-
target (str): The ID of the target vertex.
|
|
539
|
-
edge_name (str): The name of the edge to delete.
|
|
540
|
-
properties (dict[str, typing.Any], optional): Properties to filter the edge to delete.
|
|
541
|
-
|
|
542
|
-
Returns:
|
|
543
|
-
None
|
|
544
|
-
"""
|
|
545
|
-
traversal = (
|
|
546
|
-
session.V(source)
|
|
547
|
-
.outE(edge_name)
|
|
548
|
-
.where(gremlin_python.process.graph_traversal.__.inV().hasId(target))
|
|
549
|
-
)
|
|
550
|
-
if properties:
|
|
551
|
-
for key, value in properties.items():
|
|
552
|
-
traversal = traversal.has(key, value)
|
|
553
|
-
traversal.drop().iterate()
|
|
554
|
-
|
|
555
|
-
@staticmethod
|
|
556
|
-
def delete_edge_by_id(
|
|
557
|
-
session: gremlin_python.process.graph_traversal.GraphTraversalSource,
|
|
558
|
-
edge_id: str,
|
|
559
|
-
):
|
|
560
|
-
"""
|
|
561
|
-
Deletes an edge by its ID in the Gremlin graph.
|
|
562
|
-
|
|
563
|
-
Args:
|
|
564
|
-
session (GraphTraversalSource): The Gremlin session to use for the operation.
|
|
565
|
-
edge_id (str): The ID of the edge to delete.
|
|
566
|
-
|
|
567
|
-
Returns:
|
|
568
|
-
None
|
|
569
|
-
"""
|
|
570
|
-
session.E(edge_id).drop().iterate()
|
|
571
|
-
|
|
572
|
-
@staticmethod
|
|
573
|
-
def sync_edges(
|
|
574
|
-
session: gremlin_python.process.graph_traversal.GraphTraversalSource,
|
|
575
|
-
source: str,
|
|
576
|
-
targets: list[str],
|
|
577
|
-
edge_name: str,
|
|
578
|
-
properties: dict[str, typing.Any] | None = None,
|
|
579
|
-
direction: typing.Literal["out", "in"] = "out",
|
|
580
|
-
*,
|
|
581
|
-
properties_mapping: dict[str, dict[str, typing.Any]] | None = None,
|
|
582
|
-
):
|
|
583
|
-
"""
|
|
584
|
-
Synchronizes edges between a source vertex and multiple target vertices in the Gremlin graph.
|
|
585
|
-
|
|
586
|
-
This method will delete edges that exist in the source but not in the targets,
|
|
587
|
-
and create edges for targets that do not have an edge from the source.
|
|
588
|
-
|
|
589
|
-
Args:
|
|
590
|
-
session (GraphTraversalSource): The Gremlin session to use for the operation.
|
|
591
|
-
source (str): The ID of the source vertex.
|
|
592
|
-
targets (list[str]): A list of IDs of target vertices.
|
|
593
|
-
edge_name (str): The name of the edge to synchronize.
|
|
594
|
-
properties (dict[str, typing.Any], optional): Properties to set on the edges.
|
|
595
|
-
direction (str, optional): The direction of the edges. Can be 'out' or 'in'. Defaults to 'out'.
|
|
596
|
-
|
|
597
|
-
Returns:
|
|
598
|
-
None
|
|
599
|
-
"""
|
|
600
|
-
if direction not in ["out", "in"]:
|
|
601
|
-
raise ValueError("Direction must be 'out' or 'in'.")
|
|
602
|
-
existing_edges = GremlinInterface.get_edges(
|
|
603
|
-
session, source, edge_name, properties, direction
|
|
604
|
-
)
|
|
605
|
-
key = "target" if direction == "out" else "source"
|
|
606
|
-
to_be_deleted = [
|
|
607
|
-
edge["id"] for edge in existing_edges if edge[key] not in targets
|
|
608
|
-
]
|
|
609
|
-
to_be_created = [
|
|
610
|
-
target
|
|
611
|
-
for target in targets
|
|
612
|
-
if target not in [edge[key] for edge in existing_edges]
|
|
613
|
-
]
|
|
614
|
-
for edge_id in to_be_deleted:
|
|
615
|
-
GremlinInterface.delete_edge_by_id(session, edge_id)
|
|
616
|
-
for target in to_be_created:
|
|
617
|
-
GremlinInterface.create_edge(
|
|
618
|
-
session,
|
|
619
|
-
source if key == "target" else target,
|
|
620
|
-
target if key == "target" else source,
|
|
621
|
-
edge_name,
|
|
622
|
-
{
|
|
623
|
-
**(properties or {}),
|
|
624
|
-
**(
|
|
625
|
-
properties_mapping.get(target, {}) if properties_mapping else {}
|
|
626
|
-
),
|
|
627
|
-
},
|
|
628
|
-
)
|
|
629
|
-
|
|
630
|
-
def _get_rel_parser(self):
|
|
631
|
-
"""
|
|
632
|
-
Returns a dictionary of relation parsers for the current object.
|
|
633
|
-
The keys are the relation column names, and the values are the corresponding GremlinModel parsers.
|
|
634
|
-
"""
|
|
635
|
-
return {
|
|
636
|
-
k: self.list_properties[k].obj
|
|
637
|
-
for k in [x for x in self.list_properties if self.is_relation(x)]
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
def _handle_data_from_gremlinpython(
|
|
641
|
-
self, data: dict[str, typing.Any], rel_parser: dict[str, GremlinModel]
|
|
642
|
-
):
|
|
643
|
-
"""
|
|
644
|
-
Handles the data returned from Gremlin Python and converts it to a dictionary suitable for the model.
|
|
645
|
-
|
|
646
|
-
Args:
|
|
647
|
-
data (dict[str, typing.Any]): The data returned from Gremlin Python.
|
|
648
|
-
rel_parser (dict[str, GremlinModel]): A dictionary of relation parsers.
|
|
649
|
-
|
|
650
|
-
Returns:
|
|
651
|
-
dict[str, typing.Any]: A dictionary with the data formatted for the model.
|
|
652
|
-
"""
|
|
653
|
-
item: dict[str, typing.Any] = data[self.obj.__label__ or UNSPECIFIED_LABEL]
|
|
654
|
-
for key, value in data.items():
|
|
655
|
-
if key == (self.obj.__label__ or UNSPECIFIED_LABEL):
|
|
656
|
-
continue
|
|
657
|
-
if self.list_properties[key].uselist:
|
|
658
|
-
if not isinstance(value, list):
|
|
659
|
-
value = [value]
|
|
660
|
-
else:
|
|
661
|
-
if isinstance(value, list):
|
|
662
|
-
value = value[0] if value else None
|
|
663
|
-
|
|
664
|
-
if value is None:
|
|
665
|
-
continue
|
|
666
|
-
parser = rel_parser.get(key, None)
|
|
667
|
-
|
|
668
|
-
def func(data):
|
|
669
|
-
func = self.obj.parse_from_gremlinpython
|
|
670
|
-
if parser:
|
|
671
|
-
func = parser.from_gremlinpython
|
|
672
|
-
if self.list_properties[key].with_edge:
|
|
673
|
-
data = {**data[key], "edge": data["edge"]}
|
|
674
|
-
result = func(
|
|
675
|
-
data,
|
|
676
|
-
preserve_as_list=[key]
|
|
677
|
-
if self.list_properties[key].uselist
|
|
678
|
-
else None,
|
|
679
|
-
)
|
|
680
|
-
if parser:
|
|
681
|
-
result = result.to_json()
|
|
682
|
-
return result
|
|
683
|
-
|
|
684
|
-
item[key] = (
|
|
685
|
-
[func(data) for data in value]
|
|
686
|
-
if self.list_properties[key].uselist
|
|
687
|
-
else func(value)
|
|
688
|
-
)
|
|
689
|
-
return item
|
|
690
|
-
|
|
691
|
-
def _init_traversal(
|
|
692
|
-
self,
|
|
693
|
-
session: gremlin_python.process.graph_traversal.GraphTraversalSource,
|
|
694
|
-
*,
|
|
695
|
-
type: typing.Literal["get", "add"] = "get",
|
|
696
|
-
item: GremlinModel | None = None,
|
|
697
|
-
):
|
|
698
|
-
"""
|
|
699
|
-
Initializes a Gremlin traversal for the given session and item.
|
|
700
|
-
|
|
701
|
-
Args:
|
|
702
|
-
session (gremlin_python.process.graph_traversal.GraphTraversalSource): The Gremlin session to use for the traversal.
|
|
703
|
-
type (typing.Literal["get", "add"], optional): The type of traversal to initialize. Can be either "get" or "add". Defaults to "get".
|
|
704
|
-
item (GremlinModel | None, optional): The item to use for the traversal. If None, the default object of the interface will be used. Defaults to None.
|
|
705
|
-
|
|
706
|
-
Returns:
|
|
707
|
-
gremlin_python.process.graph_traversal.GraphTraversal: The initialized traversal.
|
|
708
|
-
"""
|
|
709
|
-
item = use_default_when_none(item, self.obj)
|
|
710
|
-
traversal = session
|
|
711
|
-
if type == "get":
|
|
712
|
-
if item is self.obj:
|
|
713
|
-
traversal = traversal.V()
|
|
714
|
-
else:
|
|
715
|
-
traversal = traversal.V(item.get_pk())
|
|
716
|
-
if item.__label__:
|
|
717
|
-
traversal = traversal.hasLabel(item.__label__)
|
|
718
|
-
if item.__properties__:
|
|
719
|
-
for key, value in item.__properties__.items():
|
|
720
|
-
traversal = traversal.has(key, value)
|
|
721
|
-
elif type == "add":
|
|
722
|
-
traversal = (
|
|
723
|
-
traversal.addV(item.__label__) if item.__label__ else traversal.addV()
|
|
724
|
-
)
|
|
725
|
-
if item.__properties__:
|
|
726
|
-
for key, value in item.__properties__.items():
|
|
727
|
-
traversal = traversal.property(key, value)
|
|
728
|
-
return traversal
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
class GremlinInterface(GremlinInterfaceMixin, GenericInterface):
|
|
732
|
-
"""
|
|
733
|
-
Represents an interface for a Gremlin model.
|
|
734
|
-
"""
|