graflo 1.3.7__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 (70) hide show
  1. graflo/README.md +18 -0
  2. graflo/__init__.py +70 -0
  3. graflo/architecture/__init__.py +38 -0
  4. graflo/architecture/actor.py +1276 -0
  5. graflo/architecture/actor_util.py +450 -0
  6. graflo/architecture/edge.py +418 -0
  7. graflo/architecture/onto.py +376 -0
  8. graflo/architecture/onto_sql.py +54 -0
  9. graflo/architecture/resource.py +163 -0
  10. graflo/architecture/schema.py +135 -0
  11. graflo/architecture/transform.py +292 -0
  12. graflo/architecture/util.py +89 -0
  13. graflo/architecture/vertex.py +562 -0
  14. graflo/caster.py +736 -0
  15. graflo/cli/__init__.py +14 -0
  16. graflo/cli/ingest.py +203 -0
  17. graflo/cli/manage_dbs.py +197 -0
  18. graflo/cli/plot_schema.py +132 -0
  19. graflo/cli/xml2json.py +93 -0
  20. graflo/data_source/__init__.py +48 -0
  21. graflo/data_source/api.py +339 -0
  22. graflo/data_source/base.py +95 -0
  23. graflo/data_source/factory.py +304 -0
  24. graflo/data_source/file.py +148 -0
  25. graflo/data_source/memory.py +70 -0
  26. graflo/data_source/registry.py +82 -0
  27. graflo/data_source/sql.py +183 -0
  28. graflo/db/__init__.py +44 -0
  29. graflo/db/arango/__init__.py +22 -0
  30. graflo/db/arango/conn.py +1025 -0
  31. graflo/db/arango/query.py +180 -0
  32. graflo/db/arango/util.py +88 -0
  33. graflo/db/conn.py +377 -0
  34. graflo/db/connection/__init__.py +6 -0
  35. graflo/db/connection/config_mapping.py +18 -0
  36. graflo/db/connection/onto.py +717 -0
  37. graflo/db/connection/wsgi.py +29 -0
  38. graflo/db/manager.py +119 -0
  39. graflo/db/neo4j/__init__.py +16 -0
  40. graflo/db/neo4j/conn.py +639 -0
  41. graflo/db/postgres/__init__.py +37 -0
  42. graflo/db/postgres/conn.py +948 -0
  43. graflo/db/postgres/fuzzy_matcher.py +281 -0
  44. graflo/db/postgres/heuristics.py +133 -0
  45. graflo/db/postgres/inference_utils.py +428 -0
  46. graflo/db/postgres/resource_mapping.py +273 -0
  47. graflo/db/postgres/schema_inference.py +372 -0
  48. graflo/db/postgres/types.py +148 -0
  49. graflo/db/postgres/util.py +87 -0
  50. graflo/db/tigergraph/__init__.py +9 -0
  51. graflo/db/tigergraph/conn.py +2365 -0
  52. graflo/db/tigergraph/onto.py +26 -0
  53. graflo/db/util.py +49 -0
  54. graflo/filter/__init__.py +21 -0
  55. graflo/filter/onto.py +525 -0
  56. graflo/logging.conf +22 -0
  57. graflo/onto.py +312 -0
  58. graflo/plot/__init__.py +17 -0
  59. graflo/plot/plotter.py +616 -0
  60. graflo/util/__init__.py +23 -0
  61. graflo/util/chunker.py +807 -0
  62. graflo/util/merge.py +150 -0
  63. graflo/util/misc.py +37 -0
  64. graflo/util/onto.py +422 -0
  65. graflo/util/transform.py +454 -0
  66. graflo-1.3.7.dist-info/METADATA +243 -0
  67. graflo-1.3.7.dist-info/RECORD +70 -0
  68. graflo-1.3.7.dist-info/WHEEL +4 -0
  69. graflo-1.3.7.dist-info/entry_points.txt +5 -0
  70. graflo-1.3.7.dist-info/licenses/LICENSE +126 -0
@@ -0,0 +1,26 @@
1
+ """TigerGraph-specific type mappings and constants.
2
+
3
+ This module provides TigerGraph-specific type mappings and constants,
4
+ separating database-specific concerns from universal types defined at
5
+ the root GraFlo level.
6
+
7
+ Universal types (FieldType enum) are defined in graflo.architecture.vertex.
8
+ This module provides TigerGraph-specific mappings and aliases.
9
+ """
10
+
11
+ from graflo.architecture.vertex import FieldType
12
+
13
+ # Type aliases for TigerGraph
14
+ # Maps common type name variants to standard FieldType values
15
+ # These are TigerGraph-specific mappings (e.g., "INTEGER" -> "INT" for TigerGraph)
16
+ TIGERGRAPH_TYPE_ALIASES: dict[str, str] = {
17
+ "INTEGER": FieldType.INT.value,
18
+ "STR": FieldType.STRING.value,
19
+ "BOOLEAN": FieldType.BOOL.value,
20
+ "DATE": FieldType.DATETIME.value,
21
+ "TIME": FieldType.DATETIME.value,
22
+ }
23
+
24
+ # Set of valid TigerGraph type strings (FieldType enum values)
25
+ # FieldType enum values are already in TigerGraph format
26
+ VALID_TIGERGRAPH_TYPES: set[str] = {ft.value for ft in FieldType}
graflo/db/util.py ADDED
@@ -0,0 +1,49 @@
1
+ """Database cursor utilities for graph operations.
2
+
3
+ This module provides utility functions for working with database cursors,
4
+ particularly for handling batch data retrieval and cursor iteration.
5
+
6
+ Key Functions:
7
+ - get_data_from_cursor: Retrieve data from a cursor with optional limit
8
+
9
+ Example:
10
+ >>> cursor = db.execute("FOR doc IN collection RETURN doc")
11
+ >>> batch = get_data_from_cursor(cursor, limit=100)
12
+ """
13
+
14
+ from arango.exceptions import CursorNextError
15
+
16
+
17
+ def get_data_from_cursor(cursor, limit=None):
18
+ """Retrieve data from a cursor with optional limit.
19
+
20
+ This function iterates over a database cursor and collects the results
21
+ into a batch. It handles cursor iteration errors and supports an optional
22
+ limit on the number of items retrieved.
23
+
24
+ Args:
25
+ cursor: Database cursor to iterate over
26
+ limit: Optional maximum number of items to retrieve
27
+
28
+ Returns:
29
+ list: Batch of items retrieved from the cursor
30
+
31
+ Note:
32
+ The function will stop iteration if:
33
+ - The limit is reached
34
+ - The cursor is exhausted
35
+ - A CursorNextError occurs
36
+ """
37
+ batch = []
38
+ cnt = 0
39
+ while True:
40
+ try:
41
+ if limit is not None and cnt >= limit:
42
+ raise StopIteration
43
+ item = next(cursor)
44
+ batch.append(item)
45
+ cnt += 1
46
+ except StopIteration:
47
+ return batch
48
+ except CursorNextError:
49
+ return batch
@@ -0,0 +1,21 @@
1
+ """Filter expression system for database queries.
2
+
3
+ This package provides a flexible system for creating and evaluating filter expressions
4
+ that can be translated into different database query languages (AQL, Cypher, Python).
5
+
6
+ Key Components:
7
+ - LogicalOperator: Logical operations (AND, OR, NOT, IMPLICATION)
8
+ - ComparisonOperator: Comparison operations (==, !=, >, <, etc.)
9
+ - Clause: Filter clause implementation
10
+ - Expression: Filter expression factory
11
+
12
+ Example:
13
+ >>> from graflo.filter import Expression
14
+ >>> expr = Expression.from_dict({
15
+ ... "AND": [
16
+ ... {"field": "age", "cmp_operator": ">=", "value": 18},
17
+ ... {"field": "status", "cmp_operator": "==", "value": "active"}
18
+ ... ]
19
+ ... })
20
+ >>> # Converts to: "age >= 18 AND status == 'active'"
21
+ """
graflo/filter/onto.py ADDED
@@ -0,0 +1,525 @@
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
+ from typing import Any
30
+
31
+ from graflo.onto import BaseDataclass, BaseEnum, ExpressionFlavor
32
+
33
+ logger = logging.getLogger(__name__)
34
+
35
+
36
+ class LogicalOperator(BaseEnum):
37
+ """Logical operators for combining filter conditions.
38
+
39
+ Attributes:
40
+ AND: Logical AND operation
41
+ OR: Logical OR operation
42
+ NOT: Logical NOT operation
43
+ IMPLICATION: Logical IF-THEN operation
44
+ """
45
+
46
+ AND = "AND"
47
+ OR = "OR"
48
+ NOT = "NOT"
49
+ IMPLICATION = "IF_THEN"
50
+
51
+
52
+ def implication(ops):
53
+ """Evaluate logical implication (IF-THEN).
54
+
55
+ Args:
56
+ ops: Tuple of (antecedent, consequent)
57
+
58
+ Returns:
59
+ bool: True if antecedent is False or consequent is True
60
+ """
61
+ a, b = ops
62
+ return b if a else True
63
+
64
+
65
+ OperatorMapping = MappingProxyType(
66
+ {
67
+ LogicalOperator.AND: all,
68
+ LogicalOperator.OR: any,
69
+ LogicalOperator.IMPLICATION: implication,
70
+ }
71
+ )
72
+
73
+
74
+ class ComparisonOperator(BaseEnum):
75
+ """Comparison operators for field comparisons.
76
+
77
+ Attributes:
78
+ NEQ: Not equal (!=)
79
+ EQ: Equal (==)
80
+ GE: Greater than or equal (>=)
81
+ LE: Less than or equal (<=)
82
+ GT: Greater than (>)
83
+ LT: Less than (<)
84
+ IN: Membership test (IN)
85
+ """
86
+
87
+ NEQ = "!="
88
+ EQ = "=="
89
+ GE = ">="
90
+ LE = "<="
91
+ GT = ">"
92
+ LT = "<"
93
+ IN = "IN"
94
+
95
+
96
+ @dataclasses.dataclass
97
+ class AbsClause(BaseDataclass, metaclass=ABCMeta):
98
+ """Abstract base class for filter clauses.
99
+
100
+ This class defines the interface for all filter clauses, requiring
101
+ implementation of the __call__ method to evaluate or render the clause.
102
+ """
103
+
104
+ @abstractmethod
105
+ def __call__(
106
+ self,
107
+ doc_name,
108
+ kind: ExpressionFlavor = ExpressionFlavor.ARANGO,
109
+ **kwargs,
110
+ ):
111
+ """Evaluate or render the clause.
112
+
113
+ Args:
114
+ doc_name: Name of the document variable in the query
115
+ kind: Target expression flavor (ARANGO, NEO4J, PYTHON)
116
+ **kwargs: Additional arguments for evaluation
117
+
118
+ Returns:
119
+ str: Rendered clause in the target language
120
+ """
121
+ pass
122
+
123
+
124
+ @dataclasses.dataclass
125
+ class LeafClause(AbsClause):
126
+ """Concrete clause for field comparisons.
127
+
128
+ This class represents a single field comparison operation, such as
129
+ "field >= value" or "field IN [values]".
130
+
131
+ Attributes:
132
+ cmp_operator: Comparison operator to use
133
+ value: Value(s) to compare against
134
+ field: Field name to compare
135
+ operator: Optional operator to apply before comparison
136
+ """
137
+
138
+ cmp_operator: ComparisonOperator | None = None
139
+ value: list = dataclasses.field(default_factory=list)
140
+ field: str | None = None
141
+ operator: str | None = None
142
+
143
+ def __post_init__(self):
144
+ """Convert single value to list if necessary."""
145
+ if not isinstance(self.value, list):
146
+ self.value = [self.value]
147
+
148
+ def __call__(
149
+ self,
150
+ doc_name="doc",
151
+ kind: ExpressionFlavor = ExpressionFlavor.ARANGO,
152
+ **kwargs,
153
+ ):
154
+ """Render the leaf clause in the target language.
155
+
156
+ Args:
157
+ doc_name: Name of the document variable
158
+ kind: Target expression flavor
159
+ **kwargs: Additional arguments (may include field_types for REST++)
160
+
161
+ Returns:
162
+ str: Rendered clause
163
+
164
+ Raises:
165
+ ValueError: If kind is not implemented
166
+ """
167
+ if not self.value:
168
+ logger.warning(f"for {self} value is not set : {self.value}")
169
+ if kind == ExpressionFlavor.ARANGO:
170
+ assert self.cmp_operator is not None
171
+ return self._cast_arango(doc_name)
172
+ elif kind == ExpressionFlavor.NEO4J:
173
+ assert self.cmp_operator is not None
174
+ return self._cast_cypher(doc_name)
175
+ elif kind == ExpressionFlavor.TIGERGRAPH:
176
+ assert self.cmp_operator is not None
177
+ # Check if this is for REST++ API (no doc_name prefix)
178
+ if doc_name == "":
179
+ field_types = kwargs.get("field_types")
180
+ return self._cast_restpp(field_types=field_types)
181
+ else:
182
+ return self._cast_tigergraph(doc_name)
183
+ elif kind == ExpressionFlavor.PYTHON:
184
+ return self._cast_python(**kwargs)
185
+ else:
186
+ raise ValueError(f"kind {kind} not implemented")
187
+
188
+ def _cast_value(self):
189
+ """Format the comparison value for query rendering.
190
+
191
+ Returns:
192
+ str: Formatted value string
193
+ """
194
+ value = f"{self.value[0]}" if len(self.value) == 1 else f"{self.value}"
195
+ if len(self.value) == 1:
196
+ if isinstance(self.value[0], str):
197
+ value = f'"{self.value[0]}"'
198
+ elif self.value[0] is None:
199
+ value = "null"
200
+ else:
201
+ value = f"{self.value[0]}"
202
+ return value
203
+
204
+ def _cast_arango(self, doc_name):
205
+ """Render the clause in AQL format.
206
+
207
+ Args:
208
+ doc_name: Document variable name
209
+
210
+ Returns:
211
+ str: AQL clause
212
+ """
213
+ const = self._cast_value()
214
+
215
+ lemma = f"{self.cmp_operator} {const}"
216
+ if self.operator is not None:
217
+ lemma = f"{self.operator} {lemma}"
218
+
219
+ if self.field is not None:
220
+ lemma = f'{doc_name}["{self.field}"] {lemma}'
221
+ return lemma
222
+
223
+ def _cast_cypher(self, doc_name):
224
+ """Render the clause in Cypher format.
225
+
226
+ Args:
227
+ doc_name: Document variable name
228
+
229
+ Returns:
230
+ str: Cypher clause
231
+ """
232
+ const = self._cast_value()
233
+ if self.cmp_operator == ComparisonOperator.EQ:
234
+ cmp_operator = "="
235
+ else:
236
+ cmp_operator = self.cmp_operator
237
+ lemma = f"{cmp_operator} {const}"
238
+ if self.operator is not None:
239
+ lemma = f"{self.operator} {lemma}"
240
+
241
+ if self.field is not None:
242
+ lemma = f"{doc_name}.{self.field} {lemma}"
243
+ return lemma
244
+
245
+ def _cast_tigergraph(self, doc_name):
246
+ """Render the clause in GSQL format.
247
+
248
+ Args:
249
+ doc_name: Document variable name (typically "v" for vertex)
250
+
251
+ Returns:
252
+ str: GSQL clause
253
+ """
254
+ const = self._cast_value()
255
+ # GSQL supports both == and =, but == is more common
256
+ if self.cmp_operator == ComparisonOperator.EQ:
257
+ cmp_operator = "=="
258
+ else:
259
+ cmp_operator = self.cmp_operator
260
+ lemma = f"{cmp_operator} {const}"
261
+ if self.operator is not None:
262
+ lemma = f"{self.operator} {lemma}"
263
+
264
+ if self.field is not None:
265
+ lemma = f"{doc_name}.{self.field} {lemma}"
266
+ return lemma
267
+
268
+ def _cast_restpp(self, field_types: dict[str, Any] | None = None):
269
+ """Render the clause in REST++ filter format.
270
+
271
+ REST++ filter format: "field=value" or "field>value" etc.
272
+ Format: fieldoperatorvalue (no spaces, quotes for string values)
273
+ Example: "hindex=10" or "hindex>20" or 'name="John"'
274
+
275
+ Args:
276
+ field_types: Optional mapping of field names to FieldType enum values or type strings
277
+
278
+ Returns:
279
+ str: REST++ filter clause
280
+ """
281
+ if not self.field:
282
+ return ""
283
+
284
+ # Map operator
285
+ if self.cmp_operator == ComparisonOperator.EQ:
286
+ op_str = "="
287
+ elif self.cmp_operator == ComparisonOperator.NEQ:
288
+ op_str = "!="
289
+ elif self.cmp_operator == ComparisonOperator.GT:
290
+ op_str = ">"
291
+ elif self.cmp_operator == ComparisonOperator.LT:
292
+ op_str = "<"
293
+ elif self.cmp_operator == ComparisonOperator.GE:
294
+ op_str = ">="
295
+ elif self.cmp_operator == ComparisonOperator.LE:
296
+ op_str = "<="
297
+ else:
298
+ op_str = str(self.cmp_operator)
299
+
300
+ # Format value for REST++ API
301
+ # Use field_types to determine if value should be quoted
302
+ # Default: if no explicit type information, treat as string (quote it)
303
+ value = self.value[0] if self.value else None
304
+ if value is None:
305
+ value_str = "null"
306
+ elif isinstance(value, (int, float)):
307
+ # Numeric values: pass as string without quotes
308
+ value_str = str(value)
309
+ elif isinstance(value, str):
310
+ # Check field type to determine if it's a string field
311
+ is_string_field = True # Default: treat as string unless explicitly numeric
312
+ if field_types and self.field in field_types:
313
+ field_type = field_types[self.field]
314
+ # Handle FieldType enum or string type
315
+ if hasattr(field_type, "value"):
316
+ # It's a FieldType enum
317
+ field_type_str = field_type.value
318
+ else:
319
+ # It's a string
320
+ field_type_str = str(field_type).upper()
321
+ # Check if it's explicitly a numeric type
322
+ numeric_types = ("INT", "UINT", "FLOAT", "DOUBLE")
323
+ if field_type_str in numeric_types:
324
+ # Explicitly numeric type, don't quote
325
+ is_string_field = False
326
+ else:
327
+ # Explicitly string type or other (STRING, VARCHAR, TEXT, DATETIME, BOOL, etc.)
328
+ # Quote it
329
+ is_string_field = True
330
+ # If no field_types info, default to treating as string (quote it)
331
+
332
+ if is_string_field:
333
+ value_str = f'"{value}"'
334
+ else:
335
+ # Numeric value (explicitly numeric type)
336
+ value_str = value
337
+ else:
338
+ value_str = str(value)
339
+
340
+ # REST++ format: fieldoperatorvalue (no spaces)
341
+ # Example: hindex=10, hindex>20, name="John"
342
+ return f"{self.field}{op_str}{value_str}"
343
+
344
+ def _cast_python(self, **kwargs):
345
+ """Evaluate the clause in Python.
346
+
347
+ Args:
348
+ **kwargs: Additional arguments
349
+
350
+ Returns:
351
+ bool: Evaluation result
352
+ """
353
+ if self.field is not None:
354
+ field = kwargs.pop(self.field, None)
355
+ if field is not None and self.operator is not None:
356
+ foo = getattr(field, self.operator)
357
+ return foo(self.value[0])
358
+ return False
359
+
360
+
361
+ @dataclasses.dataclass
362
+ class Clause(AbsClause):
363
+ """Composite clause combining multiple sub-clauses.
364
+
365
+ This class represents a logical combination of multiple filter clauses,
366
+ such as "clause1 AND clause2" or "NOT clause1".
367
+
368
+ Attributes:
369
+ operator: Logical operator to combine clauses
370
+ deps: List of dependent clauses
371
+ """
372
+
373
+ operator: LogicalOperator
374
+ deps: list[AbsClause]
375
+
376
+ def __call__(
377
+ self,
378
+ doc_name="doc",
379
+ kind: ExpressionFlavor = ExpressionFlavor.ARANGO,
380
+ **kwargs,
381
+ ):
382
+ """Render the composite clause in the target language.
383
+
384
+ Args:
385
+ doc_name: Document variable name
386
+ kind: Target expression flavor
387
+ **kwargs: Additional arguments
388
+
389
+ Returns:
390
+ str: Rendered clause
391
+
392
+ Raises:
393
+ ValueError: If operator and dependencies don't match
394
+ """
395
+ if kind in (
396
+ ExpressionFlavor.ARANGO,
397
+ ExpressionFlavor.NEO4J,
398
+ ExpressionFlavor.TIGERGRAPH,
399
+ ):
400
+ return self._cast_generic(doc_name=doc_name, kind=kind)
401
+ elif kind == ExpressionFlavor.PYTHON:
402
+ return self._cast_python(kind=kind, **kwargs)
403
+
404
+ def _cast_generic(self, doc_name, kind):
405
+ """Render the clause in a generic format.
406
+
407
+ Args:
408
+ doc_name: Document variable name
409
+ kind: Target expression flavor
410
+
411
+ Returns:
412
+ str: Rendered clause
413
+
414
+ Raises:
415
+ ValueError: If operator and dependencies don't match
416
+ """
417
+ if len(self.deps) == 1:
418
+ if self.operator == LogicalOperator.NOT:
419
+ result = self.deps[0](kind=kind, doc_name=doc_name)
420
+ # REST++ format uses ! prefix, not "NOT " prefix
421
+ if doc_name == "" and kind == ExpressionFlavor.TIGERGRAPH:
422
+ return f"!{result}"
423
+ else:
424
+ return f"{self.operator} {result}"
425
+ else:
426
+ raise ValueError(
427
+ f" length of deps = {len(self.deps)} but operator is not"
428
+ f" {LogicalOperator.NOT}"
429
+ )
430
+ else:
431
+ deps_str = [item(kind=kind, doc_name=doc_name) for item in self.deps]
432
+ # REST++ format uses && and || instead of AND and OR
433
+ if doc_name == "" and kind == ExpressionFlavor.TIGERGRAPH:
434
+ if self.operator == LogicalOperator.AND:
435
+ return " && ".join(deps_str)
436
+ elif self.operator == LogicalOperator.OR:
437
+ return " || ".join(deps_str)
438
+ else:
439
+ return f" {self.operator} ".join(deps_str)
440
+ else:
441
+ return f" {self.operator} ".join(deps_str)
442
+
443
+ def _cast_python(self, kind, **kwargs):
444
+ """Evaluate the clause in Python.
445
+
446
+ Args:
447
+ kind: Expression flavor
448
+ **kwargs: Additional arguments
449
+
450
+ Returns:
451
+ bool: Evaluation result
452
+
453
+ Raises:
454
+ ValueError: If operator and dependencies don't match
455
+ """
456
+ if len(self.deps) == 1:
457
+ if self.operator == LogicalOperator.NOT:
458
+ return not self.deps[0](kind=kind, **kwargs)
459
+ else:
460
+ raise ValueError(
461
+ f" length of deps = {len(self.deps)} but operator is not"
462
+ f" {LogicalOperator.NOT}"
463
+ )
464
+ else:
465
+ return OperatorMapping[self.operator](
466
+ [item(kind=kind, **kwargs) for item in self.deps]
467
+ )
468
+
469
+
470
+ @dataclasses.dataclass
471
+ class Expression(AbsClause):
472
+ """Factory class for creating filter expressions.
473
+
474
+ This class provides methods to create filter expressions from dictionaries
475
+ and evaluate them in different languages.
476
+ """
477
+
478
+ @classmethod
479
+ def from_dict(cls, current):
480
+ """Create a filter expression from a dictionary.
481
+
482
+ Args:
483
+ current: Dictionary or list representing the filter expression
484
+
485
+ Returns:
486
+ AbsClause: Created filter expression
487
+
488
+ Example:
489
+ >>> expr = Expression.from_dict({
490
+ ... "AND": [
491
+ ... {"field": "age", "cmp_operator": ">=", "value": 18},
492
+ ... {"field": "status", "cmp_operator": "==", "value": "active"}
493
+ ... ]
494
+ ... })
495
+ """
496
+ if isinstance(current, list):
497
+ if current[0] in ComparisonOperator:
498
+ return LeafClause(*current)
499
+ elif current[0] in LogicalOperator:
500
+ return Clause(*current)
501
+ elif isinstance(current, dict):
502
+ k = list(current.keys())[0]
503
+ if k in LogicalOperator:
504
+ clauses = [cls.from_dict(v) for v in current[k]]
505
+ return Clause(operator=k, deps=clauses)
506
+ else:
507
+ return LeafClause(**current)
508
+
509
+ def __call__(
510
+ self,
511
+ doc_name="doc",
512
+ kind: ExpressionFlavor = ExpressionFlavor.ARANGO,
513
+ **kwargs,
514
+ ):
515
+ """Evaluate the expression in the target language.
516
+
517
+ Args:
518
+ doc_name: Document variable name
519
+ kind: Target expression flavor
520
+ **kwargs: Additional arguments
521
+
522
+ Returns:
523
+ str: Rendered expression
524
+ """
525
+ 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