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.
@@ -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
+ ]