Skip to content

Commit 2d28003

Browse files
genezhangclaude
andauthored
docs: embedded mode tutorials and runnable examples (#246)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 30bc81f commit 2d28003

14 files changed

Lines changed: 950 additions & 0 deletions

clickgraph-py/clickgraph/__init__.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,9 @@ def __init__(self, ffi_result: _FfiQueryResult):
158158
self._column_names = ffi_result.column_names()
159159
self._rows = ffi_result.get_all_rows()
160160
self._position = 0
161+
self._compiling_time = ffi_result.get_compiling_time()
162+
self._execution_time = ffi_result.get_execution_time()
163+
self._column_data_types = ffi_result.get_column_data_types()
161164

162165
@property
163166
def column_names(self) -> list[str]:
@@ -169,6 +172,21 @@ def num_rows(self) -> int:
169172
"""Number of rows."""
170173
return len(self._rows)
171174

175+
@property
176+
def compiling_time(self) -> float:
177+
"""Time spent translating Cypher to SQL (milliseconds)."""
178+
return self._compiling_time
179+
180+
@property
181+
def execution_time(self) -> float:
182+
"""Time spent executing the SQL query (milliseconds)."""
183+
return self._execution_time
184+
185+
@property
186+
def column_data_types(self) -> list[str]:
187+
"""Inferred column data types (e.g., ``["String", "Int64", "Date"]``)."""
188+
return list(self._column_data_types)
189+
172190
def has_next(self) -> bool:
173191
"""Return True if there are more rows (Kuzu-compatible cursor)."""
174192
return self._position < len(self._rows)
@@ -472,10 +490,79 @@ def store_subgraph(self, graph: GraphResult) -> StoreStats:
472490
edges_stored=ffi_stats.edges_stored,
473491
)
474492

493+
# -- Write operations --
494+
495+
def create_node(self, label: str, properties: dict) -> str:
496+
"""Create a node with the given label and properties.
497+
498+
Returns the node ID (caller-provided or auto-generated UUID).
499+
500+
>>> node_id = conn.create_node("User", {"user_id": "u1", "name": "Alice", "age": 30})
501+
"""
502+
return self._ffi.create_node(label, _python_to_ffi_props(properties))
503+
504+
def create_edge(
505+
self, edge_type: str, from_id: str, to_id: str, properties: dict | None = None
506+
) -> None:
507+
"""Create an edge between two nodes.
508+
509+
>>> conn.create_edge("FOLLOWS", "u1", "u2", {"follow_date": "2024-01-15"})
510+
"""
511+
self._ffi.create_edge(edge_type, from_id, to_id, _python_to_ffi_props(properties or {}))
512+
513+
def create_nodes(self, label: str, batch: list[dict]) -> list[str]:
514+
"""Create multiple nodes in a single batch INSERT.
515+
516+
>>> ids = conn.create_nodes("User", [{"user_id": "u1", ...}, {"user_id": "u2", ...}])
517+
"""
518+
return self._ffi.create_nodes(label, [_python_to_ffi_props(p) for p in batch])
519+
520+
def import_file(self, label: str, file_path: str) -> None:
521+
"""Import nodes from a file (CSV, Parquet, JSON — auto-detected from extension).
522+
523+
>>> conn.import_file("User", "users.csv")
524+
"""
525+
self._ffi.import_file(label, file_path)
526+
527+
def execute_sql(self, sql: str) -> None:
528+
"""Execute a raw SQL statement (DDL, DML, or administrative command).
529+
530+
>>> conn.execute_sql("OPTIMIZE TABLE default.users FINAL")
531+
"""
532+
self._ffi.execute_sql(sql)
533+
475534
def __repr__(self) -> str:
476535
return "<Connection>"
477536

478537

538+
def _python_to_ffi_value(v):
539+
"""Convert a Python value to an FFI Value enum variant."""
540+
if v is None:
541+
return _FfiValue.NULL()
542+
if isinstance(v, bool):
543+
return _FfiValue.BOOL(v=v)
544+
if isinstance(v, int):
545+
return _FfiValue.INT64(v=v)
546+
if isinstance(v, float):
547+
return _FfiValue.FLOAT64(v=v)
548+
if isinstance(v, str):
549+
return _FfiValue.STRING(v=v)
550+
if isinstance(v, list):
551+
return _FfiValue.LIST(items=[_python_to_ffi_value(i) for i in v])
552+
if isinstance(v, dict):
553+
from clickgraph._ffi import MapEntry as _FfiMapEntry
554+
return _FfiValue.MAP(entries=[
555+
_FfiMapEntry(key=str(k), value=_python_to_ffi_value(val))
556+
for k, val in v.items()
557+
])
558+
return _FfiValue.STRING(v=str(v))
559+
560+
561+
def _python_to_ffi_props(props: dict) -> dict:
562+
"""Convert a Python dict to an FFI-compatible property map."""
563+
return {str(k): _python_to_ffi_value(v) for k, v in props.items()}
564+
565+
479566
# ---------------------------------------------------------------------------
480567
# Database
481568
# ---------------------------------------------------------------------------

0 commit comments

Comments
 (0)