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.
Files changed (98) hide show
  1. fastapi_rtk/__init__.py +39 -35
  2. fastapi_rtk/_version.py +1 -0
  3. fastapi_rtk/api/model_rest_api.py +476 -221
  4. fastapi_rtk/auth/auth.py +0 -9
  5. fastapi_rtk/backends/generic/__init__.py +6 -0
  6. fastapi_rtk/backends/generic/column.py +21 -12
  7. fastapi_rtk/backends/generic/db.py +42 -7
  8. fastapi_rtk/backends/generic/filters.py +21 -16
  9. fastapi_rtk/backends/generic/interface.py +14 -8
  10. fastapi_rtk/backends/generic/model.py +19 -11
  11. fastapi_rtk/backends/sqla/__init__.py +1 -0
  12. fastapi_rtk/backends/sqla/db.py +77 -17
  13. fastapi_rtk/backends/sqla/extensions/audit/audit.py +401 -189
  14. fastapi_rtk/backends/sqla/extensions/geoalchemy2/filters.py +15 -12
  15. fastapi_rtk/backends/sqla/filters.py +50 -21
  16. fastapi_rtk/backends/sqla/interface.py +96 -34
  17. fastapi_rtk/backends/sqla/model.py +56 -39
  18. fastapi_rtk/bases/__init__.py +20 -0
  19. fastapi_rtk/bases/db.py +94 -7
  20. fastapi_rtk/bases/file_manager.py +47 -3
  21. fastapi_rtk/bases/filter.py +22 -0
  22. fastapi_rtk/bases/interface.py +49 -5
  23. fastapi_rtk/bases/model.py +3 -0
  24. fastapi_rtk/bases/session.py +2 -0
  25. fastapi_rtk/cli/cli.py +62 -9
  26. fastapi_rtk/cli/commands/__init__.py +23 -0
  27. fastapi_rtk/cli/{db.py → commands/db/__init__.py} +107 -50
  28. fastapi_rtk/cli/{templates → commands/db/templates}/fastapi/env.py +2 -3
  29. fastapi_rtk/cli/{templates → commands/db/templates}/fastapi-multidb/env.py +10 -9
  30. fastapi_rtk/cli/{templates → commands/db/templates}/fastapi-multidb/script.py.mako +3 -1
  31. fastapi_rtk/cli/{export.py → commands/export.py} +12 -10
  32. fastapi_rtk/cli/{security.py → commands/security.py} +73 -7
  33. fastapi_rtk/cli/commands/translate.py +299 -0
  34. fastapi_rtk/cli/decorators.py +9 -4
  35. fastapi_rtk/cli/utils.py +46 -0
  36. fastapi_rtk/config.py +41 -1
  37. fastapi_rtk/const.py +29 -1
  38. fastapi_rtk/db.py +76 -40
  39. fastapi_rtk/decorators.py +1 -1
  40. fastapi_rtk/dependencies.py +134 -62
  41. fastapi_rtk/exceptions.py +51 -1
  42. fastapi_rtk/fastapi_react_toolkit.py +186 -171
  43. fastapi_rtk/file_managers/file_manager.py +8 -6
  44. fastapi_rtk/file_managers/s3_file_manager.py +69 -33
  45. fastapi_rtk/globals.py +22 -12
  46. fastapi_rtk/lang/__init__.py +3 -0
  47. fastapi_rtk/lang/babel/__init__.py +4 -0
  48. fastapi_rtk/lang/babel/cli.py +40 -0
  49. fastapi_rtk/lang/babel/config.py +17 -0
  50. fastapi_rtk/lang/babel.cfg +1 -0
  51. fastapi_rtk/lang/lazy_text.py +120 -0
  52. fastapi_rtk/lang/messages.pot +238 -0
  53. fastapi_rtk/lang/translations/de/LC_MESSAGES/messages.mo +0 -0
  54. fastapi_rtk/lang/translations/de/LC_MESSAGES/messages.po +248 -0
  55. fastapi_rtk/lang/translations/en/LC_MESSAGES/messages.mo +0 -0
  56. fastapi_rtk/lang/translations/en/LC_MESSAGES/messages.po +244 -0
  57. fastapi_rtk/manager.py +355 -37
  58. fastapi_rtk/mixins.py +12 -0
  59. fastapi_rtk/routers.py +208 -72
  60. fastapi_rtk/schemas.py +142 -39
  61. fastapi_rtk/security/sqla/apis.py +39 -13
  62. fastapi_rtk/security/sqla/models.py +8 -23
  63. fastapi_rtk/security/sqla/security_manager.py +369 -11
  64. fastapi_rtk/setting.py +446 -88
  65. fastapi_rtk/types.py +94 -27
  66. fastapi_rtk/utils/__init__.py +8 -0
  67. fastapi_rtk/utils/async_task_runner.py +286 -61
  68. fastapi_rtk/utils/csv_json_converter.py +243 -40
  69. fastapi_rtk/utils/hooks.py +34 -0
  70. fastapi_rtk/utils/merge_schema.py +3 -3
  71. fastapi_rtk/utils/multiple_async_contexts.py +21 -0
  72. fastapi_rtk/utils/pydantic.py +46 -1
  73. fastapi_rtk/utils/run_utils.py +31 -1
  74. fastapi_rtk/utils/self_dependencies.py +1 -1
  75. fastapi_rtk/utils/use_default_when_none.py +1 -1
  76. fastapi_rtk/version.py +6 -1
  77. fastapi_rtk-1.0.13.dist-info/METADATA +28 -0
  78. fastapi_rtk-1.0.13.dist-info/RECORD +133 -0
  79. {fastapi_rtk-0.2.27.dist-info → fastapi_rtk-1.0.13.dist-info}/WHEEL +1 -2
  80. fastapi_rtk/backends/gremlinpython/__init__.py +0 -108
  81. fastapi_rtk/backends/gremlinpython/column.py +0 -208
  82. fastapi_rtk/backends/gremlinpython/db.py +0 -228
  83. fastapi_rtk/backends/gremlinpython/exceptions.py +0 -34
  84. fastapi_rtk/backends/gremlinpython/filters.py +0 -461
  85. fastapi_rtk/backends/gremlinpython/interface.py +0 -734
  86. fastapi_rtk/backends/gremlinpython/model.py +0 -364
  87. fastapi_rtk/backends/gremlinpython/session.py +0 -23
  88. fastapi_rtk/cli/commands.py +0 -295
  89. fastapi_rtk-0.2.27.dist-info/METADATA +0 -23
  90. fastapi_rtk-0.2.27.dist-info/RECORD +0 -126
  91. fastapi_rtk-0.2.27.dist-info/top_level.txt +0 -1
  92. /fastapi_rtk/cli/{templates → commands/db/templates}/fastapi/README +0 -0
  93. /fastapi_rtk/cli/{templates → commands/db/templates}/fastapi/alembic.ini.mako +0 -0
  94. /fastapi_rtk/cli/{templates → commands/db/templates}/fastapi/script.py.mako +0 -0
  95. /fastapi_rtk/cli/{templates → commands/db/templates}/fastapi-multidb/README +0 -0
  96. /fastapi_rtk/cli/{templates → commands/db/templates}/fastapi-multidb/alembic.ini.mako +0 -0
  97. {fastapi_rtk-0.2.27.dist-info → fastapi_rtk-1.0.13.dist-info}/entry_points.txt +0 -0
  98. {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
- """