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.
- graflo/README.md +18 -0
- graflo/__init__.py +39 -0
- graflo/architecture/__init__.py +37 -0
- graflo/architecture/actor.py +974 -0
- graflo/architecture/actor_util.py +425 -0
- graflo/architecture/edge.py +295 -0
- graflo/architecture/onto.py +374 -0
- graflo/architecture/resource.py +161 -0
- graflo/architecture/schema.py +136 -0
- graflo/architecture/transform.py +292 -0
- graflo/architecture/util.py +93 -0
- graflo/architecture/vertex.py +277 -0
- graflo/caster.py +409 -0
- graflo/cli/__init__.py +14 -0
- graflo/cli/ingest.py +144 -0
- graflo/cli/manage_dbs.py +193 -0
- graflo/cli/plot_schema.py +132 -0
- graflo/cli/xml2json.py +93 -0
- graflo/db/__init__.py +32 -0
- graflo/db/arango/__init__.py +16 -0
- graflo/db/arango/conn.py +734 -0
- graflo/db/arango/query.py +180 -0
- graflo/db/arango/util.py +88 -0
- graflo/db/connection.py +304 -0
- graflo/db/manager.py +104 -0
- graflo/db/neo4j/__init__.py +16 -0
- graflo/db/neo4j/conn.py +432 -0
- graflo/db/util.py +49 -0
- graflo/filter/__init__.py +21 -0
- graflo/filter/onto.py +400 -0
- graflo/logging.conf +22 -0
- graflo/onto.py +186 -0
- graflo/plot/__init__.py +17 -0
- graflo/plot/plotter.py +556 -0
- graflo/util/__init__.py +23 -0
- graflo/util/chunker.py +739 -0
- graflo/util/merge.py +148 -0
- graflo/util/misc.py +37 -0
- graflo/util/onto.py +63 -0
- graflo/util/transform.py +406 -0
- graflo-1.1.0.dist-info/METADATA +157 -0
- graflo-1.1.0.dist-info/RECORD +45 -0
- graflo-1.1.0.dist-info/WHEEL +4 -0
- graflo-1.1.0.dist-info/entry_points.txt +5 -0
- graflo-1.1.0.dist-info/licenses/LICENSE +126 -0
graflo/filter/onto.py
ADDED
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
"""Filter expression system for database queries.
|
|
2
|
+
|
|
3
|
+
This module provides a flexible system for creating and evaluating filter expressions
|
|
4
|
+
that can be translated into different database query languages (AQL, Cypher, Python).
|
|
5
|
+
It includes classes for logical operators, comparison operators, and filter clauses.
|
|
6
|
+
|
|
7
|
+
Key Components:
|
|
8
|
+
- LogicalOperator: Enum for logical operations (AND, OR, NOT, IMPLICATION)
|
|
9
|
+
- ComparisonOperator: Enum for comparison operations (==, !=, >, <, etc.)
|
|
10
|
+
- AbsClause: Abstract base class for filter clauses
|
|
11
|
+
- LeafClause: Concrete clause for field comparisons
|
|
12
|
+
- Clause: Composite clause combining multiple sub-clauses
|
|
13
|
+
- Expression: Factory class for creating filter expressions from dictionaries
|
|
14
|
+
|
|
15
|
+
Example:
|
|
16
|
+
>>> expr = Expression.from_dict({
|
|
17
|
+
... "AND": [
|
|
18
|
+
... {"field": "age", "cmp_operator": ">=", "value": 18},
|
|
19
|
+
... {"field": "status", "cmp_operator": "==", "value": "active"}
|
|
20
|
+
... ]
|
|
21
|
+
... })
|
|
22
|
+
>>> # Converts to: "age >= 18 AND status == 'active'"
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
import dataclasses
|
|
26
|
+
import logging
|
|
27
|
+
from abc import ABCMeta, abstractmethod
|
|
28
|
+
from types import MappingProxyType
|
|
29
|
+
|
|
30
|
+
from graflo.onto import BaseDataclass, BaseEnum, ExpressionFlavor
|
|
31
|
+
|
|
32
|
+
logger = logging.getLogger(__name__)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class LogicalOperator(BaseEnum):
|
|
36
|
+
"""Logical operators for combining filter conditions.
|
|
37
|
+
|
|
38
|
+
Attributes:
|
|
39
|
+
AND: Logical AND operation
|
|
40
|
+
OR: Logical OR operation
|
|
41
|
+
NOT: Logical NOT operation
|
|
42
|
+
IMPLICATION: Logical IF-THEN operation
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
AND = "AND"
|
|
46
|
+
OR = "OR"
|
|
47
|
+
NOT = "NOT"
|
|
48
|
+
IMPLICATION = "IF_THEN"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def implication(ops):
|
|
52
|
+
"""Evaluate logical implication (IF-THEN).
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
ops: Tuple of (antecedent, consequent)
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
bool: True if antecedent is False or consequent is True
|
|
59
|
+
"""
|
|
60
|
+
a, b = ops
|
|
61
|
+
return b if a else True
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
OperatorMapping = MappingProxyType(
|
|
65
|
+
{
|
|
66
|
+
LogicalOperator.AND: all,
|
|
67
|
+
LogicalOperator.OR: any,
|
|
68
|
+
LogicalOperator.IMPLICATION: implication,
|
|
69
|
+
}
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class ComparisonOperator(BaseEnum):
|
|
74
|
+
"""Comparison operators for field comparisons.
|
|
75
|
+
|
|
76
|
+
Attributes:
|
|
77
|
+
NEQ: Not equal (!=)
|
|
78
|
+
EQ: Equal (==)
|
|
79
|
+
GE: Greater than or equal (>=)
|
|
80
|
+
LE: Less than or equal (<=)
|
|
81
|
+
GT: Greater than (>)
|
|
82
|
+
LT: Less than (<)
|
|
83
|
+
IN: Membership test (IN)
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
NEQ = "!="
|
|
87
|
+
EQ = "=="
|
|
88
|
+
GE = ">="
|
|
89
|
+
LE = "<="
|
|
90
|
+
GT = ">"
|
|
91
|
+
LT = "<"
|
|
92
|
+
IN = "IN"
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@dataclasses.dataclass
|
|
96
|
+
class AbsClause(BaseDataclass, metaclass=ABCMeta):
|
|
97
|
+
"""Abstract base class for filter clauses.
|
|
98
|
+
|
|
99
|
+
This class defines the interface for all filter clauses, requiring
|
|
100
|
+
implementation of the __call__ method to evaluate or render the clause.
|
|
101
|
+
"""
|
|
102
|
+
|
|
103
|
+
@abstractmethod
|
|
104
|
+
def __call__(
|
|
105
|
+
self,
|
|
106
|
+
doc_name,
|
|
107
|
+
kind: ExpressionFlavor = ExpressionFlavor.ARANGO,
|
|
108
|
+
**kwargs,
|
|
109
|
+
):
|
|
110
|
+
"""Evaluate or render the clause.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
doc_name: Name of the document variable in the query
|
|
114
|
+
kind: Target expression flavor (ARANGO, NEO4J, PYTHON)
|
|
115
|
+
**kwargs: Additional arguments for evaluation
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
str: Rendered clause in the target language
|
|
119
|
+
"""
|
|
120
|
+
pass
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@dataclasses.dataclass
|
|
124
|
+
class LeafClause(AbsClause):
|
|
125
|
+
"""Concrete clause for field comparisons.
|
|
126
|
+
|
|
127
|
+
This class represents a single field comparison operation, such as
|
|
128
|
+
"field >= value" or "field IN [values]".
|
|
129
|
+
|
|
130
|
+
Attributes:
|
|
131
|
+
cmp_operator: Comparison operator to use
|
|
132
|
+
value: Value(s) to compare against
|
|
133
|
+
field: Field name to compare
|
|
134
|
+
operator: Optional operator to apply before comparison
|
|
135
|
+
"""
|
|
136
|
+
|
|
137
|
+
cmp_operator: ComparisonOperator | None = None
|
|
138
|
+
value: list = dataclasses.field(default_factory=list)
|
|
139
|
+
field: str | None = None
|
|
140
|
+
operator: str | None = None
|
|
141
|
+
|
|
142
|
+
def __post_init__(self):
|
|
143
|
+
"""Convert single value to list if necessary."""
|
|
144
|
+
if not isinstance(self.value, list):
|
|
145
|
+
self.value = [self.value]
|
|
146
|
+
|
|
147
|
+
def __call__(
|
|
148
|
+
self,
|
|
149
|
+
doc_name="doc",
|
|
150
|
+
kind: ExpressionFlavor = ExpressionFlavor.ARANGO,
|
|
151
|
+
**kwargs,
|
|
152
|
+
):
|
|
153
|
+
"""Render the leaf clause in the target language.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
doc_name: Name of the document variable
|
|
157
|
+
kind: Target expression flavor
|
|
158
|
+
**kwargs: Additional arguments
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
str: Rendered clause
|
|
162
|
+
|
|
163
|
+
Raises:
|
|
164
|
+
ValueError: If kind is not implemented
|
|
165
|
+
"""
|
|
166
|
+
if not self.value:
|
|
167
|
+
logger.warning(f"for {self} value is not set : {self.value}")
|
|
168
|
+
if kind == ExpressionFlavor.ARANGO:
|
|
169
|
+
assert self.cmp_operator is not None
|
|
170
|
+
return self._cast_arango(doc_name)
|
|
171
|
+
elif kind == ExpressionFlavor.NEO4J:
|
|
172
|
+
assert self.cmp_operator is not None
|
|
173
|
+
return self._cast_cypher(doc_name)
|
|
174
|
+
elif kind == ExpressionFlavor.PYTHON:
|
|
175
|
+
return self._cast_python(**kwargs)
|
|
176
|
+
else:
|
|
177
|
+
raise ValueError(f"kind {kind} not implemented")
|
|
178
|
+
|
|
179
|
+
def _cast_value(self):
|
|
180
|
+
"""Format the comparison value for query rendering.
|
|
181
|
+
|
|
182
|
+
Returns:
|
|
183
|
+
str: Formatted value string
|
|
184
|
+
"""
|
|
185
|
+
value = f"{self.value[0]}" if len(self.value) == 1 else f"{self.value}"
|
|
186
|
+
if len(self.value) == 1:
|
|
187
|
+
if isinstance(self.value[0], str):
|
|
188
|
+
value = f'"{self.value[0]}"'
|
|
189
|
+
elif self.value[0] is None:
|
|
190
|
+
value = "null"
|
|
191
|
+
else:
|
|
192
|
+
value = f"{self.value[0]}"
|
|
193
|
+
return value
|
|
194
|
+
|
|
195
|
+
def _cast_arango(self, doc_name):
|
|
196
|
+
"""Render the clause in AQL format.
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
doc_name: Document variable name
|
|
200
|
+
|
|
201
|
+
Returns:
|
|
202
|
+
str: AQL clause
|
|
203
|
+
"""
|
|
204
|
+
const = self._cast_value()
|
|
205
|
+
|
|
206
|
+
lemma = f"{self.cmp_operator} {const}"
|
|
207
|
+
if self.operator is not None:
|
|
208
|
+
lemma = f"{self.operator} {lemma}"
|
|
209
|
+
|
|
210
|
+
if self.field is not None:
|
|
211
|
+
lemma = f'{doc_name}["{self.field}"] {lemma}'
|
|
212
|
+
return lemma
|
|
213
|
+
|
|
214
|
+
def _cast_cypher(self, doc_name):
|
|
215
|
+
"""Render the clause in Cypher format.
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
doc_name: Document variable name
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
str: Cypher clause
|
|
222
|
+
"""
|
|
223
|
+
const = self._cast_value()
|
|
224
|
+
if self.cmp_operator == ComparisonOperator.EQ:
|
|
225
|
+
cmp_operator = "="
|
|
226
|
+
else:
|
|
227
|
+
cmp_operator = self.cmp_operator
|
|
228
|
+
lemma = f"{cmp_operator} {const}"
|
|
229
|
+
if self.operator is not None:
|
|
230
|
+
lemma = f"{self.operator} {lemma}"
|
|
231
|
+
|
|
232
|
+
if self.field is not None:
|
|
233
|
+
lemma = f"{doc_name}.{self.field} {lemma}"
|
|
234
|
+
return lemma
|
|
235
|
+
|
|
236
|
+
def _cast_python(self, **kwargs):
|
|
237
|
+
"""Evaluate the clause in Python.
|
|
238
|
+
|
|
239
|
+
Args:
|
|
240
|
+
**kwargs: Additional arguments
|
|
241
|
+
|
|
242
|
+
Returns:
|
|
243
|
+
bool: Evaluation result
|
|
244
|
+
"""
|
|
245
|
+
field = kwargs.pop(self.field, None)
|
|
246
|
+
if field is not None:
|
|
247
|
+
foo = getattr(field, self.operator)
|
|
248
|
+
return foo(self.value[0])
|
|
249
|
+
else:
|
|
250
|
+
return False
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
@dataclasses.dataclass
|
|
254
|
+
class Clause(AbsClause):
|
|
255
|
+
"""Composite clause combining multiple sub-clauses.
|
|
256
|
+
|
|
257
|
+
This class represents a logical combination of multiple filter clauses,
|
|
258
|
+
such as "clause1 AND clause2" or "NOT clause1".
|
|
259
|
+
|
|
260
|
+
Attributes:
|
|
261
|
+
operator: Logical operator to combine clauses
|
|
262
|
+
deps: List of dependent clauses
|
|
263
|
+
"""
|
|
264
|
+
|
|
265
|
+
operator: LogicalOperator
|
|
266
|
+
deps: list[AbsClause]
|
|
267
|
+
|
|
268
|
+
def __call__(
|
|
269
|
+
self,
|
|
270
|
+
doc_name="doc",
|
|
271
|
+
kind: ExpressionFlavor = ExpressionFlavor.ARANGO,
|
|
272
|
+
**kwargs,
|
|
273
|
+
):
|
|
274
|
+
"""Render the composite clause in the target language.
|
|
275
|
+
|
|
276
|
+
Args:
|
|
277
|
+
doc_name: Document variable name
|
|
278
|
+
kind: Target expression flavor
|
|
279
|
+
**kwargs: Additional arguments
|
|
280
|
+
|
|
281
|
+
Returns:
|
|
282
|
+
str: Rendered clause
|
|
283
|
+
|
|
284
|
+
Raises:
|
|
285
|
+
ValueError: If operator and dependencies don't match
|
|
286
|
+
"""
|
|
287
|
+
if kind == ExpressionFlavor.ARANGO or kind == ExpressionFlavor.ARANGO:
|
|
288
|
+
return self._cast_generic(doc_name=doc_name, kind=kind)
|
|
289
|
+
elif kind == ExpressionFlavor.PYTHON:
|
|
290
|
+
return self._cast_python(kind=kind, **kwargs)
|
|
291
|
+
|
|
292
|
+
def _cast_generic(self, doc_name, kind):
|
|
293
|
+
"""Render the clause in a generic format.
|
|
294
|
+
|
|
295
|
+
Args:
|
|
296
|
+
doc_name: Document variable name
|
|
297
|
+
kind: Target expression flavor
|
|
298
|
+
|
|
299
|
+
Returns:
|
|
300
|
+
str: Rendered clause
|
|
301
|
+
|
|
302
|
+
Raises:
|
|
303
|
+
ValueError: If operator and dependencies don't match
|
|
304
|
+
"""
|
|
305
|
+
if len(self.deps) == 1:
|
|
306
|
+
if self.operator == LogicalOperator.NOT:
|
|
307
|
+
return f"{self.operator} {self.deps[0](kind=kind, doc_name=doc_name)}"
|
|
308
|
+
else:
|
|
309
|
+
raise ValueError(
|
|
310
|
+
f" length of deps = {len(self.deps)} but operator is not"
|
|
311
|
+
f" {LogicalOperator.NOT}"
|
|
312
|
+
)
|
|
313
|
+
else:
|
|
314
|
+
return f" {self.operator} ".join(
|
|
315
|
+
[item(kind=kind, doc_name=doc_name) for item in self.deps]
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
def _cast_python(self, kind, **kwargs):
|
|
319
|
+
"""Evaluate the clause in Python.
|
|
320
|
+
|
|
321
|
+
Args:
|
|
322
|
+
kind: Expression flavor
|
|
323
|
+
**kwargs: Additional arguments
|
|
324
|
+
|
|
325
|
+
Returns:
|
|
326
|
+
bool: Evaluation result
|
|
327
|
+
|
|
328
|
+
Raises:
|
|
329
|
+
ValueError: If operator and dependencies don't match
|
|
330
|
+
"""
|
|
331
|
+
if len(self.deps) == 1:
|
|
332
|
+
if self.operator == LogicalOperator.NOT:
|
|
333
|
+
return not self.deps[0](kind=kind, **kwargs)
|
|
334
|
+
else:
|
|
335
|
+
raise ValueError(
|
|
336
|
+
f" length of deps = {len(self.deps)} but operator is not"
|
|
337
|
+
f" {LogicalOperator.NOT}"
|
|
338
|
+
)
|
|
339
|
+
else:
|
|
340
|
+
return OperatorMapping[self.operator](
|
|
341
|
+
[item(kind=kind, **kwargs) for item in self.deps]
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
@dataclasses.dataclass
|
|
346
|
+
class Expression(AbsClause):
|
|
347
|
+
"""Factory class for creating filter expressions.
|
|
348
|
+
|
|
349
|
+
This class provides methods to create filter expressions from dictionaries
|
|
350
|
+
and evaluate them in different languages.
|
|
351
|
+
"""
|
|
352
|
+
|
|
353
|
+
@classmethod
|
|
354
|
+
def from_dict(cls, current):
|
|
355
|
+
"""Create a filter expression from a dictionary.
|
|
356
|
+
|
|
357
|
+
Args:
|
|
358
|
+
current: Dictionary or list representing the filter expression
|
|
359
|
+
|
|
360
|
+
Returns:
|
|
361
|
+
AbsClause: Created filter expression
|
|
362
|
+
|
|
363
|
+
Example:
|
|
364
|
+
>>> expr = Expression.from_dict({
|
|
365
|
+
... "AND": [
|
|
366
|
+
... {"field": "age", "cmp_operator": ">=", "value": 18},
|
|
367
|
+
... {"field": "status", "cmp_operator": "==", "value": "active"}
|
|
368
|
+
... ]
|
|
369
|
+
... })
|
|
370
|
+
"""
|
|
371
|
+
if isinstance(current, list):
|
|
372
|
+
if current[0] in ComparisonOperator:
|
|
373
|
+
return LeafClause(*current)
|
|
374
|
+
elif current[0] in LogicalOperator:
|
|
375
|
+
return Clause(*current)
|
|
376
|
+
elif isinstance(current, dict):
|
|
377
|
+
k = list(current.keys())[0]
|
|
378
|
+
if k in LogicalOperator:
|
|
379
|
+
clauses = [cls.from_dict(v) for v in current[k]]
|
|
380
|
+
return Clause(operator=k, deps=clauses)
|
|
381
|
+
else:
|
|
382
|
+
return LeafClause(**current)
|
|
383
|
+
|
|
384
|
+
def __call__(
|
|
385
|
+
self,
|
|
386
|
+
doc_name="doc",
|
|
387
|
+
kind: ExpressionFlavor = ExpressionFlavor.ARANGO,
|
|
388
|
+
**kwargs,
|
|
389
|
+
):
|
|
390
|
+
"""Evaluate the expression in the target language.
|
|
391
|
+
|
|
392
|
+
Args:
|
|
393
|
+
doc_name: Document variable name
|
|
394
|
+
kind: Target expression flavor
|
|
395
|
+
**kwargs: Additional arguments
|
|
396
|
+
|
|
397
|
+
Returns:
|
|
398
|
+
str: Rendered expression
|
|
399
|
+
"""
|
|
400
|
+
pass
|
graflo/logging.conf
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
[loggers]
|
|
2
|
+
keys=root
|
|
3
|
+
|
|
4
|
+
[handlers]
|
|
5
|
+
keys=consoleHandler
|
|
6
|
+
|
|
7
|
+
[formatters]
|
|
8
|
+
keys=consoleFormatter
|
|
9
|
+
|
|
10
|
+
[logger_root]
|
|
11
|
+
level=INFO
|
|
12
|
+
handlers=consoleHandler
|
|
13
|
+
|
|
14
|
+
[handler_consoleHandler]
|
|
15
|
+
class=StreamHandler
|
|
16
|
+
level=INFO
|
|
17
|
+
formatter=consoleFormatter
|
|
18
|
+
args=(sys.stdout,)
|
|
19
|
+
|
|
20
|
+
[formatter_consoleFormatter]
|
|
21
|
+
format=%(asctime)s - %(levelname)s - %(name)s - %(funcName)s : %(message)s
|
|
22
|
+
datefmt=%Y-%m-%d %H:%M:%S
|
graflo/onto.py
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
"""Core ontology and base classes for graph database operations.
|
|
2
|
+
|
|
3
|
+
This module provides the fundamental data structures and base classes used throughout
|
|
4
|
+
the graph database system. It includes base classes for enums, dataclasses, and
|
|
5
|
+
database-specific configurations.
|
|
6
|
+
|
|
7
|
+
Key Components:
|
|
8
|
+
- BaseEnum: Base class for string-based enumerations with flexible membership testing
|
|
9
|
+
- BaseDataclass: Base class for dataclasses with JSON/YAML serialization support
|
|
10
|
+
- DBFlavor: Enum for supported database types (ArangoDB, Neo4j)
|
|
11
|
+
- ExpressionFlavor: Enum for expression language types
|
|
12
|
+
- AggregationType: Enum for supported aggregation operations
|
|
13
|
+
|
|
14
|
+
Example:
|
|
15
|
+
>>> class MyEnum(BaseEnum):
|
|
16
|
+
... VALUE1 = "value1"
|
|
17
|
+
... VALUE2 = "value2"
|
|
18
|
+
>>> "value1" in MyEnum # True
|
|
19
|
+
>>> "invalid" in MyEnum # False
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
import dataclasses
|
|
23
|
+
from copy import deepcopy
|
|
24
|
+
from enum import EnumMeta, StrEnum
|
|
25
|
+
|
|
26
|
+
from dataclass_wizard import JSONWizard, YAMLWizard
|
|
27
|
+
from dataclass_wizard.enums import DateTimeTo
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class MetaEnum(EnumMeta):
|
|
31
|
+
"""Metaclass for flexible enumeration membership testing.
|
|
32
|
+
|
|
33
|
+
This metaclass allows checking if a value is a valid member of an enum
|
|
34
|
+
using the `in` operator, even if the value hasn't been instantiated as
|
|
35
|
+
an enum member.
|
|
36
|
+
|
|
37
|
+
Example:
|
|
38
|
+
>>> class MyEnum(BaseEnum):
|
|
39
|
+
... VALUE = "value"
|
|
40
|
+
>>> "value" in MyEnum # True
|
|
41
|
+
>>> "invalid" in MyEnum # False
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
def __contains__(cls, item, **kwargs):
|
|
45
|
+
"""Check if an item is a valid member of the enum.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
item: Value to check for membership
|
|
49
|
+
**kwargs: Additional keyword arguments
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
bool: True if the item is a valid enum member, False otherwise
|
|
53
|
+
"""
|
|
54
|
+
try:
|
|
55
|
+
cls(item, **kwargs)
|
|
56
|
+
except ValueError:
|
|
57
|
+
return False
|
|
58
|
+
return True
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class BaseEnum(StrEnum, metaclass=MetaEnum):
|
|
62
|
+
"""Base class for string-based enumerations.
|
|
63
|
+
|
|
64
|
+
This class provides a foundation for string-based enums with flexible
|
|
65
|
+
membership testing through the MetaEnum metaclass.
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
pass
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class DBFlavor(BaseEnum):
|
|
72
|
+
"""Supported database types.
|
|
73
|
+
|
|
74
|
+
This enum defines the supported graph database types in the system.
|
|
75
|
+
|
|
76
|
+
Attributes:
|
|
77
|
+
ARANGO: ArangoDB database
|
|
78
|
+
NEO4J: Neo4j database
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
ARANGO = "arango"
|
|
82
|
+
NEO4J = "neo4j"
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class ExpressionFlavor(BaseEnum):
|
|
86
|
+
"""Supported expression language types.
|
|
87
|
+
|
|
88
|
+
This enum defines the supported expression languages for querying and
|
|
89
|
+
filtering data.
|
|
90
|
+
|
|
91
|
+
Attributes:
|
|
92
|
+
ARANGO: ArangoDB AQL expressions
|
|
93
|
+
NEO4J: Neo4j Cypher expressions
|
|
94
|
+
PYTHON: Python expressions
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
ARANGO = "arango"
|
|
98
|
+
NEO4J = "neo4j"
|
|
99
|
+
PYTHON = "python"
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class AggregationType(BaseEnum):
|
|
103
|
+
"""Supported aggregation operations.
|
|
104
|
+
|
|
105
|
+
This enum defines the supported aggregation operations for data analysis.
|
|
106
|
+
|
|
107
|
+
Attributes:
|
|
108
|
+
COUNT: Count operation
|
|
109
|
+
MAX: Maximum value
|
|
110
|
+
MIN: Minimum value
|
|
111
|
+
AVERAGE: Average value
|
|
112
|
+
SORTED_UNIQUE: Sorted unique values
|
|
113
|
+
"""
|
|
114
|
+
|
|
115
|
+
COUNT = "COUNT"
|
|
116
|
+
MAX = "MAX"
|
|
117
|
+
MIN = "MIN"
|
|
118
|
+
AVERAGE = "AVERAGE"
|
|
119
|
+
SORTED_UNIQUE = "SORTED_UNIQUE"
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@dataclasses.dataclass
|
|
123
|
+
class BaseDataclass(JSONWizard, JSONWizard.Meta, YAMLWizard):
|
|
124
|
+
"""Base class for dataclasses with serialization support.
|
|
125
|
+
|
|
126
|
+
This class provides a foundation for dataclasses with JSON and YAML
|
|
127
|
+
serialization capabilities. It includes methods for updating instances
|
|
128
|
+
and accessing field members.
|
|
129
|
+
|
|
130
|
+
Attributes:
|
|
131
|
+
marshal_date_time_as: Format for datetime serialization
|
|
132
|
+
key_transform_with_dump: Key transformation style for serialization
|
|
133
|
+
"""
|
|
134
|
+
|
|
135
|
+
marshal_date_time_as = DateTimeTo.ISO_FORMAT
|
|
136
|
+
key_transform_with_dump = "SNAKE"
|
|
137
|
+
# skip_defaults = True
|
|
138
|
+
|
|
139
|
+
def update(self, other):
|
|
140
|
+
"""Update this instance with values from another instance.
|
|
141
|
+
|
|
142
|
+
This method performs a deep update of the instance's attributes using
|
|
143
|
+
values from another instance of the same type. It handles different
|
|
144
|
+
types of attributes (sets, lists, dicts, dataclasses) appropriately.
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
other: Another instance of the same type to update from
|
|
148
|
+
|
|
149
|
+
Raises:
|
|
150
|
+
TypeError: If other is not an instance of the same type
|
|
151
|
+
"""
|
|
152
|
+
if not isinstance(other, type(self)):
|
|
153
|
+
raise TypeError(
|
|
154
|
+
f"Expected {type(self).__name__} instance, got {type(other).__name__}"
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
for field in dataclasses.fields(self):
|
|
158
|
+
name = field.name
|
|
159
|
+
current_value = getattr(self, name)
|
|
160
|
+
other_value = getattr(other, name)
|
|
161
|
+
|
|
162
|
+
if other_value is None:
|
|
163
|
+
pass
|
|
164
|
+
elif isinstance(other_value, set):
|
|
165
|
+
setattr(self, name, current_value | deepcopy(other_value))
|
|
166
|
+
elif isinstance(other_value, list):
|
|
167
|
+
setattr(self, name, current_value + deepcopy(other_value))
|
|
168
|
+
elif isinstance(other_value, dict):
|
|
169
|
+
setattr(self, name, {**current_value, **deepcopy(other_value)})
|
|
170
|
+
elif dataclasses.is_dataclass(type(other_value)):
|
|
171
|
+
if current_value is not None:
|
|
172
|
+
current_value.update(other_value)
|
|
173
|
+
else:
|
|
174
|
+
setattr(self, name, deepcopy(other_value))
|
|
175
|
+
else:
|
|
176
|
+
if current_value is None:
|
|
177
|
+
setattr(self, name, other_value)
|
|
178
|
+
|
|
179
|
+
@classmethod
|
|
180
|
+
def get_fields_members(cls):
|
|
181
|
+
"""Get list of field members excluding private ones.
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
list[str]: List of public field names
|
|
185
|
+
"""
|
|
186
|
+
return [k for k in cls.__annotations__ if not k.startswith("_")]
|
graflo/plot/__init__.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Plotting utilities for graph visualization.
|
|
2
|
+
|
|
3
|
+
This module provides tools for visualizing graph schemas and structures.
|
|
4
|
+
It includes functionality for creating visual representations of graph
|
|
5
|
+
databases, their vertices, edges, and relationships.
|
|
6
|
+
|
|
7
|
+
Key Components:
|
|
8
|
+
- SchemaPlotter: Creates visual representations of graph schemas
|
|
9
|
+
|
|
10
|
+
Example:
|
|
11
|
+
>>> plotter = SchemaPlotter(schema)
|
|
12
|
+
>>> plotter.plot("schema.png")
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from .plotter import SchemaPlotter
|
|
16
|
+
|
|
17
|
+
__all__ = ["SchemaPlotter"]
|