graflo 1.1.0__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.

Potentially problematic release.


This version of graflo might be problematic. Click here for more details.

Files changed (45) hide show
  1. graflo/README.md +18 -0
  2. graflo/__init__.py +39 -0
  3. graflo/architecture/__init__.py +37 -0
  4. graflo/architecture/actor.py +974 -0
  5. graflo/architecture/actor_util.py +425 -0
  6. graflo/architecture/edge.py +295 -0
  7. graflo/architecture/onto.py +374 -0
  8. graflo/architecture/resource.py +161 -0
  9. graflo/architecture/schema.py +136 -0
  10. graflo/architecture/transform.py +292 -0
  11. graflo/architecture/util.py +93 -0
  12. graflo/architecture/vertex.py +277 -0
  13. graflo/caster.py +409 -0
  14. graflo/cli/__init__.py +14 -0
  15. graflo/cli/ingest.py +144 -0
  16. graflo/cli/manage_dbs.py +193 -0
  17. graflo/cli/plot_schema.py +132 -0
  18. graflo/cli/xml2json.py +93 -0
  19. graflo/db/__init__.py +32 -0
  20. graflo/db/arango/__init__.py +16 -0
  21. graflo/db/arango/conn.py +734 -0
  22. graflo/db/arango/query.py +180 -0
  23. graflo/db/arango/util.py +88 -0
  24. graflo/db/connection.py +304 -0
  25. graflo/db/manager.py +104 -0
  26. graflo/db/neo4j/__init__.py +16 -0
  27. graflo/db/neo4j/conn.py +432 -0
  28. graflo/db/util.py +49 -0
  29. graflo/filter/__init__.py +21 -0
  30. graflo/filter/onto.py +400 -0
  31. graflo/logging.conf +22 -0
  32. graflo/onto.py +186 -0
  33. graflo/plot/__init__.py +17 -0
  34. graflo/plot/plotter.py +556 -0
  35. graflo/util/__init__.py +23 -0
  36. graflo/util/chunker.py +739 -0
  37. graflo/util/merge.py +148 -0
  38. graflo/util/misc.py +37 -0
  39. graflo/util/onto.py +63 -0
  40. graflo/util/transform.py +406 -0
  41. graflo-1.1.0.dist-info/METADATA +157 -0
  42. graflo-1.1.0.dist-info/RECORD +45 -0
  43. graflo-1.1.0.dist-info/WHEEL +4 -0
  44. graflo-1.1.0.dist-info/entry_points.txt +5 -0
  45. graflo-1.1.0.dist-info/licenses/LICENSE +126 -0
@@ -0,0 +1,161 @@
1
+ """Resource management and processing for graph databases.
2
+
3
+ This module provides the core resource handling functionality for graph databases.
4
+ It defines how data resources are processed, transformed, and mapped to graph
5
+ structures through a system of actors and transformations.
6
+
7
+ Key Components:
8
+ - Resource: Main class for resource processing and transformation
9
+ - ActorWrapper: Wrapper for processing actors
10
+ - ActionContext: Context for processing actions
11
+
12
+ The resource system allows for:
13
+ - Data encoding and transformation
14
+ - Vertex and edge creation
15
+ - Weight management
16
+ - Collection merging
17
+ - Type casting and validation
18
+
19
+ Example:
20
+ >>> resource = Resource(
21
+ ... resource_name="users",
22
+ ... apply=[VertexActor("user"), EdgeActor("follows")],
23
+ ... encoding=EncodingType.UTF_8
24
+ ... )
25
+ >>> result = resource(doc)
26
+ """
27
+
28
+ import dataclasses
29
+ import logging
30
+ from collections import defaultdict
31
+ from typing import Callable
32
+
33
+ from dataclass_wizard import JSONWizard
34
+
35
+ from graflo.architecture.actor import (
36
+ ActorWrapper,
37
+ )
38
+ from graflo.architecture.edge import Edge, EdgeConfig
39
+ from graflo.architecture.onto import (
40
+ ActionContext,
41
+ EncodingType,
42
+ GraphEntity,
43
+ )
44
+ from graflo.architecture.transform import ProtoTransform
45
+ from graflo.architecture.vertex import (
46
+ VertexConfig,
47
+ )
48
+ from graflo.onto import BaseDataclass
49
+
50
+ logger = logging.getLogger(__name__)
51
+
52
+
53
+ @dataclasses.dataclass(kw_only=True)
54
+ class Resource(BaseDataclass, JSONWizard):
55
+ """Resource configuration and processing.
56
+
57
+ This class represents a data resource that can be processed and transformed
58
+ into graph structures. It manages the processing pipeline through actors
59
+ and handles data encoding, transformation, and mapping.
60
+
61
+ Attributes:
62
+ resource_name: Name of the resource
63
+ apply: List of actors to apply in sequence
64
+ encoding: Data encoding type (default: UTF_8)
65
+ merge_collections: List of collections to merge
66
+ extra_weights: List of additional edge weights
67
+ types: Dictionary of field type mappings
68
+ root: Root actor wrapper for processing
69
+ vertex_config: Configuration for vertices
70
+ edge_config: Configuration for edges
71
+ """
72
+
73
+ resource_name: str
74
+ apply: list
75
+ encoding: EncodingType = EncodingType.UTF_8
76
+ merge_collections: list[str] = dataclasses.field(default_factory=list)
77
+ extra_weights: list[Edge] = dataclasses.field(default_factory=list)
78
+ types: dict[str, str] = dataclasses.field(default_factory=dict)
79
+
80
+ def __post_init__(self):
81
+ """Initialize the resource after dataclass initialization.
82
+
83
+ Sets up the actor wrapper and type mappings. Evaluates type expressions
84
+ for field type casting.
85
+
86
+ Raises:
87
+ Exception: If type evaluation fails for any field
88
+ """
89
+ self.root = ActorWrapper(*self.apply)
90
+ self._types: dict[str, Callable] = dict()
91
+ self.vertex_config: VertexConfig
92
+ self.edge_config: EdgeConfig
93
+ for k, v in self.types.items():
94
+ try:
95
+ self._types[k] = eval(v)
96
+ except Exception as ex:
97
+ logger.error(
98
+ f"For resource {self.name} for field {k} failed to cast type {v} : {ex}"
99
+ )
100
+
101
+ @property
102
+ def name(self):
103
+ """Get the resource name.
104
+
105
+ Returns:
106
+ str: Name of the resource
107
+ """
108
+ return self.resource_name
109
+
110
+ def finish_init(
111
+ self,
112
+ vertex_config: VertexConfig,
113
+ edge_config: EdgeConfig,
114
+ transforms: dict[str, ProtoTransform],
115
+ ):
116
+ """Complete resource initialization.
117
+
118
+ Initializes the resource with vertex and edge configurations,
119
+ and sets up the processing pipeline.
120
+
121
+ Args:
122
+ vertex_config: Configuration for vertices
123
+ edge_config: Configuration for edges
124
+ transforms: Dictionary of available transforms
125
+ """
126
+ self.vertex_config = vertex_config
127
+ self.edge_config = edge_config
128
+
129
+ logger.debug(f"total resource actor count : {self.root.count()}")
130
+ self.root.finish_init(
131
+ vertex_config=vertex_config,
132
+ transforms=transforms,
133
+ edge_config=edge_config,
134
+ )
135
+
136
+ logger.debug(f"total resource actor count (after 2 finit): {self.root.count()}")
137
+
138
+ for e in self.extra_weights:
139
+ e.finish_init(vertex_config)
140
+
141
+ def __call__(self, doc: dict) -> defaultdict[GraphEntity, list]:
142
+ """Process a document through the resource pipeline.
143
+
144
+ Args:
145
+ doc: Document to process
146
+
147
+ Returns:
148
+ defaultdict[GraphEntity, list]: Processed graph entities
149
+ """
150
+ ctx = ActionContext()
151
+ ctx = self.root(ctx, doc=doc)
152
+ acc = self.root.normalize_ctx(ctx)
153
+ return acc
154
+
155
+ def count(self):
156
+ """Get the total number of actors in the resource.
157
+
158
+ Returns:
159
+ int: Number of actors
160
+ """
161
+ return self.root.count()
@@ -0,0 +1,136 @@
1
+ """Graph database schema management and configuration.
2
+
3
+ This module provides the core schema management functionality for graph databases.
4
+ It defines the structure and configuration of vertices, edges, and resources
5
+ that make up the graph database schema.
6
+
7
+ Key Components:
8
+ - Schema: Main schema container with metadata and configurations
9
+ - SchemaMetadata: Schema versioning and naming information
10
+ - Resource: Resource definitions for data processing
11
+ - VertexConfig: Vertex collection configurations
12
+ - EdgeConfig: Edge collection configurations
13
+
14
+ The schema system provides:
15
+ - Schema versioning and metadata
16
+ - Resource management and validation
17
+ - Vertex and edge configuration
18
+ - Transform registration and management
19
+
20
+ Example:
21
+ >>> schema = Schema(
22
+ ... general=SchemaMetadata(name="social_network", version="1.0"),
23
+ ... vertex_config=VertexConfig(...),
24
+ ... edge_config=EdgeConfig(...),
25
+ ... resources=[Resource(...)]
26
+ ... )
27
+ >>> resource = schema.fetch_resource("users")
28
+ """
29
+
30
+ import dataclasses
31
+ import logging
32
+ from collections import Counter
33
+ from typing import Optional
34
+
35
+ from graflo.architecture.edge import EdgeConfig
36
+ from graflo.architecture.resource import Resource
37
+ from graflo.architecture.transform import ProtoTransform
38
+ from graflo.architecture.vertex import VertexConfig
39
+ from graflo.onto import BaseDataclass
40
+
41
+ logger = logging.getLogger(__name__)
42
+
43
+
44
+ @dataclasses.dataclass
45
+ class SchemaMetadata(BaseDataclass):
46
+ """Schema metadata and versioning information.
47
+
48
+ This class holds metadata about the schema, including its name and version.
49
+ It's used for schema identification and versioning.
50
+
51
+ Attributes:
52
+ name: Name of the schema
53
+ version: Optional version string of the schema
54
+ """
55
+
56
+ name: str
57
+ version: Optional[str] = None
58
+
59
+
60
+ @dataclasses.dataclass
61
+ class Schema(BaseDataclass):
62
+ """Graph database schema configuration.
63
+
64
+ This class represents the complete schema configuration for a graph database.
65
+ It manages resources, vertex configurations, edge configurations, and transforms.
66
+
67
+ Attributes:
68
+ general: Schema metadata and versioning information
69
+ vertex_config: Configuration for vertex collections
70
+ edge_config: Configuration for edge collections
71
+ resources: List of resource definitions
72
+ transforms: Dictionary of available transforms
73
+ _resources: Internal mapping of resource names to resources
74
+ """
75
+
76
+ general: SchemaMetadata
77
+ vertex_config: VertexConfig
78
+ edge_config: EdgeConfig
79
+ resources: list[Resource]
80
+ transforms: dict[str, ProtoTransform] = dataclasses.field(default_factory=dict)
81
+
82
+ def __post_init__(self):
83
+ """Initialize the schema after dataclass initialization.
84
+
85
+ Sets up transforms, initializes edge configuration, and validates
86
+ resource names for uniqueness.
87
+
88
+ Raises:
89
+ ValueError: If duplicate resource names are found
90
+ """
91
+ for name, t in self.transforms.items():
92
+ t.name = name
93
+
94
+ self.edge_config.finish_init(self.vertex_config)
95
+
96
+ for r in self.resources:
97
+ r.finish_init(
98
+ vertex_config=self.vertex_config,
99
+ edge_config=self.edge_config,
100
+ transforms=self.transforms,
101
+ )
102
+
103
+ names = [r.name for r in self.resources]
104
+ c = Counter(names)
105
+ for k, v in c.items():
106
+ if v > 1:
107
+ raise ValueError(f"resource name {k} used {v} times")
108
+ self._resources: dict[str, Resource] = {}
109
+ for r in self.resources:
110
+ self._resources[r.name] = r
111
+
112
+ def fetch_resource(self, name: Optional[str] = None) -> Resource:
113
+ """Fetch a resource by name or get the first available resource.
114
+
115
+ Args:
116
+ name: Optional name of the resource to fetch
117
+
118
+ Returns:
119
+ Resource: The requested resource
120
+
121
+ Raises:
122
+ ValueError: If the requested resource is not found or if no resources exist
123
+ """
124
+ _current_resource = None
125
+
126
+ if name is not None:
127
+ if name in self._resources:
128
+ _current_resource = self._resources[name]
129
+ else:
130
+ raise ValueError(f"Resource {name} not found")
131
+ else:
132
+ if self._resources:
133
+ _current_resource = self.resources[0]
134
+ else:
135
+ raise ValueError("Empty resource container 😕")
136
+ return _current_resource
@@ -0,0 +1,292 @@
1
+ """Data transformation and mapping system for graph databases.
2
+
3
+ This module provides a flexible system for transforming and mapping data in graph
4
+ databases. It supports both functional transformations and declarative mappings,
5
+ with support for field switching and parameter configuration.
6
+
7
+ Key Components:
8
+ - ProtoTransform: Base class for transform definitions
9
+ - Transform: Concrete transform implementation
10
+ - TransformException: Custom exception for transform errors
11
+
12
+ The transform system supports:
13
+ - Functional transformations through imported modules
14
+ - Field mapping and switching
15
+ - Parameter configuration
16
+ - Input/output field specification
17
+ - Transform composition and inheritance
18
+
19
+ Example:
20
+ >>> transform = Transform(
21
+ ... module="my_module",
22
+ ... foo="process_data",
23
+ ... input=("field1", "field2"),
24
+ ... output=("result1", "result2")
25
+ ... )
26
+ >>> result = transform({"field1": 1, "field2": 2})
27
+ """
28
+
29
+ from __future__ import annotations
30
+
31
+ import dataclasses
32
+ import importlib
33
+ import logging
34
+ from copy import deepcopy
35
+ from typing import Optional
36
+
37
+ from graflo.onto import BaseDataclass
38
+
39
+ logger = logging.getLogger(__name__)
40
+
41
+
42
+ class TransformException(BaseException):
43
+ """Base exception for transform-related errors."""
44
+
45
+ pass
46
+
47
+
48
+ @dataclasses.dataclass
49
+ class ProtoTransform(BaseDataclass):
50
+ """Base class for transform definitions.
51
+
52
+ This class provides the foundation for data transformations, supporting both
53
+ functional transformations and declarative mappings.
54
+
55
+ Attributes:
56
+ name: Optional name of the transform
57
+ module: Optional module containing the transform function
58
+ params: Dictionary of transform parameters
59
+ foo: Optional name of the transform function
60
+ input: Tuple of input field names
61
+ output: Tuple of output field names
62
+ _foo: Internal reference to the transform function
63
+ """
64
+
65
+ name: Optional[str] = None
66
+ module: Optional[str] = None
67
+ params: dict = dataclasses.field(default_factory=dict)
68
+ foo: Optional[str] = None
69
+ input: tuple[str, ...] = dataclasses.field(default_factory=tuple)
70
+ output: tuple[str, ...] = dataclasses.field(default_factory=tuple)
71
+
72
+ def __post_init__(self):
73
+ """Initialize the transform after dataclass initialization.
74
+
75
+ Sets up the transform function and input/output field specifications.
76
+ """
77
+ self._foo = None
78
+ self._init_foo()
79
+
80
+ self.input = self._tuple_it(self.input)
81
+
82
+ if not self.output:
83
+ self.output = self.input
84
+ self.output = self._tuple_it(self.output)
85
+
86
+ @staticmethod
87
+ def _tuple_it(x):
88
+ """Convert input to tuple format.
89
+
90
+ Args:
91
+ x: Input to convert (string, list, or tuple)
92
+
93
+ Returns:
94
+ tuple: Converted tuple
95
+ """
96
+ if isinstance(x, str):
97
+ x = [x]
98
+ if isinstance(x, list):
99
+ x = tuple(x)
100
+ return x
101
+
102
+ def _init_foo(self):
103
+ """Initialize the transform function from module.
104
+
105
+ Imports the specified module and gets the transform function.
106
+
107
+ Raises:
108
+ TypeError: If module import fails
109
+ ValueError: If function lookup fails
110
+ """
111
+ if self.module is not None:
112
+ try:
113
+ _module = importlib.import_module(self.module)
114
+ except Exception as e:
115
+ raise TypeError(f"Provided module {self.module} is not valid: {e}")
116
+ try:
117
+ self._foo = getattr(_module, self.foo)
118
+ except Exception as e:
119
+ raise ValueError(
120
+ f"Could not instantiate transform function. Exception: {e}"
121
+ )
122
+
123
+ def __lt__(self, other):
124
+ """Compare transforms for ordering.
125
+
126
+ Args:
127
+ other: Other transform to compare with
128
+
129
+ Returns:
130
+ bool: True if this transform should be ordered before other
131
+ """
132
+ if self._foo is None and other._foo is not None:
133
+ return True
134
+ return False
135
+
136
+
137
+ @dataclasses.dataclass
138
+ class Transform(ProtoTransform):
139
+ """Concrete transform implementation.
140
+
141
+ This class extends ProtoTransform with additional functionality for
142
+ field mapping, switching, and transform composition.
143
+
144
+ Attributes:
145
+ fields: Tuple of fields to transform
146
+ map: Dictionary mapping input fields to output fields
147
+ switch: Dictionary for field switching logic
148
+ functional_transform: Whether this is a functional transform
149
+ """
150
+
151
+ fields: tuple[str, ...] = dataclasses.field(default_factory=tuple)
152
+ map: dict[str, str] = dataclasses.field(default_factory=dict)
153
+ switch: dict[str, str] = dataclasses.field(default_factory=dict)
154
+
155
+ def __post_init__(self):
156
+ """Initialize the transform after dataclass initialization.
157
+
158
+ Sets up field specifications and validates transform configuration.
159
+
160
+ Raises:
161
+ ValueError: If transform configuration is invalid
162
+ """
163
+ super().__post_init__()
164
+ self.functional_transform = False
165
+ if self._foo is not None:
166
+ self.functional_transform = True
167
+
168
+ self.input = self._tuple_it(self.input)
169
+
170
+ self.fields = self._tuple_it(self.fields)
171
+
172
+ self.input = self.fields if self.fields and not self.input else self.input
173
+ if not self.output:
174
+ self.output = self.input
175
+ self.output = self._tuple_it(self.output)
176
+
177
+ if not self.input and not self.output:
178
+ if self.map:
179
+ items = list(self.map.items())
180
+ self.input = tuple(x for x, _ in items)
181
+ self.output = tuple(x for _, x in items)
182
+ elif self.switch:
183
+ self.input = tuple([k for k in self.switch])
184
+ self.output = tuple(self.switch[self.input[0]])
185
+ elif not self.name:
186
+ raise ValueError(
187
+ "Either input and output, fields, map or name should be"
188
+ " provided in Transform constructor."
189
+ )
190
+
191
+ def __call__(self, *nargs, **kwargs):
192
+ """Execute the transform.
193
+
194
+ Args:
195
+ *nargs: Positional arguments for the transform
196
+ **kwargs: Keyword arguments for the transform
197
+
198
+ Returns:
199
+ dict: Transformed data
200
+ """
201
+ is_mapping = self._foo is None
202
+
203
+ if is_mapping:
204
+ input_doc = nargs[0]
205
+ if isinstance(input_doc, dict):
206
+ output_values = [input_doc[k] for k in self.input]
207
+ else:
208
+ output_values = nargs
209
+ else:
210
+ if nargs and isinstance(input_doc := nargs[0], dict):
211
+ new_args = [input_doc[k] for k in self.input]
212
+ output_values = self._foo(*new_args, **kwargs, **self.params)
213
+ else:
214
+ output_values = self._foo(*nargs, **kwargs, **self.params)
215
+
216
+ if self.output:
217
+ r = self._dress_as_dict(output_values)
218
+ else:
219
+ r = output_values
220
+ return r
221
+
222
+ def _dress_as_dict(self, transform_result):
223
+ """Convert transform result to dictionary format.
224
+
225
+ Args:
226
+ transform_result: Result of the transform
227
+
228
+ Returns:
229
+ dict: Dictionary representation of the result
230
+ """
231
+ if isinstance(transform_result, (list, tuple)) and not self.switch:
232
+ upd = {k: v for k, v in zip(self.output, transform_result)}
233
+ else:
234
+ # TODO : temporary solution works only there is one switch clause
235
+ upd = {self.output[-1]: transform_result}
236
+ for k0, (q, qq) in self.switch.items():
237
+ upd.update({q: k0})
238
+ return upd
239
+
240
+ @property
241
+ def is_dummy(self):
242
+ """Check if this is a dummy transform.
243
+
244
+ Returns:
245
+ bool: True if this is a dummy transform
246
+ """
247
+ return (self.name is not None) and (not self.map and self._foo is None)
248
+
249
+ def update(self, t: Transform):
250
+ """Update this transform with another transform's configuration.
251
+
252
+ Args:
253
+ t: Transform to update from
254
+
255
+ Returns:
256
+ Transform: Updated transform
257
+ """
258
+ t_copy = deepcopy(t)
259
+ if self.input:
260
+ t_copy.input = self.input
261
+ if self.output:
262
+ t_copy.output = self.output
263
+ if self.params:
264
+ t_copy.params.update(self.params)
265
+ t_copy.__post_init__()
266
+ return t_copy
267
+
268
+ def get_barebone(
269
+ self, other: Optional[Transform]
270
+ ) -> tuple[Optional[Transform], Optional[Transform]]:
271
+ """Get the barebone transform configuration.
272
+
273
+ Args:
274
+ other: Optional transform to use as base
275
+
276
+ Returns:
277
+ tuple[Optional[Transform], Optional[Transform]]: Updated self transform
278
+ and transform to store in library
279
+ """
280
+ self_param = self.to_dict(skip_defaults=True)
281
+ if self.foo is not None:
282
+ # self will be the lib transform
283
+ return None, self
284
+ elif other is not None and other.foo is not None:
285
+ # init self from other
286
+ self_param.pop("foo", None)
287
+ self_param.pop("module", None)
288
+ other_param = other.to_dict(skip_defaults=True)
289
+ other_param.update(self_param)
290
+ return Transform(**other_param), None
291
+ else:
292
+ return None, None
@@ -0,0 +1,93 @@
1
+ """Utility functions for graph architecture operations.
2
+
3
+ This module provides utility functions for working with graph data structures
4
+ and transformations. It includes functions for dictionary projection and
5
+ graph entity name formatting.
6
+
7
+ Key Functions:
8
+ - project_dict: Project dictionary fields based on inclusion/exclusion
9
+ - cast_graph_name_to_triple: Convert graph names to standardized triple format
10
+
11
+ Example:
12
+ >>> data = {"a": 1, "b": 2, "c": 3}
13
+ >>> project_dict(data, ["a", "b"], how="include")
14
+ {'a': 1, 'b': 2}
15
+ >>> cast_graph_name_to_triple("user_post_graph")
16
+ ('user', 'post', None)
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ from typing import Union
22
+
23
+ from graflo.architecture.onto import GraphEntity
24
+
25
+
26
+ def project_dict(item, keys, how="include"):
27
+ """Project dictionary fields based on inclusion or exclusion.
28
+
29
+ This function filters a dictionary based on a list of keys, either including
30
+ or excluding the specified keys.
31
+
32
+ Args:
33
+ item: Dictionary to project
34
+ keys: List of keys to include or exclude
35
+ how: Projection mode - "include" or "exclude" (default: "include")
36
+
37
+ Returns:
38
+ dict: Projected dictionary containing only the specified fields
39
+
40
+ Example:
41
+ >>> data = {"a": 1, "b": 2, "c": 3}
42
+ >>> project_dict(data, ["a", "b"], how="include")
43
+ {'a': 1, 'b': 2}
44
+ >>> project_dict(data, ["a"], how="exclude")
45
+ {'b': 2, 'c': 3}
46
+ """
47
+ if how == "include":
48
+ return {k: v for k, v in item.items() if k in keys}
49
+ elif how == "exclude":
50
+ return {k: v for k, v in item.items() if k not in keys}
51
+ else:
52
+ return {}
53
+
54
+
55
+ def cast_graph_name_to_triple(s: GraphEntity) -> Union[str, tuple]:
56
+ """Convert a graph name string to a triple format.
57
+
58
+ This function parses graph entity names into a standardized triple format
59
+ (source, target, type). It handles various naming patterns and special
60
+ suffixes like "graph" or "edges".
61
+
62
+ Args:
63
+ s: Graph entity name or ID
64
+
65
+ Returns:
66
+ Union[str, tuple]: Either a string for simple names or a tuple
67
+ representing (source, target, type) for complex names
68
+
69
+ Raises:
70
+ ValueError: If the graph name cannot be cast to a valid format
71
+
72
+ Example:
73
+ >>> cast_graph_name_to_triple("user_post_graph")
74
+ ('user', 'post', None)
75
+ >>> cast_graph_name_to_triple("simple_vertex")
76
+ ('simple', None)
77
+ """
78
+ if isinstance(s, str):
79
+ s2 = s.split("_")
80
+ if len(s2) < 2:
81
+ return s2[0]
82
+ elif len(s2) == 2:
83
+ return *s2[:-1], None
84
+ elif len(s2) == 3:
85
+ if s2[-1] in ["graph", "edges"]:
86
+ return *s2[:-1], None
87
+ else:
88
+ return tuple(s2)
89
+ elif len(s2) == 4 and s2[-1] in ["graph", "edges"]:
90
+ return tuple(s2[:-1])
91
+ raise ValueError(f"Invalid graph_name {s} : can not be cast to GraphEntity")
92
+ else:
93
+ return s