Skip to content
490 changes: 490 additions & 0 deletions python-sdk/README.md

Large diffs are not rendered by default.

147 changes: 147 additions & 0 deletions python-sdk/examples/basic_usage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
"""
GraphLite Python SDK - Basic Usage Example

This example demonstrates the core features of the GraphLite Python SDK:
- Opening a database
- Creating sessions
- Executing queries
- Using transactions
- Query builder API
- Typed result deserialization

Run with: python3 examples/basic_usage.py
"""

from dataclasses import dataclass
from typing import Optional
import tempfile
import shutil
import sys
from pathlib import Path

# Add parent directory to path so we can import src
sys.path.insert(0, str(Path(__file__).parent.parent))

from src.connection import GraphLite
from src.error import GraphLiteError, SerializationError, QueryError, TransactionError
from src.result import TypedResult

@dataclass
class Person:
"""Person entity for typed deserialization"""
name: str
age: int


def main() -> int:
"""Run the basic usage example"""
print("=== GraphLite SDK Basic Usage Example ===\n")

# Use temporary directory for demo
db_path = tempfile.mkdtemp(prefix="graphlite_sdk_example_")

try:
# 1. Open a database
print("1. Opening database...")
db = GraphLite.open(db_path)
print(f" ✓ Database opened at {db_path}\n")

# 2. Create a session
print("2. Creating session...")
session = db.session("admin")
print(" ✓ Session created for user 'admin'\n")

# 3. Execute DDL statements
print("3. Creating schema and graph...")
session.execute("CREATE SCHEMA IF NOT EXISTS /example")
session.execute("SESSION SET SCHEMA /example")
session.execute("CREATE GRAPH IF NOT EXISTS social")
session.execute("SESSION SET GRAPH social")
print(" ✓ Schema and graph created\n")

# 4. Insert data using transactions
print("4. Inserting data with transaction...")
with session.transaction() as tx:
tx.execute("INSERT (p:Person {name: 'Alice', age: 30})")
tx.execute("INSERT (p:Person {name: 'Bob', age: 25})")
tx.execute("INSERT (p:Person {name: 'Charlie', age: 35})")
tx.execute("INSERT (p:Person {name: 'David', age: 28})")
tx.execute("INSERT (p:Person {name: 'Eve', age: 23})")
tx.execute("INSERT (p:Person {name: 'Frank', age: 40})")
tx.commit()
print(" ✓ Inserted 6 persons\n")

# 5. Query data directly
print("5. Querying data...")
result = session.query("MATCH (p:Person) RETURN p.name as name, p.age as age")
print(f" Found {len(result.rows)} persons:")
for row in result.rows:
name = row.get("name")
age = row.get("age")
if name is not None and age is not None:
print(f" - Name: {name}, Age: {age}")
print()

# 6. Use query builder
print("6. Using query builder...")
result = (session.query_builder()
.match_pattern("(p:Person)")
.where_clause("p.age > 25")
.return_clause("p.name as name, p.age as age")
.order_by("p.age DESC")
.execute())
print(f" Found {len(result.rows)} persons over 25:")
for row in result.rows:
name = row.get("name")
age = row.get("age")
if name is not None and age is not None:
print(f" - Name: {name}, Age: {age}")
print()

# 7. Typed deserialization
print("7. Using typed deserialization...")
result = session.query("MATCH (p:Person) RETURN p.name as name, p.age as age")
typed = TypedResult(result)
people = typed.deserialize_rows(Person)
print(f" Deserialized {len(people)} persons:")
for person in people:
print(f" - {person}")
print()

# 8. Transaction with rollback
print("8. Demonstrating transaction rollback...")
try:
with session.transaction() as tx:
tx.execute("INSERT (p:Person {name: 'George', age: 50})")
print(" Inserted person 'George' in transaction")
# Transaction is NOT committed - will auto-rollback
# (by not calling tx.commit())
except Exception:
pass # Expected - rollback on exception
print(" Transaction rolled back (George not persisted)\n")

# 9. Verify rollback
result = session.query("MATCH (p:Person) RETURN count(p) as count")
if result.rows:
count = result.rows[0].get("count")
print(f" Person count after rollback: {count}\n")
print("=== Example completed successfully ===")
return 0

except GraphLiteError as e:
print(f"\n❌ GraphLite Error: {e}")
return 1

except Exception as e:
print(f"\n❌ Unexpected error: {e}")
import traceback
traceback.print_exc()
return 1

finally:
# Cleanup temporary directory
shutil.rmtree(db_path, ignore_errors=True)


if __name__ == "__main__":
exit(main())
76 changes: 76 additions & 0 deletions python-sdk/src/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
"""
GraphLite SDK - High-level Python API for GraphLite

This package provides a high-level, developer-friendly SDK on top of GraphLite's core API.
It offers ergonomic patterns, type safety, session management, query builders, and
transaction support - everything needed to build robust graph-based applications in Python.

Quick Start
-----------

```python
from graphlite_sdk import GraphLite

# Open database
db = GraphLite.open("./mydb")

# Create session
session = db.session("admin")

# Execute query
result = session.query("MATCH (p:Person) RETURN p.name")

# Use transactions
with session.transaction() as tx:
tx.execute("INSERT (p:Person {name: 'Alice'})")
tx.commit()
```

Architecture
-----------

```
Your Application
┌─────────────────────────────────────────┐
│ GraphLite SDK (this package) │
│ - GraphLite (main API) │
│ - Session (session management) │
│ - Transaction (ACID support) │
│ - QueryBuilder (fluent queries) │
│ - TypedResult (deserialization) │
└─────────────────────────────────────────┘
┌─────────────────────────────────────────┐
│ GraphLite FFI Bindings │
│ (Low-level ctypes wrapper) │
└─────────────────────────────────────────┘
┌─────────────────────────────────────────┐
│ GraphLite Core (Rust) │
│ - QueryCoordinator │
│ - Storage Engine │
│ - Catalog Manager │
└─────────────────────────────────────────┘
```
"""

from .error import GraphLiteError
from .connection import GraphLite, Session
from .transaction import Transaction
from .query import QueryBuilder
# from .result import TypedResult

__version__ = "0.1.0"

__all__ = [
"GraphLite",
"Session",
"Transaction",
"QueryBuilder",
"TypedResult",
"GraphLiteError",
]
161 changes: 161 additions & 0 deletions python-sdk/src/connection.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
"""
Database connection and session management
This module provides the main entry points for working with GraphLite databases.
"""

import sys
from pathlib import Path

# Add bindings/python to path so we can import the low-level bindings
bindings_path = Path(__file__).parent.parent.parent / "bindings" / "python"
if str(bindings_path) not in sys.path:
sys.path.insert(0, str(bindings_path))

# Low-level GraphLite binding (like QueryCoordinator in Rust)
from graphlite import GraphLite as _GraphLiteBinding, QueryResult
from .error import ConnectionError, SessionError, QueryError


class GraphLite:
"""
GraphLite database connection

High-level wrapper around the FFI bindings, similar to the Rust SDK.
"""

def __init__(self, db: _GraphLiteBinding):
"""
Internal constructor - use GraphLite.open() instead

Store instance of the low-level GraphLite binding
"""
self._db = db

@classmethod
def open(cls, path: str):
"""
Open a GraphLite database at the given path

Args:
path: Path to the database directory

Returns:
GraphLite instance

Raises:
ConnectionError: If database cannot be opened
"""
try:
db = _GraphLiteBinding(path)
return cls(db)
except Exception as e:
raise ConnectionError(f"Failed to open database: {e}")

def session(self, username: str):
"""
Create a new session for the given user

Args:
username: Username for the session

Returns:
Session instance

Raises:
SessionError: If session creation fails
"""
try:
session_id = self._db.create_session(username)
return Session(session_id, self._db, username)
except Exception as e:
raise SessionError(f"Failed to create session: {e}")

def close(self):
"""Close the database connection"""
if self._db:
self._db.close()


class Session:
"""
GraphLite database session

Provides user context for executing queries.
"""

def __init__(self, session_id: str, db: _GraphLiteBinding, username: str):
"""Internal constructor - use db.session() instead"""
self._session_id = session_id
self._db = db
self._username = username

def id(self) -> str:
"""Get the session ID"""
return self._session_id

def username(self) -> str:
"""Get the username for this session"""
return self._username

def query(self, query: str) -> QueryResult:
"""
Execute a GQL query

Args:
query: GQL query string

Returns:
QueryResult with rows and metadata

Raises:
QueryError: If query execution fails
"""
try:
return self._db.query(self._session_id, query)
except Exception as e:
raise QueryError(f"Query failed: {e}")

def execute(self, statement: str):
"""
Execute a statement without returning results

Args:
statement: GQL statement to execute

Raises:
QueryError: If execution fails
"""
try:
self._db.execute(self._session_id, statement)
except Exception as e:
raise QueryError(f"Execute failed: {e}")

def transaction(self):
"""
Begin a new transaction

Returns a Transaction object that should be used as a context manager.
The transaction will automatically roll back when the context exits
unless commit() is explicitly called.

Returns:
Transaction instance

Raises:
TransactionError: If transaction cannot be started
"""
from .transaction import Transaction
return Transaction(self)

def query_builder(self):
"""
Create a new query builder for fluent query construction

Returns:
QueryBuilder instance
"""
from .query import QueryBuilder
return QueryBuilder(self)


__all__ = ['GraphLite', 'Session', 'QueryResult']
Loading