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
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("_")]
@@ -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"]