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.
- graflo/README.md +18 -0
- graflo/__init__.py +70 -0
- graflo/architecture/__init__.py +38 -0
- graflo/architecture/actor.py +1276 -0
- graflo/architecture/actor_util.py +450 -0
- graflo/architecture/edge.py +418 -0
- graflo/architecture/onto.py +376 -0
- graflo/architecture/onto_sql.py +54 -0
- graflo/architecture/resource.py +163 -0
- graflo/architecture/schema.py +135 -0
- graflo/architecture/transform.py +292 -0
- graflo/architecture/util.py +89 -0
- graflo/architecture/vertex.py +562 -0
- graflo/caster.py +736 -0
- graflo/cli/__init__.py +14 -0
- graflo/cli/ingest.py +203 -0
- graflo/cli/manage_dbs.py +197 -0
- graflo/cli/plot_schema.py +132 -0
- graflo/cli/xml2json.py +93 -0
- graflo/data_source/__init__.py +48 -0
- graflo/data_source/api.py +339 -0
- graflo/data_source/base.py +95 -0
- graflo/data_source/factory.py +304 -0
- graflo/data_source/file.py +148 -0
- graflo/data_source/memory.py +70 -0
- graflo/data_source/registry.py +82 -0
- graflo/data_source/sql.py +183 -0
- graflo/db/__init__.py +44 -0
- graflo/db/arango/__init__.py +22 -0
- graflo/db/arango/conn.py +1025 -0
- graflo/db/arango/query.py +180 -0
- graflo/db/arango/util.py +88 -0
- graflo/db/conn.py +377 -0
- graflo/db/connection/__init__.py +6 -0
- graflo/db/connection/config_mapping.py +18 -0
- graflo/db/connection/onto.py +717 -0
- graflo/db/connection/wsgi.py +29 -0
- graflo/db/manager.py +119 -0
- graflo/db/neo4j/__init__.py +16 -0
- graflo/db/neo4j/conn.py +639 -0
- graflo/db/postgres/__init__.py +37 -0
- graflo/db/postgres/conn.py +948 -0
- graflo/db/postgres/fuzzy_matcher.py +281 -0
- graflo/db/postgres/heuristics.py +133 -0
- graflo/db/postgres/inference_utils.py +428 -0
- graflo/db/postgres/resource_mapping.py +273 -0
- graflo/db/postgres/schema_inference.py +372 -0
- graflo/db/postgres/types.py +148 -0
- graflo/db/postgres/util.py +87 -0
- graflo/db/tigergraph/__init__.py +9 -0
- graflo/db/tigergraph/conn.py +2365 -0
- graflo/db/tigergraph/onto.py +26 -0
- graflo/db/util.py +49 -0
- graflo/filter/__init__.py +21 -0
- graflo/filter/onto.py +525 -0
- graflo/logging.conf +22 -0
- graflo/onto.py +312 -0
- graflo/plot/__init__.py +17 -0
- graflo/plot/plotter.py +616 -0
- graflo/util/__init__.py +23 -0
- graflo/util/chunker.py +807 -0
- graflo/util/merge.py +150 -0
- graflo/util/misc.py +37 -0
- graflo/util/onto.py +422 -0
- graflo/util/transform.py +454 -0
- graflo-1.3.7.dist-info/METADATA +243 -0
- graflo-1.3.7.dist-info/RECORD +70 -0
- graflo-1.3.7.dist-info/WHEEL +4 -0
- graflo-1.3.7.dist-info/entry_points.txt +5 -0
- 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
|