graflo 1.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of graflo might be problematic. Click here for more details.
- graflo/README.md +18 -0
- graflo/__init__.py +39 -0
- graflo/architecture/__init__.py +37 -0
- graflo/architecture/actor.py +974 -0
- graflo/architecture/actor_util.py +425 -0
- graflo/architecture/edge.py +295 -0
- graflo/architecture/onto.py +374 -0
- graflo/architecture/resource.py +161 -0
- graflo/architecture/schema.py +136 -0
- graflo/architecture/transform.py +292 -0
- graflo/architecture/util.py +93 -0
- graflo/architecture/vertex.py +277 -0
- graflo/caster.py +409 -0
- graflo/cli/__init__.py +14 -0
- graflo/cli/ingest.py +144 -0
- graflo/cli/manage_dbs.py +193 -0
- graflo/cli/plot_schema.py +132 -0
- graflo/cli/xml2json.py +93 -0
- graflo/db/__init__.py +32 -0
- graflo/db/arango/__init__.py +16 -0
- graflo/db/arango/conn.py +734 -0
- graflo/db/arango/query.py +180 -0
- graflo/db/arango/util.py +88 -0
- graflo/db/connection.py +304 -0
- graflo/db/manager.py +104 -0
- graflo/db/neo4j/__init__.py +16 -0
- graflo/db/neo4j/conn.py +432 -0
- graflo/db/util.py +49 -0
- graflo/filter/__init__.py +21 -0
- graflo/filter/onto.py +400 -0
- graflo/logging.conf +22 -0
- graflo/onto.py +186 -0
- graflo/plot/__init__.py +17 -0
- graflo/plot/plotter.py +556 -0
- graflo/util/__init__.py +23 -0
- graflo/util/chunker.py +739 -0
- graflo/util/merge.py +148 -0
- graflo/util/misc.py +37 -0
- graflo/util/onto.py +63 -0
- graflo/util/transform.py +406 -0
- graflo-1.1.0.dist-info/METADATA +157 -0
- graflo-1.1.0.dist-info/RECORD +45 -0
- graflo-1.1.0.dist-info/WHEEL +4 -0
- graflo-1.1.0.dist-info/entry_points.txt +5 -0
- graflo-1.1.0.dist-info/licenses/LICENSE +126 -0
|
@@ -0,0 +1,161 @@
|
|
|
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
|
+
|
|
80
|
+
def __post_init__(self):
|
|
81
|
+
"""Initialize the resource after dataclass initialization.
|
|
82
|
+
|
|
83
|
+
Sets up the actor wrapper and type mappings. Evaluates type expressions
|
|
84
|
+
for field type casting.
|
|
85
|
+
|
|
86
|
+
Raises:
|
|
87
|
+
Exception: If type evaluation fails for any field
|
|
88
|
+
"""
|
|
89
|
+
self.root = ActorWrapper(*self.apply)
|
|
90
|
+
self._types: dict[str, Callable] = dict()
|
|
91
|
+
self.vertex_config: VertexConfig
|
|
92
|
+
self.edge_config: EdgeConfig
|
|
93
|
+
for k, v in self.types.items():
|
|
94
|
+
try:
|
|
95
|
+
self._types[k] = eval(v)
|
|
96
|
+
except Exception as ex:
|
|
97
|
+
logger.error(
|
|
98
|
+
f"For resource {self.name} for field {k} failed to cast type {v} : {ex}"
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
@property
|
|
102
|
+
def name(self):
|
|
103
|
+
"""Get the resource name.
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
str: Name of the resource
|
|
107
|
+
"""
|
|
108
|
+
return self.resource_name
|
|
109
|
+
|
|
110
|
+
def finish_init(
|
|
111
|
+
self,
|
|
112
|
+
vertex_config: VertexConfig,
|
|
113
|
+
edge_config: EdgeConfig,
|
|
114
|
+
transforms: dict[str, ProtoTransform],
|
|
115
|
+
):
|
|
116
|
+
"""Complete resource initialization.
|
|
117
|
+
|
|
118
|
+
Initializes the resource with vertex and edge configurations,
|
|
119
|
+
and sets up the processing pipeline.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
vertex_config: Configuration for vertices
|
|
123
|
+
edge_config: Configuration for edges
|
|
124
|
+
transforms: Dictionary of available transforms
|
|
125
|
+
"""
|
|
126
|
+
self.vertex_config = vertex_config
|
|
127
|
+
self.edge_config = edge_config
|
|
128
|
+
|
|
129
|
+
logger.debug(f"total resource actor count : {self.root.count()}")
|
|
130
|
+
self.root.finish_init(
|
|
131
|
+
vertex_config=vertex_config,
|
|
132
|
+
transforms=transforms,
|
|
133
|
+
edge_config=edge_config,
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
logger.debug(f"total resource actor count (after 2 finit): {self.root.count()}")
|
|
137
|
+
|
|
138
|
+
for e in self.extra_weights:
|
|
139
|
+
e.finish_init(vertex_config)
|
|
140
|
+
|
|
141
|
+
def __call__(self, doc: dict) -> defaultdict[GraphEntity, list]:
|
|
142
|
+
"""Process a document through the resource pipeline.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
doc: Document to process
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
defaultdict[GraphEntity, list]: Processed graph entities
|
|
149
|
+
"""
|
|
150
|
+
ctx = ActionContext()
|
|
151
|
+
ctx = self.root(ctx, doc=doc)
|
|
152
|
+
acc = self.root.normalize_ctx(ctx)
|
|
153
|
+
return acc
|
|
154
|
+
|
|
155
|
+
def count(self):
|
|
156
|
+
"""Get the total number of actors in the resource.
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
int: Number of actors
|
|
160
|
+
"""
|
|
161
|
+
return self.root.count()
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"""Graph database schema management and configuration.
|
|
2
|
+
|
|
3
|
+
This module provides the core schema management functionality for graph databases.
|
|
4
|
+
It defines the structure and configuration of vertices, edges, and resources
|
|
5
|
+
that make up the graph database schema.
|
|
6
|
+
|
|
7
|
+
Key Components:
|
|
8
|
+
- Schema: Main schema container with metadata and configurations
|
|
9
|
+
- SchemaMetadata: Schema versioning and naming information
|
|
10
|
+
- Resource: Resource definitions for data processing
|
|
11
|
+
- VertexConfig: Vertex collection configurations
|
|
12
|
+
- EdgeConfig: Edge collection configurations
|
|
13
|
+
|
|
14
|
+
The schema system provides:
|
|
15
|
+
- Schema versioning and metadata
|
|
16
|
+
- Resource management and validation
|
|
17
|
+
- Vertex and edge configuration
|
|
18
|
+
- Transform registration and management
|
|
19
|
+
|
|
20
|
+
Example:
|
|
21
|
+
>>> schema = Schema(
|
|
22
|
+
... general=SchemaMetadata(name="social_network", version="1.0"),
|
|
23
|
+
... vertex_config=VertexConfig(...),
|
|
24
|
+
... edge_config=EdgeConfig(...),
|
|
25
|
+
... resources=[Resource(...)]
|
|
26
|
+
... )
|
|
27
|
+
>>> resource = schema.fetch_resource("users")
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
import dataclasses
|
|
31
|
+
import logging
|
|
32
|
+
from collections import Counter
|
|
33
|
+
from typing import Optional
|
|
34
|
+
|
|
35
|
+
from graflo.architecture.edge import EdgeConfig
|
|
36
|
+
from graflo.architecture.resource import Resource
|
|
37
|
+
from graflo.architecture.transform import ProtoTransform
|
|
38
|
+
from graflo.architecture.vertex import VertexConfig
|
|
39
|
+
from graflo.onto import BaseDataclass
|
|
40
|
+
|
|
41
|
+
logger = logging.getLogger(__name__)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclasses.dataclass
|
|
45
|
+
class SchemaMetadata(BaseDataclass):
|
|
46
|
+
"""Schema metadata and versioning information.
|
|
47
|
+
|
|
48
|
+
This class holds metadata about the schema, including its name and version.
|
|
49
|
+
It's used for schema identification and versioning.
|
|
50
|
+
|
|
51
|
+
Attributes:
|
|
52
|
+
name: Name of the schema
|
|
53
|
+
version: Optional version string of the schema
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
name: str
|
|
57
|
+
version: Optional[str] = None
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@dataclasses.dataclass
|
|
61
|
+
class Schema(BaseDataclass):
|
|
62
|
+
"""Graph database schema configuration.
|
|
63
|
+
|
|
64
|
+
This class represents the complete schema configuration for a graph database.
|
|
65
|
+
It manages resources, vertex configurations, edge configurations, and transforms.
|
|
66
|
+
|
|
67
|
+
Attributes:
|
|
68
|
+
general: Schema metadata and versioning information
|
|
69
|
+
vertex_config: Configuration for vertex collections
|
|
70
|
+
edge_config: Configuration for edge collections
|
|
71
|
+
resources: List of resource definitions
|
|
72
|
+
transforms: Dictionary of available transforms
|
|
73
|
+
_resources: Internal mapping of resource names to resources
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
general: SchemaMetadata
|
|
77
|
+
vertex_config: VertexConfig
|
|
78
|
+
edge_config: EdgeConfig
|
|
79
|
+
resources: list[Resource]
|
|
80
|
+
transforms: dict[str, ProtoTransform] = dataclasses.field(default_factory=dict)
|
|
81
|
+
|
|
82
|
+
def __post_init__(self):
|
|
83
|
+
"""Initialize the schema after dataclass initialization.
|
|
84
|
+
|
|
85
|
+
Sets up transforms, initializes edge configuration, and validates
|
|
86
|
+
resource names for uniqueness.
|
|
87
|
+
|
|
88
|
+
Raises:
|
|
89
|
+
ValueError: If duplicate resource names are found
|
|
90
|
+
"""
|
|
91
|
+
for name, t in self.transforms.items():
|
|
92
|
+
t.name = name
|
|
93
|
+
|
|
94
|
+
self.edge_config.finish_init(self.vertex_config)
|
|
95
|
+
|
|
96
|
+
for r in self.resources:
|
|
97
|
+
r.finish_init(
|
|
98
|
+
vertex_config=self.vertex_config,
|
|
99
|
+
edge_config=self.edge_config,
|
|
100
|
+
transforms=self.transforms,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
names = [r.name for r in self.resources]
|
|
104
|
+
c = Counter(names)
|
|
105
|
+
for k, v in c.items():
|
|
106
|
+
if v > 1:
|
|
107
|
+
raise ValueError(f"resource name {k} used {v} times")
|
|
108
|
+
self._resources: dict[str, Resource] = {}
|
|
109
|
+
for r in self.resources:
|
|
110
|
+
self._resources[r.name] = r
|
|
111
|
+
|
|
112
|
+
def fetch_resource(self, name: Optional[str] = None) -> Resource:
|
|
113
|
+
"""Fetch a resource by name or get the first available resource.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
name: Optional name of the resource to fetch
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
Resource: The requested resource
|
|
120
|
+
|
|
121
|
+
Raises:
|
|
122
|
+
ValueError: If the requested resource is not found or if no resources exist
|
|
123
|
+
"""
|
|
124
|
+
_current_resource = None
|
|
125
|
+
|
|
126
|
+
if name is not None:
|
|
127
|
+
if name in self._resources:
|
|
128
|
+
_current_resource = self._resources[name]
|
|
129
|
+
else:
|
|
130
|
+
raise ValueError(f"Resource {name} not found")
|
|
131
|
+
else:
|
|
132
|
+
if self._resources:
|
|
133
|
+
_current_resource = self.resources[0]
|
|
134
|
+
else:
|
|
135
|
+
raise ValueError("Empty resource container 😕")
|
|
136
|
+
return _current_resource
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
"""Data transformation and mapping system for graph databases.
|
|
2
|
+
|
|
3
|
+
This module provides a flexible system for transforming and mapping data in graph
|
|
4
|
+
databases. It supports both functional transformations and declarative mappings,
|
|
5
|
+
with support for field switching and parameter configuration.
|
|
6
|
+
|
|
7
|
+
Key Components:
|
|
8
|
+
- ProtoTransform: Base class for transform definitions
|
|
9
|
+
- Transform: Concrete transform implementation
|
|
10
|
+
- TransformException: Custom exception for transform errors
|
|
11
|
+
|
|
12
|
+
The transform system supports:
|
|
13
|
+
- Functional transformations through imported modules
|
|
14
|
+
- Field mapping and switching
|
|
15
|
+
- Parameter configuration
|
|
16
|
+
- Input/output field specification
|
|
17
|
+
- Transform composition and inheritance
|
|
18
|
+
|
|
19
|
+
Example:
|
|
20
|
+
>>> transform = Transform(
|
|
21
|
+
... module="my_module",
|
|
22
|
+
... foo="process_data",
|
|
23
|
+
... input=("field1", "field2"),
|
|
24
|
+
... output=("result1", "result2")
|
|
25
|
+
... )
|
|
26
|
+
>>> result = transform({"field1": 1, "field2": 2})
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from __future__ import annotations
|
|
30
|
+
|
|
31
|
+
import dataclasses
|
|
32
|
+
import importlib
|
|
33
|
+
import logging
|
|
34
|
+
from copy import deepcopy
|
|
35
|
+
from typing import Optional
|
|
36
|
+
|
|
37
|
+
from graflo.onto import BaseDataclass
|
|
38
|
+
|
|
39
|
+
logger = logging.getLogger(__name__)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class TransformException(BaseException):
|
|
43
|
+
"""Base exception for transform-related errors."""
|
|
44
|
+
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclasses.dataclass
|
|
49
|
+
class ProtoTransform(BaseDataclass):
|
|
50
|
+
"""Base class for transform definitions.
|
|
51
|
+
|
|
52
|
+
This class provides the foundation for data transformations, supporting both
|
|
53
|
+
functional transformations and declarative mappings.
|
|
54
|
+
|
|
55
|
+
Attributes:
|
|
56
|
+
name: Optional name of the transform
|
|
57
|
+
module: Optional module containing the transform function
|
|
58
|
+
params: Dictionary of transform parameters
|
|
59
|
+
foo: Optional name of the transform function
|
|
60
|
+
input: Tuple of input field names
|
|
61
|
+
output: Tuple of output field names
|
|
62
|
+
_foo: Internal reference to the transform function
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
name: Optional[str] = None
|
|
66
|
+
module: Optional[str] = None
|
|
67
|
+
params: dict = dataclasses.field(default_factory=dict)
|
|
68
|
+
foo: Optional[str] = None
|
|
69
|
+
input: tuple[str, ...] = dataclasses.field(default_factory=tuple)
|
|
70
|
+
output: tuple[str, ...] = dataclasses.field(default_factory=tuple)
|
|
71
|
+
|
|
72
|
+
def __post_init__(self):
|
|
73
|
+
"""Initialize the transform after dataclass initialization.
|
|
74
|
+
|
|
75
|
+
Sets up the transform function and input/output field specifications.
|
|
76
|
+
"""
|
|
77
|
+
self._foo = None
|
|
78
|
+
self._init_foo()
|
|
79
|
+
|
|
80
|
+
self.input = self._tuple_it(self.input)
|
|
81
|
+
|
|
82
|
+
if not self.output:
|
|
83
|
+
self.output = self.input
|
|
84
|
+
self.output = self._tuple_it(self.output)
|
|
85
|
+
|
|
86
|
+
@staticmethod
|
|
87
|
+
def _tuple_it(x):
|
|
88
|
+
"""Convert input to tuple format.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
x: Input to convert (string, list, or tuple)
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
tuple: Converted tuple
|
|
95
|
+
"""
|
|
96
|
+
if isinstance(x, str):
|
|
97
|
+
x = [x]
|
|
98
|
+
if isinstance(x, list):
|
|
99
|
+
x = tuple(x)
|
|
100
|
+
return x
|
|
101
|
+
|
|
102
|
+
def _init_foo(self):
|
|
103
|
+
"""Initialize the transform function from module.
|
|
104
|
+
|
|
105
|
+
Imports the specified module and gets the transform function.
|
|
106
|
+
|
|
107
|
+
Raises:
|
|
108
|
+
TypeError: If module import fails
|
|
109
|
+
ValueError: If function lookup fails
|
|
110
|
+
"""
|
|
111
|
+
if self.module is not None:
|
|
112
|
+
try:
|
|
113
|
+
_module = importlib.import_module(self.module)
|
|
114
|
+
except Exception as e:
|
|
115
|
+
raise TypeError(f"Provided module {self.module} is not valid: {e}")
|
|
116
|
+
try:
|
|
117
|
+
self._foo = getattr(_module, self.foo)
|
|
118
|
+
except Exception as e:
|
|
119
|
+
raise ValueError(
|
|
120
|
+
f"Could not instantiate transform function. Exception: {e}"
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
def __lt__(self, other):
|
|
124
|
+
"""Compare transforms for ordering.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
other: Other transform to compare with
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
bool: True if this transform should be ordered before other
|
|
131
|
+
"""
|
|
132
|
+
if self._foo is None and other._foo is not None:
|
|
133
|
+
return True
|
|
134
|
+
return False
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
@dataclasses.dataclass
|
|
138
|
+
class Transform(ProtoTransform):
|
|
139
|
+
"""Concrete transform implementation.
|
|
140
|
+
|
|
141
|
+
This class extends ProtoTransform with additional functionality for
|
|
142
|
+
field mapping, switching, and transform composition.
|
|
143
|
+
|
|
144
|
+
Attributes:
|
|
145
|
+
fields: Tuple of fields to transform
|
|
146
|
+
map: Dictionary mapping input fields to output fields
|
|
147
|
+
switch: Dictionary for field switching logic
|
|
148
|
+
functional_transform: Whether this is a functional transform
|
|
149
|
+
"""
|
|
150
|
+
|
|
151
|
+
fields: tuple[str, ...] = dataclasses.field(default_factory=tuple)
|
|
152
|
+
map: dict[str, str] = dataclasses.field(default_factory=dict)
|
|
153
|
+
switch: dict[str, str] = dataclasses.field(default_factory=dict)
|
|
154
|
+
|
|
155
|
+
def __post_init__(self):
|
|
156
|
+
"""Initialize the transform after dataclass initialization.
|
|
157
|
+
|
|
158
|
+
Sets up field specifications and validates transform configuration.
|
|
159
|
+
|
|
160
|
+
Raises:
|
|
161
|
+
ValueError: If transform configuration is invalid
|
|
162
|
+
"""
|
|
163
|
+
super().__post_init__()
|
|
164
|
+
self.functional_transform = False
|
|
165
|
+
if self._foo is not None:
|
|
166
|
+
self.functional_transform = True
|
|
167
|
+
|
|
168
|
+
self.input = self._tuple_it(self.input)
|
|
169
|
+
|
|
170
|
+
self.fields = self._tuple_it(self.fields)
|
|
171
|
+
|
|
172
|
+
self.input = self.fields if self.fields and not self.input else self.input
|
|
173
|
+
if not self.output:
|
|
174
|
+
self.output = self.input
|
|
175
|
+
self.output = self._tuple_it(self.output)
|
|
176
|
+
|
|
177
|
+
if not self.input and not self.output:
|
|
178
|
+
if self.map:
|
|
179
|
+
items = list(self.map.items())
|
|
180
|
+
self.input = tuple(x for x, _ in items)
|
|
181
|
+
self.output = tuple(x for _, x in items)
|
|
182
|
+
elif self.switch:
|
|
183
|
+
self.input = tuple([k for k in self.switch])
|
|
184
|
+
self.output = tuple(self.switch[self.input[0]])
|
|
185
|
+
elif not self.name:
|
|
186
|
+
raise ValueError(
|
|
187
|
+
"Either input and output, fields, map or name should be"
|
|
188
|
+
" provided in Transform constructor."
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
def __call__(self, *nargs, **kwargs):
|
|
192
|
+
"""Execute the transform.
|
|
193
|
+
|
|
194
|
+
Args:
|
|
195
|
+
*nargs: Positional arguments for the transform
|
|
196
|
+
**kwargs: Keyword arguments for the transform
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
dict: Transformed data
|
|
200
|
+
"""
|
|
201
|
+
is_mapping = self._foo is None
|
|
202
|
+
|
|
203
|
+
if is_mapping:
|
|
204
|
+
input_doc = nargs[0]
|
|
205
|
+
if isinstance(input_doc, dict):
|
|
206
|
+
output_values = [input_doc[k] for k in self.input]
|
|
207
|
+
else:
|
|
208
|
+
output_values = nargs
|
|
209
|
+
else:
|
|
210
|
+
if nargs and isinstance(input_doc := nargs[0], dict):
|
|
211
|
+
new_args = [input_doc[k] for k in self.input]
|
|
212
|
+
output_values = self._foo(*new_args, **kwargs, **self.params)
|
|
213
|
+
else:
|
|
214
|
+
output_values = self._foo(*nargs, **kwargs, **self.params)
|
|
215
|
+
|
|
216
|
+
if self.output:
|
|
217
|
+
r = self._dress_as_dict(output_values)
|
|
218
|
+
else:
|
|
219
|
+
r = output_values
|
|
220
|
+
return r
|
|
221
|
+
|
|
222
|
+
def _dress_as_dict(self, transform_result):
|
|
223
|
+
"""Convert transform result to dictionary format.
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
transform_result: Result of the transform
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
dict: Dictionary representation of the result
|
|
230
|
+
"""
|
|
231
|
+
if isinstance(transform_result, (list, tuple)) and not self.switch:
|
|
232
|
+
upd = {k: v for k, v in zip(self.output, transform_result)}
|
|
233
|
+
else:
|
|
234
|
+
# TODO : temporary solution works only there is one switch clause
|
|
235
|
+
upd = {self.output[-1]: transform_result}
|
|
236
|
+
for k0, (q, qq) in self.switch.items():
|
|
237
|
+
upd.update({q: k0})
|
|
238
|
+
return upd
|
|
239
|
+
|
|
240
|
+
@property
|
|
241
|
+
def is_dummy(self):
|
|
242
|
+
"""Check if this is a dummy transform.
|
|
243
|
+
|
|
244
|
+
Returns:
|
|
245
|
+
bool: True if this is a dummy transform
|
|
246
|
+
"""
|
|
247
|
+
return (self.name is not None) and (not self.map and self._foo is None)
|
|
248
|
+
|
|
249
|
+
def update(self, t: Transform):
|
|
250
|
+
"""Update this transform with another transform's configuration.
|
|
251
|
+
|
|
252
|
+
Args:
|
|
253
|
+
t: Transform to update from
|
|
254
|
+
|
|
255
|
+
Returns:
|
|
256
|
+
Transform: Updated transform
|
|
257
|
+
"""
|
|
258
|
+
t_copy = deepcopy(t)
|
|
259
|
+
if self.input:
|
|
260
|
+
t_copy.input = self.input
|
|
261
|
+
if self.output:
|
|
262
|
+
t_copy.output = self.output
|
|
263
|
+
if self.params:
|
|
264
|
+
t_copy.params.update(self.params)
|
|
265
|
+
t_copy.__post_init__()
|
|
266
|
+
return t_copy
|
|
267
|
+
|
|
268
|
+
def get_barebone(
|
|
269
|
+
self, other: Optional[Transform]
|
|
270
|
+
) -> tuple[Optional[Transform], Optional[Transform]]:
|
|
271
|
+
"""Get the barebone transform configuration.
|
|
272
|
+
|
|
273
|
+
Args:
|
|
274
|
+
other: Optional transform to use as base
|
|
275
|
+
|
|
276
|
+
Returns:
|
|
277
|
+
tuple[Optional[Transform], Optional[Transform]]: Updated self transform
|
|
278
|
+
and transform to store in library
|
|
279
|
+
"""
|
|
280
|
+
self_param = self.to_dict(skip_defaults=True)
|
|
281
|
+
if self.foo is not None:
|
|
282
|
+
# self will be the lib transform
|
|
283
|
+
return None, self
|
|
284
|
+
elif other is not None and other.foo is not None:
|
|
285
|
+
# init self from other
|
|
286
|
+
self_param.pop("foo", None)
|
|
287
|
+
self_param.pop("module", None)
|
|
288
|
+
other_param = other.to_dict(skip_defaults=True)
|
|
289
|
+
other_param.update(self_param)
|
|
290
|
+
return Transform(**other_param), None
|
|
291
|
+
else:
|
|
292
|
+
return None, None
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""Utility functions for graph architecture operations.
|
|
2
|
+
|
|
3
|
+
This module provides utility functions for working with graph data structures
|
|
4
|
+
and transformations. It includes functions for dictionary projection and
|
|
5
|
+
graph entity name formatting.
|
|
6
|
+
|
|
7
|
+
Key Functions:
|
|
8
|
+
- project_dict: Project dictionary fields based on inclusion/exclusion
|
|
9
|
+
- cast_graph_name_to_triple: Convert graph names to standardized triple format
|
|
10
|
+
|
|
11
|
+
Example:
|
|
12
|
+
>>> data = {"a": 1, "b": 2, "c": 3}
|
|
13
|
+
>>> project_dict(data, ["a", "b"], how="include")
|
|
14
|
+
{'a': 1, 'b': 2}
|
|
15
|
+
>>> cast_graph_name_to_triple("user_post_graph")
|
|
16
|
+
('user', 'post', None)
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
from typing import Union
|
|
22
|
+
|
|
23
|
+
from graflo.architecture.onto import GraphEntity
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def project_dict(item, keys, how="include"):
|
|
27
|
+
"""Project dictionary fields based on inclusion or exclusion.
|
|
28
|
+
|
|
29
|
+
This function filters a dictionary based on a list of keys, either including
|
|
30
|
+
or excluding the specified keys.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
item: Dictionary to project
|
|
34
|
+
keys: List of keys to include or exclude
|
|
35
|
+
how: Projection mode - "include" or "exclude" (default: "include")
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
dict: Projected dictionary containing only the specified fields
|
|
39
|
+
|
|
40
|
+
Example:
|
|
41
|
+
>>> data = {"a": 1, "b": 2, "c": 3}
|
|
42
|
+
>>> project_dict(data, ["a", "b"], how="include")
|
|
43
|
+
{'a': 1, 'b': 2}
|
|
44
|
+
>>> project_dict(data, ["a"], how="exclude")
|
|
45
|
+
{'b': 2, 'c': 3}
|
|
46
|
+
"""
|
|
47
|
+
if how == "include":
|
|
48
|
+
return {k: v for k, v in item.items() if k in keys}
|
|
49
|
+
elif how == "exclude":
|
|
50
|
+
return {k: v for k, v in item.items() if k not in keys}
|
|
51
|
+
else:
|
|
52
|
+
return {}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def cast_graph_name_to_triple(s: GraphEntity) -> Union[str, tuple]:
|
|
56
|
+
"""Convert a graph name string to a triple format.
|
|
57
|
+
|
|
58
|
+
This function parses graph entity names into a standardized triple format
|
|
59
|
+
(source, target, type). It handles various naming patterns and special
|
|
60
|
+
suffixes like "graph" or "edges".
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
s: Graph entity name or ID
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
Union[str, tuple]: Either a string for simple names or a tuple
|
|
67
|
+
representing (source, target, type) for complex names
|
|
68
|
+
|
|
69
|
+
Raises:
|
|
70
|
+
ValueError: If the graph name cannot be cast to a valid format
|
|
71
|
+
|
|
72
|
+
Example:
|
|
73
|
+
>>> cast_graph_name_to_triple("user_post_graph")
|
|
74
|
+
('user', 'post', None)
|
|
75
|
+
>>> cast_graph_name_to_triple("simple_vertex")
|
|
76
|
+
('simple', None)
|
|
77
|
+
"""
|
|
78
|
+
if isinstance(s, str):
|
|
79
|
+
s2 = s.split("_")
|
|
80
|
+
if len(s2) < 2:
|
|
81
|
+
return s2[0]
|
|
82
|
+
elif len(s2) == 2:
|
|
83
|
+
return *s2[:-1], None
|
|
84
|
+
elif len(s2) == 3:
|
|
85
|
+
if s2[-1] in ["graph", "edges"]:
|
|
86
|
+
return *s2[:-1], None
|
|
87
|
+
else:
|
|
88
|
+
return tuple(s2)
|
|
89
|
+
elif len(s2) == 4 and s2[-1] in ["graph", "edges"]:
|
|
90
|
+
return tuple(s2[:-1])
|
|
91
|
+
raise ValueError(f"Invalid graph_name {s} : can not be cast to GraphEntity")
|
|
92
|
+
else:
|
|
93
|
+
return s
|