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,376 @@
|
|
|
1
|
+
"""Core ontology and data structures for graph database operations.
|
|
2
|
+
|
|
3
|
+
This module defines the fundamental data structures and types used throughout the graflo
|
|
4
|
+
package for working with graph databases. It provides:
|
|
5
|
+
|
|
6
|
+
- Core data types for vertices and edges
|
|
7
|
+
- Database index configurations
|
|
8
|
+
- Graph container implementations
|
|
9
|
+
- Edge mapping and casting utilities
|
|
10
|
+
- Action context for graph transformations
|
|
11
|
+
|
|
12
|
+
The module is designed to be database-agnostic, supporting both ArangoDB and Neo4j through
|
|
13
|
+
the DBFlavor enum. It provides a unified interface for working with graph data structures
|
|
14
|
+
while allowing for database-specific optimizations and features.
|
|
15
|
+
|
|
16
|
+
Key Components:
|
|
17
|
+
- EdgeMapping: Defines how edges are mapped between vertices
|
|
18
|
+
- IndexType: Supported database index types
|
|
19
|
+
- EdgeType: Types of edge handling in the graph database
|
|
20
|
+
- GraphContainer: Main container for graph data
|
|
21
|
+
- ActionContext: Context for graph transformation operations
|
|
22
|
+
|
|
23
|
+
Example:
|
|
24
|
+
>>> container = GraphContainer(vertices={}, edges={}, linear=[])
|
|
25
|
+
>>> index = Index(fields=["name", "age"], type=IndexType.PERSISTENT)
|
|
26
|
+
>>> context = ActionContext()
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from __future__ import annotations
|
|
30
|
+
|
|
31
|
+
import dataclasses
|
|
32
|
+
import logging
|
|
33
|
+
from abc import ABCMeta
|
|
34
|
+
from collections import defaultdict
|
|
35
|
+
from typing import Any, TypeAlias
|
|
36
|
+
|
|
37
|
+
from dataclass_wizard import JSONWizard, YAMLWizard
|
|
38
|
+
|
|
39
|
+
from graflo.onto import BaseDataclass, BaseEnum, DBFlavor
|
|
40
|
+
from graflo.util.transform import pick_unique_dict
|
|
41
|
+
|
|
42
|
+
# type for vertex or edge name (index)
|
|
43
|
+
EdgeId: TypeAlias = tuple[str, str, str | None]
|
|
44
|
+
GraphEntity: TypeAlias = str | EdgeId
|
|
45
|
+
|
|
46
|
+
logger = logging.getLogger(__name__)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class EdgeMapping(BaseEnum):
|
|
50
|
+
"""Defines how edges are mapped between vertices.
|
|
51
|
+
|
|
52
|
+
ALL: Maps all vertices to all vertices
|
|
53
|
+
ONE_N: Maps one vertex to many vertices
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
ALL = "all"
|
|
57
|
+
ONE_N = "1-n"
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class EncodingType(BaseEnum):
|
|
61
|
+
"""Supported character encodings for data input/output."""
|
|
62
|
+
|
|
63
|
+
ISO_8859 = "ISO-8859-1"
|
|
64
|
+
UTF_8 = "utf-8"
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class IndexType(BaseEnum):
|
|
68
|
+
"""Types of database indexes supported.
|
|
69
|
+
|
|
70
|
+
PERSISTENT: Standard persistent index
|
|
71
|
+
HASH: Hash-based index for fast lookups
|
|
72
|
+
SKIPLIST: Sorted index using skip list data structure
|
|
73
|
+
FULLTEXT: Index optimized for text search
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
PERSISTENT = "persistent"
|
|
77
|
+
HASH = "hash"
|
|
78
|
+
SKIPLIST = "skiplist"
|
|
79
|
+
FULLTEXT = "fulltext"
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class EdgeType(BaseEnum):
|
|
83
|
+
"""Defines how edges are handled in the graph database.
|
|
84
|
+
|
|
85
|
+
INDIRECT: Defined as a collection with indexes, may be used after data ingestion
|
|
86
|
+
DIRECT: In addition to indexes, these edges are generated during ingestion
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
INDIRECT = "indirect"
|
|
90
|
+
DIRECT = "direct"
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@dataclasses.dataclass
|
|
94
|
+
class ABCFields(BaseDataclass, metaclass=ABCMeta):
|
|
95
|
+
"""Abstract base class for entities that have fields.
|
|
96
|
+
|
|
97
|
+
Attributes:
|
|
98
|
+
name: Optional name of the entity
|
|
99
|
+
fields: List of field names
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
name: str | None = None
|
|
103
|
+
fields: list[str] = dataclasses.field(default_factory=list)
|
|
104
|
+
keep_vertex_name: bool = True
|
|
105
|
+
|
|
106
|
+
def cfield(self, x: str) -> str:
|
|
107
|
+
"""Creates a composite field name by combining the entity name with a field name.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
x: Field name to combine with entity name
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
Composite field name in format "entity@field"
|
|
114
|
+
"""
|
|
115
|
+
return f"{self.name}@{x}" if self.keep_vertex_name else x
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
@dataclasses.dataclass
|
|
119
|
+
class Weight(ABCFields):
|
|
120
|
+
"""Defines weight configuration for edges.
|
|
121
|
+
|
|
122
|
+
Attributes:
|
|
123
|
+
map: Dictionary mapping field values to weights
|
|
124
|
+
filter: Dictionary of filter conditions for weights
|
|
125
|
+
"""
|
|
126
|
+
|
|
127
|
+
map: dict = dataclasses.field(default_factory=dict)
|
|
128
|
+
filter: dict = dataclasses.field(default_factory=dict)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
@dataclasses.dataclass
|
|
132
|
+
class Index(BaseDataclass):
|
|
133
|
+
"""Configuration for database indexes.
|
|
134
|
+
|
|
135
|
+
Attributes:
|
|
136
|
+
name: Optional name of the index
|
|
137
|
+
fields: List of fields to index
|
|
138
|
+
unique: Whether the index enforces uniqueness
|
|
139
|
+
type: Type of index to create
|
|
140
|
+
deduplicate: Whether to deduplicate index entries
|
|
141
|
+
sparse: Whether to create a sparse index
|
|
142
|
+
exclude_edge_endpoints: Whether to exclude edge endpoints from index
|
|
143
|
+
"""
|
|
144
|
+
|
|
145
|
+
name: str | None = None
|
|
146
|
+
fields: list[str] = dataclasses.field(default_factory=list)
|
|
147
|
+
unique: bool = True
|
|
148
|
+
type: IndexType = IndexType.PERSISTENT
|
|
149
|
+
deduplicate: bool = True
|
|
150
|
+
sparse: bool = False
|
|
151
|
+
exclude_edge_endpoints: bool = False
|
|
152
|
+
|
|
153
|
+
def __iter__(self):
|
|
154
|
+
"""Iterate over the indexed fields."""
|
|
155
|
+
return iter(self.fields)
|
|
156
|
+
|
|
157
|
+
def db_form(self, db_type: DBFlavor) -> dict:
|
|
158
|
+
"""Convert index configuration to database-specific format.
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
db_type: Type of database (ARANGO or NEO4J)
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
Dictionary of index configuration in database-specific format
|
|
165
|
+
|
|
166
|
+
Raises:
|
|
167
|
+
ValueError: If db_type is not supported
|
|
168
|
+
"""
|
|
169
|
+
r = self.to_dict()
|
|
170
|
+
if db_type == DBFlavor.ARANGO:
|
|
171
|
+
_ = r.pop("name")
|
|
172
|
+
_ = r.pop("exclude_edge_endpoints")
|
|
173
|
+
elif db_type == DBFlavor.NEO4J:
|
|
174
|
+
pass
|
|
175
|
+
else:
|
|
176
|
+
raise ValueError(f"Unknown db_type {db_type}")
|
|
177
|
+
|
|
178
|
+
return r
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
class ItemsView:
|
|
182
|
+
"""View class for iterating over vertices and edges in a GraphContainer."""
|
|
183
|
+
|
|
184
|
+
def __init__(self, gc: GraphContainer):
|
|
185
|
+
self._dictlike = gc
|
|
186
|
+
|
|
187
|
+
def __iter__(self):
|
|
188
|
+
"""Iterate over vertices and edges in the container."""
|
|
189
|
+
for key in self._dictlike.vertices:
|
|
190
|
+
yield key, self._dictlike.vertices[key]
|
|
191
|
+
for key in self._dictlike.edges:
|
|
192
|
+
yield key, self._dictlike.edges[key]
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
@dataclasses.dataclass
|
|
196
|
+
class GraphContainer(BaseDataclass):
|
|
197
|
+
"""Container for graph data including vertices and edges.
|
|
198
|
+
|
|
199
|
+
Attributes:
|
|
200
|
+
vertices: Dictionary mapping vertex names to lists of vertex data
|
|
201
|
+
edges: Dictionary mapping edge IDs to lists of edge data
|
|
202
|
+
linear: List of default dictionaries containing linear data
|
|
203
|
+
"""
|
|
204
|
+
|
|
205
|
+
vertices: dict[str, list]
|
|
206
|
+
edges: dict[tuple[str, str, str | None], list]
|
|
207
|
+
linear: list[defaultdict[str | tuple[str, str, str | None], list[Any]]]
|
|
208
|
+
|
|
209
|
+
def __post_init__(self):
|
|
210
|
+
pass
|
|
211
|
+
|
|
212
|
+
def items(self):
|
|
213
|
+
"""Get an ItemsView of the container's contents."""
|
|
214
|
+
return ItemsView(self)
|
|
215
|
+
|
|
216
|
+
def pick_unique(self):
|
|
217
|
+
"""Remove duplicate entries from vertices and edges."""
|
|
218
|
+
for k, v in self.vertices.items():
|
|
219
|
+
self.vertices[k] = pick_unique_dict(v)
|
|
220
|
+
for k, v in self.edges.items():
|
|
221
|
+
self.edges[k] = pick_unique_dict(v)
|
|
222
|
+
|
|
223
|
+
def loop_over_relations(self, edge_def: tuple[str, str, str | None]):
|
|
224
|
+
"""Iterate over edges matching the given edge definition.
|
|
225
|
+
|
|
226
|
+
Args:
|
|
227
|
+
edge_def: Tuple of (source, target, optional_purpose)
|
|
228
|
+
|
|
229
|
+
Returns:
|
|
230
|
+
Generator yielding matching edge IDs
|
|
231
|
+
"""
|
|
232
|
+
source, target, _ = edge_def
|
|
233
|
+
return (ed for ed in self.edges if source == ed[0] and target == ed[1])
|
|
234
|
+
|
|
235
|
+
@classmethod
|
|
236
|
+
def from_docs_list(
|
|
237
|
+
cls, list_default_dicts: list[defaultdict[GraphEntity, list]]
|
|
238
|
+
) -> GraphContainer:
|
|
239
|
+
"""Create a GraphContainer from a list of default dictionaries.
|
|
240
|
+
|
|
241
|
+
Args:
|
|
242
|
+
list_default_dicts: List of default dictionaries containing vertex and edge data
|
|
243
|
+
|
|
244
|
+
Returns:
|
|
245
|
+
New GraphContainer instance
|
|
246
|
+
|
|
247
|
+
Raises:
|
|
248
|
+
AssertionError: If edge IDs are not properly formatted
|
|
249
|
+
"""
|
|
250
|
+
vdict: defaultdict[str, list] = defaultdict(list)
|
|
251
|
+
edict: defaultdict[tuple[str, str, str | None], list] = defaultdict(list)
|
|
252
|
+
|
|
253
|
+
for d in list_default_dicts:
|
|
254
|
+
for k, v in d.items():
|
|
255
|
+
if isinstance(k, str):
|
|
256
|
+
vdict[k].extend(v)
|
|
257
|
+
elif isinstance(k, tuple):
|
|
258
|
+
assert (
|
|
259
|
+
len(k) == 3
|
|
260
|
+
and all(isinstance(item, str) for item in k[:-1])
|
|
261
|
+
and isinstance(k[-1], (str, type(None)))
|
|
262
|
+
)
|
|
263
|
+
edict[k].extend(v)
|
|
264
|
+
return GraphContainer(
|
|
265
|
+
vertices=dict(vdict.items()),
|
|
266
|
+
edges=dict(edict.items()),
|
|
267
|
+
linear=list_default_dicts,
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
class EdgeCastingType(BaseEnum):
|
|
272
|
+
"""Types of edge casting supported.
|
|
273
|
+
|
|
274
|
+
PAIR: Edges are cast as pairs of vertices
|
|
275
|
+
PRODUCT: Edges are cast as combinations of vertex sets
|
|
276
|
+
"""
|
|
277
|
+
|
|
278
|
+
PAIR = "pair"
|
|
279
|
+
PRODUCT = "product"
|
|
280
|
+
COMBINATIONS = "combinations"
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def inner_factory_vertex() -> defaultdict[LocationIndex, list]:
|
|
284
|
+
"""Create a default dictionary for vertex data."""
|
|
285
|
+
return defaultdict(list)
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def outer_factory() -> defaultdict[str, defaultdict[LocationIndex, list]]:
|
|
289
|
+
"""Create a nested default dictionary for vertex data."""
|
|
290
|
+
return defaultdict(inner_factory_vertex)
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def dd_factory() -> defaultdict[GraphEntity, list]:
|
|
294
|
+
"""Create a default dictionary for graph entity data."""
|
|
295
|
+
return defaultdict(list)
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
@dataclasses.dataclass(kw_only=True)
|
|
299
|
+
class VertexRep(BaseDataclass):
|
|
300
|
+
"""Context for graph transformation actions.
|
|
301
|
+
|
|
302
|
+
Attributes:
|
|
303
|
+
vertex: doc representing a vertex
|
|
304
|
+
ctx: context (for edge definition upstream
|
|
305
|
+
"""
|
|
306
|
+
|
|
307
|
+
vertex: dict
|
|
308
|
+
ctx: dict
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
@dataclasses.dataclass(frozen=True, eq=True)
|
|
312
|
+
class LocationIndex(JSONWizard, YAMLWizard):
|
|
313
|
+
path: tuple[str | int | None, ...] = dataclasses.field(default_factory=tuple)
|
|
314
|
+
|
|
315
|
+
def extend(self, extension: tuple[str | int | None, ...]) -> LocationIndex:
|
|
316
|
+
return LocationIndex((*self.path, *extension))
|
|
317
|
+
|
|
318
|
+
def depth(self):
|
|
319
|
+
return len(self.path)
|
|
320
|
+
|
|
321
|
+
def congruence_measure(self, other: LocationIndex):
|
|
322
|
+
neq_position = 0
|
|
323
|
+
for step_a, step_b in zip(self.path, other.path):
|
|
324
|
+
if step_a != step_b:
|
|
325
|
+
break
|
|
326
|
+
neq_position += 1
|
|
327
|
+
return neq_position
|
|
328
|
+
|
|
329
|
+
def filter(self, lindex_list: list[LocationIndex]) -> list[LocationIndex]:
|
|
330
|
+
return [
|
|
331
|
+
t
|
|
332
|
+
for t in lindex_list
|
|
333
|
+
if t.depth() >= self.depth() and t.path[: self.depth()] == self.path
|
|
334
|
+
]
|
|
335
|
+
|
|
336
|
+
def __lt__(self, other: LocationIndex):
|
|
337
|
+
return len(self.path) < len(other.path)
|
|
338
|
+
|
|
339
|
+
def __contains__(self, item):
|
|
340
|
+
return item in self.path
|
|
341
|
+
|
|
342
|
+
def __len__(self):
|
|
343
|
+
return len(self.path)
|
|
344
|
+
|
|
345
|
+
def __iter__(self):
|
|
346
|
+
return iter(self.path)
|
|
347
|
+
|
|
348
|
+
def __getitem__(self, item):
|
|
349
|
+
return self.path[item]
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
@dataclasses.dataclass(kw_only=True)
|
|
353
|
+
class ActionContext(BaseDataclass):
|
|
354
|
+
"""Context for graph transformation actions.
|
|
355
|
+
|
|
356
|
+
Attributes:
|
|
357
|
+
acc_vertex: Local accumulation of vertices
|
|
358
|
+
acc_global: Global accumulation of graph entities
|
|
359
|
+
buffer_vertex: Buffer for vertex data
|
|
360
|
+
buffer_transforms: Buffer for transforms data
|
|
361
|
+
target_vertices: Set of target vertex names indicating user intention
|
|
362
|
+
"""
|
|
363
|
+
|
|
364
|
+
acc_vertex: defaultdict[str, defaultdict[LocationIndex, list]] = dataclasses.field(
|
|
365
|
+
default_factory=outer_factory
|
|
366
|
+
)
|
|
367
|
+
acc_global: defaultdict[GraphEntity, list] = dataclasses.field(
|
|
368
|
+
default_factory=dd_factory
|
|
369
|
+
)
|
|
370
|
+
buffer_vertex: defaultdict[GraphEntity, list] = dataclasses.field(
|
|
371
|
+
default_factory=lambda: defaultdict(list)
|
|
372
|
+
)
|
|
373
|
+
buffer_transforms: defaultdict[LocationIndex, list[dict]] = dataclasses.field(
|
|
374
|
+
default_factory=lambda: defaultdict(list)
|
|
375
|
+
)
|
|
376
|
+
target_vertices: set[str] = dataclasses.field(default_factory=set)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from pydantic import BaseModel
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class ColumnInfo(BaseModel):
|
|
5
|
+
"""Column information from PostgreSQL table."""
|
|
6
|
+
|
|
7
|
+
name: str
|
|
8
|
+
type: str
|
|
9
|
+
description: str = ""
|
|
10
|
+
is_nullable: str = "YES"
|
|
11
|
+
column_default: str | None = None
|
|
12
|
+
is_pk: bool = False
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ForeignKeyInfo(BaseModel):
|
|
16
|
+
"""Foreign key relationship information."""
|
|
17
|
+
|
|
18
|
+
column: str
|
|
19
|
+
references_table: str
|
|
20
|
+
references_column: str | None = None
|
|
21
|
+
constraint_name: str | None = None
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class VertexTableInfo(BaseModel):
|
|
25
|
+
"""Vertex table information from schema introspection."""
|
|
26
|
+
|
|
27
|
+
name: str
|
|
28
|
+
schema_name: str
|
|
29
|
+
columns: list[ColumnInfo]
|
|
30
|
+
primary_key: list[str]
|
|
31
|
+
foreign_keys: list[ForeignKeyInfo]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class EdgeTableInfo(BaseModel):
|
|
35
|
+
"""Edge table information from schema introspection."""
|
|
36
|
+
|
|
37
|
+
name: str
|
|
38
|
+
schema_name: str
|
|
39
|
+
columns: list[ColumnInfo]
|
|
40
|
+
primary_key: list[str]
|
|
41
|
+
foreign_keys: list[ForeignKeyInfo]
|
|
42
|
+
source_table: str
|
|
43
|
+
target_table: str
|
|
44
|
+
source_column: str
|
|
45
|
+
target_column: str
|
|
46
|
+
relation: str | None = None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class SchemaIntrospectionResult(BaseModel):
|
|
50
|
+
"""Result of PostgreSQL schema introspection."""
|
|
51
|
+
|
|
52
|
+
vertex_tables: list[VertexTableInfo]
|
|
53
|
+
edge_tables: list[EdgeTableInfo]
|
|
54
|
+
schema_name: str
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"""Resource management and processing for graph databases.
|
|
2
|
+
|
|
3
|
+
This module provides the core resource handling functionality for graph databases.
|
|
4
|
+
It defines how data resources are processed, transformed, and mapped to graph
|
|
5
|
+
structures through a system of actors and transformations.
|
|
6
|
+
|
|
7
|
+
Key Components:
|
|
8
|
+
- Resource: Main class for resource processing and transformation
|
|
9
|
+
- ActorWrapper: Wrapper for processing actors
|
|
10
|
+
- ActionContext: Context for processing actions
|
|
11
|
+
|
|
12
|
+
The resource system allows for:
|
|
13
|
+
- Data encoding and transformation
|
|
14
|
+
- Vertex and edge creation
|
|
15
|
+
- Weight management
|
|
16
|
+
- Collection merging
|
|
17
|
+
- Type casting and validation
|
|
18
|
+
|
|
19
|
+
Example:
|
|
20
|
+
>>> resource = Resource(
|
|
21
|
+
... resource_name="users",
|
|
22
|
+
... apply=[VertexActor("user"), EdgeActor("follows")],
|
|
23
|
+
... encoding=EncodingType.UTF_8
|
|
24
|
+
... )
|
|
25
|
+
>>> result = resource(doc)
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
import dataclasses
|
|
29
|
+
import logging
|
|
30
|
+
from collections import defaultdict
|
|
31
|
+
from typing import Callable
|
|
32
|
+
|
|
33
|
+
from dataclass_wizard import JSONWizard
|
|
34
|
+
|
|
35
|
+
from graflo.architecture.actor import (
|
|
36
|
+
ActorWrapper,
|
|
37
|
+
)
|
|
38
|
+
from graflo.architecture.edge import Edge, EdgeConfig
|
|
39
|
+
from graflo.architecture.onto import (
|
|
40
|
+
ActionContext,
|
|
41
|
+
EncodingType,
|
|
42
|
+
GraphEntity,
|
|
43
|
+
)
|
|
44
|
+
from graflo.architecture.transform import ProtoTransform
|
|
45
|
+
from graflo.architecture.vertex import (
|
|
46
|
+
VertexConfig,
|
|
47
|
+
)
|
|
48
|
+
from graflo.onto import BaseDataclass
|
|
49
|
+
|
|
50
|
+
logger = logging.getLogger(__name__)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclasses.dataclass(kw_only=True)
|
|
54
|
+
class Resource(BaseDataclass, JSONWizard):
|
|
55
|
+
"""Resource configuration and processing.
|
|
56
|
+
|
|
57
|
+
This class represents a data resource that can be processed and transformed
|
|
58
|
+
into graph structures. It manages the processing pipeline through actors
|
|
59
|
+
and handles data encoding, transformation, and mapping.
|
|
60
|
+
|
|
61
|
+
Attributes:
|
|
62
|
+
resource_name: Name of the resource
|
|
63
|
+
apply: List of actors to apply in sequence
|
|
64
|
+
encoding: Data encoding type (default: UTF_8)
|
|
65
|
+
merge_collections: List of collections to merge
|
|
66
|
+
extra_weights: List of additional edge weights
|
|
67
|
+
types: Dictionary of field type mappings
|
|
68
|
+
root: Root actor wrapper for processing
|
|
69
|
+
vertex_config: Configuration for vertices
|
|
70
|
+
edge_config: Configuration for edges
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
resource_name: str
|
|
74
|
+
apply: list
|
|
75
|
+
encoding: EncodingType = EncodingType.UTF_8
|
|
76
|
+
merge_collections: list[str] = dataclasses.field(default_factory=list)
|
|
77
|
+
extra_weights: list[Edge] = dataclasses.field(default_factory=list)
|
|
78
|
+
types: dict[str, str] = dataclasses.field(default_factory=dict)
|
|
79
|
+
edge_greedy: bool = True
|
|
80
|
+
|
|
81
|
+
def __post_init__(self):
|
|
82
|
+
"""Initialize the resource after dataclass initialization.
|
|
83
|
+
|
|
84
|
+
Sets up the actor wrapper and type mappings. Evaluates type expressions
|
|
85
|
+
for field type casting.
|
|
86
|
+
|
|
87
|
+
Raises:
|
|
88
|
+
Exception: If type evaluation fails for any field
|
|
89
|
+
"""
|
|
90
|
+
self.root = ActorWrapper(*self.apply)
|
|
91
|
+
self._types: dict[str, Callable] = dict()
|
|
92
|
+
self.vertex_config: VertexConfig
|
|
93
|
+
self.edge_config: EdgeConfig
|
|
94
|
+
for k, v in self.types.items():
|
|
95
|
+
try:
|
|
96
|
+
self._types[k] = eval(v)
|
|
97
|
+
except Exception as ex:
|
|
98
|
+
logger.error(
|
|
99
|
+
f"For resource {self.name} for field {k} failed to cast type {v} : {ex}"
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
@property
|
|
103
|
+
def name(self):
|
|
104
|
+
"""Get the resource name.
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
str: Name of the resource
|
|
108
|
+
"""
|
|
109
|
+
return self.resource_name
|
|
110
|
+
|
|
111
|
+
def finish_init(
|
|
112
|
+
self,
|
|
113
|
+
vertex_config: VertexConfig,
|
|
114
|
+
edge_config: EdgeConfig,
|
|
115
|
+
transforms: dict[str, ProtoTransform],
|
|
116
|
+
):
|
|
117
|
+
"""Complete resource initialization.
|
|
118
|
+
|
|
119
|
+
Initializes the resource with vertex and edge configurations,
|
|
120
|
+
and sets up the processing pipeline.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
vertex_config: Configuration for vertices
|
|
124
|
+
edge_config: Configuration for edges
|
|
125
|
+
transforms: Dictionary of available transforms
|
|
126
|
+
"""
|
|
127
|
+
self.vertex_config = vertex_config
|
|
128
|
+
self.edge_config = edge_config
|
|
129
|
+
|
|
130
|
+
logger.debug(f"total resource actor count : {self.root.count()}")
|
|
131
|
+
self.root.finish_init(
|
|
132
|
+
vertex_config=vertex_config,
|
|
133
|
+
transforms=transforms,
|
|
134
|
+
edge_config=edge_config,
|
|
135
|
+
edge_greedy=self.edge_greedy,
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
logger.debug(f"total resource actor count (after 2 finit): {self.root.count()}")
|
|
139
|
+
|
|
140
|
+
for e in self.extra_weights:
|
|
141
|
+
e.finish_init(vertex_config)
|
|
142
|
+
|
|
143
|
+
def __call__(self, doc: dict) -> defaultdict[GraphEntity, list]:
|
|
144
|
+
"""Process a document through the resource pipeline.
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
doc: Document to process
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
defaultdict[GraphEntity, list]: Processed graph entities
|
|
151
|
+
"""
|
|
152
|
+
ctx = ActionContext()
|
|
153
|
+
ctx = self.root(ctx, doc=doc)
|
|
154
|
+
acc = self.root.normalize_ctx(ctx)
|
|
155
|
+
return acc
|
|
156
|
+
|
|
157
|
+
def count(self):
|
|
158
|
+
"""Get the total number of actors in the resource.
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
int: Number of actors
|
|
162
|
+
"""
|
|
163
|
+
return self.root.count()
|