grai-build 0.3.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.
- grai/__init__.py +11 -0
- grai/cli/__init__.py +5 -0
- grai/cli/main.py +2546 -0
- grai/core/__init__.py +1 -0
- grai/core/cache/__init__.py +33 -0
- grai/core/cache/build_cache.py +352 -0
- grai/core/compiler/__init__.py +23 -0
- grai/core/compiler/cypher_compiler.py +426 -0
- grai/core/exporter/__init__.py +13 -0
- grai/core/exporter/ir_exporter.py +343 -0
- grai/core/lineage/__init__.py +42 -0
- grai/core/lineage/lineage_tracker.py +685 -0
- grai/core/loader/__init__.py +21 -0
- grai/core/loader/neo4j_loader.py +514 -0
- grai/core/models.py +344 -0
- grai/core/parser/__init__.py +25 -0
- grai/core/parser/yaml_parser.py +375 -0
- grai/core/validator/__init__.py +25 -0
- grai/core/validator/validator.py +475 -0
- grai/core/visualizer/__init__.py +650 -0
- grai/core/visualizer/visualizer.py +15 -0
- grai/templates/__init__.py +1 -0
- grai_build-0.3.0.dist-info/METADATA +374 -0
- grai_build-0.3.0.dist-info/RECORD +28 -0
- grai_build-0.3.0.dist-info/WHEEL +5 -0
- grai_build-0.3.0.dist-info/entry_points.txt +2 -0
- grai_build-0.3.0.dist-info/licenses/LICENSE +21 -0
- grai_build-0.3.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Graph IR (Intermediate Representation) Exporter.
|
|
3
|
+
|
|
4
|
+
This module generates a JSON representation of the complete graph structure
|
|
5
|
+
including entities, relations, properties, constraints, and metadata.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
from datetime import UTC, datetime
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any, Dict, Optional
|
|
12
|
+
|
|
13
|
+
from grai.core.models import Entity, Project, Property, Relation
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def export_to_ir(project: Project) -> Dict[str, Any]:
|
|
17
|
+
"""
|
|
18
|
+
Export a Project to Graph IR (Intermediate Representation).
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
project: The Project to export
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
Dictionary containing the complete graph structure
|
|
25
|
+
|
|
26
|
+
Example:
|
|
27
|
+
>>> project = load_project(Path("."))
|
|
28
|
+
>>> ir = export_to_ir(project)
|
|
29
|
+
>>> print(ir["metadata"]["name"])
|
|
30
|
+
'my-graph'
|
|
31
|
+
"""
|
|
32
|
+
return {
|
|
33
|
+
"metadata": _export_metadata(project),
|
|
34
|
+
"entities": [_export_entity(entity) for entity in project.entities],
|
|
35
|
+
"relations": [_export_relation(relation) for relation in project.relations],
|
|
36
|
+
"statistics": _export_statistics(project),
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _export_metadata(project: Project) -> Dict[str, Any]:
|
|
41
|
+
"""
|
|
42
|
+
Export project metadata.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
project: The Project
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
Dictionary with metadata fields
|
|
49
|
+
"""
|
|
50
|
+
return {
|
|
51
|
+
"name": project.name,
|
|
52
|
+
"version": project.version,
|
|
53
|
+
"description": getattr(project, "description", None),
|
|
54
|
+
"exported_at": datetime.now(UTC).isoformat().replace("+00:00", "Z"),
|
|
55
|
+
"exporter_version": "0.2.0",
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _export_entity(entity: Entity) -> Dict[str, Any]:
|
|
60
|
+
"""
|
|
61
|
+
Export an Entity to IR format.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
entity: The Entity to export
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
Dictionary with entity structure
|
|
68
|
+
"""
|
|
69
|
+
source_config = entity.get_source_config()
|
|
70
|
+
return {
|
|
71
|
+
"name": entity.entity,
|
|
72
|
+
"source": {
|
|
73
|
+
"name": source_config.name,
|
|
74
|
+
"type": source_config.type.value if source_config.type else None,
|
|
75
|
+
"connection": source_config.connection,
|
|
76
|
+
"schema": source_config.db_schema,
|
|
77
|
+
"database": source_config.database,
|
|
78
|
+
"format": source_config.format,
|
|
79
|
+
"metadata": source_config.metadata,
|
|
80
|
+
},
|
|
81
|
+
"keys": entity.keys,
|
|
82
|
+
"properties": [_export_property(prop) for prop in entity.properties],
|
|
83
|
+
"metadata": {
|
|
84
|
+
"property_count": len(entity.properties),
|
|
85
|
+
"key_count": len(entity.keys),
|
|
86
|
+
"has_source": bool(source_config.name),
|
|
87
|
+
},
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _export_relation(relation: Relation) -> Dict[str, Any]:
|
|
92
|
+
"""
|
|
93
|
+
Export a Relation to IR format.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
relation: The Relation to export
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
Dictionary with relation structure
|
|
100
|
+
"""
|
|
101
|
+
source_config = relation.get_source_config()
|
|
102
|
+
return {
|
|
103
|
+
"name": relation.relation,
|
|
104
|
+
"from_entity": relation.from_entity,
|
|
105
|
+
"to_entity": relation.to_entity,
|
|
106
|
+
"source": {
|
|
107
|
+
"name": source_config.name,
|
|
108
|
+
"type": source_config.type.value if source_config.type else None,
|
|
109
|
+
"connection": source_config.connection,
|
|
110
|
+
"schema": source_config.db_schema,
|
|
111
|
+
"database": source_config.database,
|
|
112
|
+
"format": source_config.format,
|
|
113
|
+
"metadata": source_config.metadata,
|
|
114
|
+
},
|
|
115
|
+
"mappings": {
|
|
116
|
+
"from_key": relation.mappings.from_key,
|
|
117
|
+
"to_key": relation.mappings.to_key,
|
|
118
|
+
},
|
|
119
|
+
"properties": [_export_property(prop) for prop in relation.properties],
|
|
120
|
+
"metadata": {
|
|
121
|
+
"property_count": len(relation.properties),
|
|
122
|
+
"has_source": bool(source_config.name),
|
|
123
|
+
"direction": f"{relation.from_entity} -> {relation.to_entity}",
|
|
124
|
+
},
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _export_property(prop: Property) -> Dict[str, Any]:
|
|
129
|
+
"""
|
|
130
|
+
Export a Property to IR format.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
prop: The Property to export
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
Dictionary with property structure
|
|
137
|
+
"""
|
|
138
|
+
return {
|
|
139
|
+
"name": prop.name,
|
|
140
|
+
"type": prop.type.value,
|
|
141
|
+
"description": prop.description,
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _export_statistics(project: Project) -> Dict[str, Any]:
|
|
146
|
+
"""
|
|
147
|
+
Export project statistics.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
project: The Project
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
Dictionary with statistics
|
|
154
|
+
"""
|
|
155
|
+
total_entity_properties = sum(len(e.properties) for e in project.entities)
|
|
156
|
+
total_relation_properties = sum(len(r.properties) for r in project.relations)
|
|
157
|
+
|
|
158
|
+
return {
|
|
159
|
+
"entity_count": len(project.entities),
|
|
160
|
+
"relation_count": len(project.relations),
|
|
161
|
+
"total_properties": total_entity_properties + total_relation_properties,
|
|
162
|
+
"entity_properties": total_entity_properties,
|
|
163
|
+
"relation_properties": total_relation_properties,
|
|
164
|
+
"total_keys": sum(len(e.keys) for e in project.entities),
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def export_to_json(
|
|
169
|
+
project: Project,
|
|
170
|
+
pretty: bool = True,
|
|
171
|
+
indent: int = 2,
|
|
172
|
+
) -> str:
|
|
173
|
+
"""
|
|
174
|
+
Export a Project to JSON string.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
project: The Project to export
|
|
178
|
+
pretty: Whether to pretty-print the JSON
|
|
179
|
+
indent: Number of spaces for indentation (if pretty=True)
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
JSON string representation of the graph
|
|
183
|
+
|
|
184
|
+
Example:
|
|
185
|
+
>>> project = load_project(Path("."))
|
|
186
|
+
>>> json_str = export_to_json(project)
|
|
187
|
+
>>> print(json_str[:100])
|
|
188
|
+
'{
|
|
189
|
+
"metadata": {
|
|
190
|
+
"name": "my-graph",
|
|
191
|
+
'
|
|
192
|
+
"""
|
|
193
|
+
ir = export_to_ir(project)
|
|
194
|
+
|
|
195
|
+
if pretty:
|
|
196
|
+
return json.dumps(ir, indent=indent, ensure_ascii=False)
|
|
197
|
+
else:
|
|
198
|
+
return json.dumps(ir, ensure_ascii=False)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def write_ir_file(
|
|
202
|
+
project: Project,
|
|
203
|
+
output_path: Path,
|
|
204
|
+
pretty: bool = True,
|
|
205
|
+
indent: int = 2,
|
|
206
|
+
) -> None:
|
|
207
|
+
"""
|
|
208
|
+
Write Graph IR to a JSON file.
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
project: The Project to export
|
|
212
|
+
output_path: Path to write the JSON file
|
|
213
|
+
pretty: Whether to pretty-print the JSON
|
|
214
|
+
indent: Number of spaces for indentation
|
|
215
|
+
|
|
216
|
+
Raises:
|
|
217
|
+
OSError: If file cannot be written
|
|
218
|
+
|
|
219
|
+
Example:
|
|
220
|
+
>>> project = load_project(Path("."))
|
|
221
|
+
>>> write_ir_file(project, Path("graph.json"))
|
|
222
|
+
"""
|
|
223
|
+
# Ensure parent directory exists
|
|
224
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
225
|
+
|
|
226
|
+
json_content = export_to_json(project, pretty=pretty, indent=indent)
|
|
227
|
+
output_path.write_text(json_content, encoding="utf-8")
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def load_ir_from_file(input_path: Path) -> Dict[str, Any]:
|
|
231
|
+
"""
|
|
232
|
+
Load Graph IR from a JSON file.
|
|
233
|
+
|
|
234
|
+
Args:
|
|
235
|
+
input_path: Path to the JSON file
|
|
236
|
+
|
|
237
|
+
Returns:
|
|
238
|
+
Dictionary containing the graph structure
|
|
239
|
+
|
|
240
|
+
Raises:
|
|
241
|
+
FileNotFoundError: If file doesn't exist
|
|
242
|
+
json.JSONDecodeError: If file is not valid JSON
|
|
243
|
+
|
|
244
|
+
Example:
|
|
245
|
+
>>> ir = load_ir_from_file(Path("graph.json"))
|
|
246
|
+
>>> print(ir["metadata"]["name"])
|
|
247
|
+
'my-graph'
|
|
248
|
+
"""
|
|
249
|
+
if not input_path.exists():
|
|
250
|
+
raise FileNotFoundError(f"IR file not found: {input_path}")
|
|
251
|
+
|
|
252
|
+
content = input_path.read_text(encoding="utf-8")
|
|
253
|
+
return json.loads(content)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def validate_ir_structure(ir: Dict[str, Any]) -> bool:
|
|
257
|
+
"""
|
|
258
|
+
Validate that a dictionary has the expected IR structure.
|
|
259
|
+
|
|
260
|
+
Args:
|
|
261
|
+
ir: The IR dictionary to validate
|
|
262
|
+
|
|
263
|
+
Returns:
|
|
264
|
+
True if structure is valid
|
|
265
|
+
|
|
266
|
+
Raises:
|
|
267
|
+
ValueError: If structure is invalid
|
|
268
|
+
|
|
269
|
+
Example:
|
|
270
|
+
>>> ir = export_to_ir(project)
|
|
271
|
+
>>> validate_ir_structure(ir)
|
|
272
|
+
True
|
|
273
|
+
"""
|
|
274
|
+
required_top_level = {"metadata", "entities", "relations", "statistics"}
|
|
275
|
+
|
|
276
|
+
if not isinstance(ir, dict):
|
|
277
|
+
raise ValueError("IR must be a dictionary")
|
|
278
|
+
|
|
279
|
+
missing = required_top_level - set(ir.keys())
|
|
280
|
+
if missing:
|
|
281
|
+
raise ValueError(f"IR missing required fields: {missing}")
|
|
282
|
+
|
|
283
|
+
# Validate metadata
|
|
284
|
+
required_metadata = {"name", "version", "exported_at"}
|
|
285
|
+
metadata_keys = set(ir["metadata"].keys())
|
|
286
|
+
missing_metadata = required_metadata - metadata_keys
|
|
287
|
+
if missing_metadata:
|
|
288
|
+
raise ValueError(f"Metadata missing required fields: {missing_metadata}")
|
|
289
|
+
|
|
290
|
+
# Validate entities and relations are lists
|
|
291
|
+
if not isinstance(ir["entities"], list):
|
|
292
|
+
raise ValueError("IR 'entities' must be a list")
|
|
293
|
+
|
|
294
|
+
if not isinstance(ir["relations"], list):
|
|
295
|
+
raise ValueError("IR 'relations' must be a list")
|
|
296
|
+
|
|
297
|
+
return True
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def get_entity_from_ir(ir: Dict[str, Any], entity_name: str) -> Optional[Dict[str, Any]]:
|
|
301
|
+
"""
|
|
302
|
+
Get an entity by name from IR.
|
|
303
|
+
|
|
304
|
+
Args:
|
|
305
|
+
ir: The IR dictionary
|
|
306
|
+
entity_name: Name of the entity to find
|
|
307
|
+
|
|
308
|
+
Returns:
|
|
309
|
+
Entity dictionary or None if not found
|
|
310
|
+
|
|
311
|
+
Example:
|
|
312
|
+
>>> ir = export_to_ir(project)
|
|
313
|
+
>>> customer = get_entity_from_ir(ir, "customer")
|
|
314
|
+
>>> print(customer["name"])
|
|
315
|
+
'customer'
|
|
316
|
+
"""
|
|
317
|
+
for entity in ir["entities"]:
|
|
318
|
+
if entity["name"] == entity_name:
|
|
319
|
+
return entity
|
|
320
|
+
return None
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def get_relation_from_ir(ir: Dict[str, Any], relation_name: str) -> Optional[Dict[str, Any]]:
|
|
324
|
+
"""
|
|
325
|
+
Get a relation by name from IR.
|
|
326
|
+
|
|
327
|
+
Args:
|
|
328
|
+
ir: The IR dictionary
|
|
329
|
+
relation_name: Name of the relation to find
|
|
330
|
+
|
|
331
|
+
Returns:
|
|
332
|
+
Relation dictionary or None if not found
|
|
333
|
+
|
|
334
|
+
Example:
|
|
335
|
+
>>> ir = export_to_ir(project)
|
|
336
|
+
>>> purchased = get_relation_from_ir(ir, "PURCHASED")
|
|
337
|
+
>>> print(purchased["from_entity"])
|
|
338
|
+
'customer'
|
|
339
|
+
"""
|
|
340
|
+
for relation in ir["relations"]:
|
|
341
|
+
if relation["name"] == relation_name:
|
|
342
|
+
return relation
|
|
343
|
+
return None
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Lineage tracking module for knowledge graph analysis.
|
|
3
|
+
|
|
4
|
+
Exports lineage tracking functions for analyzing entity relationships,
|
|
5
|
+
dependencies, and impact analysis.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .lineage_tracker import (
|
|
9
|
+
LineageEdge,
|
|
10
|
+
LineageGraph,
|
|
11
|
+
LineageNode,
|
|
12
|
+
NodeType,
|
|
13
|
+
build_lineage_graph,
|
|
14
|
+
calculate_impact_analysis,
|
|
15
|
+
export_lineage_to_dict,
|
|
16
|
+
find_downstream_entities,
|
|
17
|
+
find_entity_path,
|
|
18
|
+
find_upstream_entities,
|
|
19
|
+
get_entity_lineage,
|
|
20
|
+
get_lineage_statistics,
|
|
21
|
+
get_relation_lineage,
|
|
22
|
+
visualize_lineage_graphviz,
|
|
23
|
+
visualize_lineage_mermaid,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
"LineageGraph",
|
|
28
|
+
"LineageNode",
|
|
29
|
+
"LineageEdge",
|
|
30
|
+
"NodeType",
|
|
31
|
+
"build_lineage_graph",
|
|
32
|
+
"get_entity_lineage",
|
|
33
|
+
"get_relation_lineage",
|
|
34
|
+
"find_upstream_entities",
|
|
35
|
+
"find_downstream_entities",
|
|
36
|
+
"find_entity_path",
|
|
37
|
+
"calculate_impact_analysis",
|
|
38
|
+
"get_lineage_statistics",
|
|
39
|
+
"export_lineage_to_dict",
|
|
40
|
+
"visualize_lineage_mermaid",
|
|
41
|
+
"visualize_lineage_graphviz",
|
|
42
|
+
]
|