diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 430dcd8..f4df3b5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,7 +19,7 @@ jobs: - name: Install dependencies run: | uv venv - uv sync --extra convert + uv sync --all-extras uv pip install -e . - name: Run tests diff --git a/icebug_format/graphar.py b/icebug_format/graphar.py index c98cbf7..af3329e 100644 --- a/icebug_format/graphar.py +++ b/icebug_format/graphar.py @@ -18,6 +18,7 @@ """ import argparse +import yaml from pathlib import Path import duckdb @@ -28,6 +29,55 @@ set_memory_limit, ) +class YamlVertexInfo: + def __init__(self, data): + self.data = data + def get_type(self): return self.data.get('type') + def get_prefix(self): return self.data.get('prefix') + +class YamlEdgeInfo: + def __init__(self, data): + self.data = data + def get_edge_type(self): return self.data.get('edge_type', self.data.get('type')) + def get_src_type(self): return self.data.get('src_type') + def get_dst_type(self): return self.data.get('dst_type') + def get_prefix(self): return self.data.get('prefix') + +class YamlGraphInfo: + def __init__(self, yaml_path: str): + self.base_path = Path(yaml_path).parent + with open(yaml_path, 'r') as f: + self.data = yaml.safe_load(f) + + self.vertices = [] + for v_file in self.data.get('vertices', []): + with open(self.base_path / v_file, 'r') as f: + self.vertices.append(YamlVertexInfo(yaml.safe_load(f))) + + self.edges = [] + for e_file in self.data.get('edges', []): + with open(self.base_path / e_file, 'r') as f: + self.edges.append(YamlEdgeInfo(yaml.safe_load(f))) + + def vertex_info_num(self): return len(self.vertices) + def get_vertex_info_by_index(self, i): return self.vertices[i] + def edge_info_num(self): return len(self.edges) + def get_edge_info_by_index(self, i): return self.edges[i] + + @classmethod + def load(cls, yaml_path): + return cls(yaml_path) + +def _require_graphar(context: str = "GraphAr conversion") -> "module": + """Lazily import and return the ``graphar`` module.""" + try: + import graphar + except ImportError as exc: + raise ImportError( + f"graphar is required by {context} but is not installed or failed to load." + "Please ensure you have graphar installed(e.g., pip install graphar)." + ) from exc + return graphar def duckdb_type_to_cypher_type(duckdb_type: str) -> str: """Convert DuckDB column type to Cypher/Kuzu type.""" @@ -175,7 +225,6 @@ def convert_graphar_to_graph_std( """ print("\n=== Converting GraphAr to Graph-Std Format ===") - import graphar # Load graph info # Find the .graph.yml file in the directory @@ -184,7 +233,13 @@ def convert_graphar_to_graph_std( if not yaml_files: raise ValueError(f"No .graph.yml file found in {graphar_dir}") yaml_path = yaml_files[0] - graph_info = graphar.GraphInfo.load(str(yaml_path.absolute())) + + try: + graphar = _require_graphar() + graph_info = graphar.GraphInfo.load(str(yaml_path.absolute())) + except ImportError: + graph_info = YamlGraphInfo.load(str(yaml_path.absolute())) + graph_path = graphar_path diff --git a/pyproject.toml b/pyproject.toml index 9b5310f..5681e24 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,6 +17,7 @@ convert-duckdb = [ ] graphar = [ "graphar", + "PyYAML>=6.0", ] [project.scripts] diff --git a/tests/test_graphar.py b/tests/test_graphar.py new file mode 100644 index 0000000..908dd78 --- /dev/null +++ b/tests/test_graphar.py @@ -0,0 +1,18 @@ +import yaml +from icebug_format.graphar import YamlGraphInfo + +def test_yaml_parser_fallback_loads_empty_graph(tmp_path): + """Test that our fallback YAML parser can read a basic graph configuration.""" + yaml_content = { + "vertices": [], + "edges": [] + } + yaml_file = tmp_path / "test.graph.yml" + + with open(yaml_file, "w") as f: + yaml.dump(yaml_content, f) + + graph_info = YamlGraphInfo.load(str(yaml_file)) + + assert graph_info.vertex_info_num() == 0 + assert graph_info.edge_info_num() == 0 \ No newline at end of file