diff --git a/cookbook/advanced/02_Advanced_Graph_Analytics.ipynb b/cookbook/advanced/02_Advanced_Graph_Analytics.ipynb index 70165452..058192da 100644 --- a/cookbook/advanced/02_Advanced_Graph_Analytics.ipynb +++ b/cookbook/advanced/02_Advanced_Graph_Analytics.ipynb @@ -189,26 +189,42 @@ "graph_store = GraphStore(backend=\"kuzu\", database_path=\"./analytics_graph_db\")\n", "graph_store.connect()\n", "\n", - "# Store entities as nodes\n", + "# Store entities as nodes and track node ID mapping\n", + "node_id_map = {}\n", "for entity in entities:\n", " node = graph_store.create_node(\n", " labels=[entity[\"type\"]],\n", " properties={\"name\": entity[\"name\"], \"original_id\": entity[\"id\"]}\n", " )\n", - " print(f\"Stored node: {entity['name']}\")\n", + " node_id_map[entity[\"id\"]] = node.get(\"id\")\n", + " print(f\"Stored node: {entity['name']} (ID: {node.get('id')})\")\n", "\n", - "# Store relationships\n", + "# Store relationships using mapped node IDs\n", "for rel in relationships:\n", - " # In a real scenario, you'd lookup node IDs first\n", - " print(f\"Relationship: {rel['source']} -{rel['type']}-> {rel['target']}\")\n", + " source_id = node_id_map.get(rel[\"source\"])\n", + " target_id = node_id_map.get(rel[\"target\"])\n", + " \n", + " if source_id is not None and target_id is not None:\n", + " relationship = graph_store.create_relationship(\n", + " start_node_id=source_id,\n", + " end_node_id=target_id,\n", + " rel_type=rel[\"type\"],\n", + " properties=rel.get(\"properties\", {})\n", + " )\n", + " print(f\"Stored relationship: {rel['source']} -{rel['type']}-> {rel['target']}\")\n", + " else:\n", + " print(f\"Warning: Could not find node IDs for relationship {rel['source']} -> {rel['target']}\")\n", "\n", "# Query using Cypher\n", "results = graph_store.execute_query(\"MATCH (n) RETURN n.name, labels(n) LIMIT 10\")\n", - "print(f\"\\nStored {len(results.get('records', []))} nodes in graph store\")\n", + "print(f\"\\nQuery results: {len(results.get('records', []))} nodes\")\n", "\n", "# Get statistics\n", "stats = graph_store.get_stats()\n", - "print(f\"Graph store stats: {stats}\")\n", + "print(f\"\\nGraph store statistics:\")\n", + "print(f\" Node count: {stats.get('node_count', 'N/A')}\")\n", + "print(f\" Relationship count: {stats.get('relationship_count', 'N/A')}\")\n", + "print(f\" Label counts: {stats.get('label_counts', {})}\")\n", "\n", "graph_store.close()\n" ] diff --git a/cookbook/introduction/01_Welcome_to_Semantica.ipynb b/cookbook/introduction/01_Welcome_to_Semantica.ipynb index fa61446c..df5a8464 100644 --- a/cookbook/introduction/01_Welcome_to_Semantica.ipynb +++ b/cookbook/introduction/01_Welcome_to_Semantica.ipynb @@ -246,8 +246,20 @@ "from semantica.graph_store import GraphStore\n", "store = GraphStore(backend=\"kuzu\", database_path=\"./my_graph_db\")\n", "store.connect()\n", - "node = store.create_node([\"Person\"], {\"name\": \"John\", \"age\": 30})\n", - "store.create_relationship(node[\"id\"], other_id, \"KNOWS\", {\"since\": 2020})\n", + "node1 = store.create_node(\n", + " labels=[\"Person\"],\n", + " properties={\"name\": \"John\", \"age\": 30}\n", + ")\n", + "node2 = store.create_node(\n", + " labels=[\"Person\"],\n", + " properties={\"name\": \"Jane\", \"age\": 28}\n", + ")\n", + "store.create_relationship(\n", + " start_node_id=node1[\"id\"],\n", + " end_node_id=node2[\"id\"],\n", + " rel_type=\"KNOWS\",\n", + " properties={\"since\": 2020}\n", + ")\n", "results = store.execute_query(\"MATCH (p:Person) RETURN p.name\")\n", "store.close()\n", "```\n", diff --git a/cookbook/introduction/09_Graph_Store.ipynb b/cookbook/introduction/09_Graph_Store.ipynb index ce0861c8..2746076d 100644 --- a/cookbook/introduction/09_Graph_Store.ipynb +++ b/cookbook/introduction/09_Graph_Store.ipynb @@ -4,50 +4,84 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/Hawksight-AI/semantica/blob/main/cookbook/introduction/10_Graph_Store.ipynb)\n", - "\n", - "# Graph Store\n", + "# Graph Store Module\n", "\n", "## Overview\n", "\n", - "This notebook demonstrates how to store and query property graphs using Semantica's graph store modules. You'll learn to use `GraphStore` with multiple backends including Neo4j, KuzuDB, and FalkorDB.\n", + "The Graph Store module provides a unified interface for working with property graph databases. It supports multiple backends (Neo4j, KuzuDB, FalkorDB) and offers comprehensive features for storing, querying, and analyzing graph data.\n", "\n", - "**Documentation**: [API Reference](https://semantica.readthedocs.io/reference/graph_store/)\n", + "### Key Features\n", + "\n", + "- **Multi-Backend Support**: Neo4j (Enterprise), KuzuDB (Embedded), FalkorDB (Redis-based)\n", + "- **Full CRUD Operations**: Create, read, update, delete nodes and relationships\n", + "- **Cypher Query Language**: Execute complex graph queries with OpenCypher support\n", + "- **Graph Analytics**: Built-in algorithms for centrality, community detection, path finding\n", + "- **Batch Operations**: Optimized bulk data loading with progress tracking\n", + "- **Transaction Support**: ACID transactions with rollback capabilities\n", + "- **Index Management**: Create and manage indexes for performance optimization\n", + "- **Convenience Functions**: Simple function-based API for common operations\n", "\n", "### Learning Objectives\n", "\n", - "- Use `GraphStore` to store nodes and relationships\n", - "- Execute Cypher queries for graph retrieval\n", - "- Use graph analytics (shortest path, neighbors)\n", - "- Compare different graph database backends\n", + "By the end of this notebook, you will be able to:\n", "\n", - "## Installation\n", - "\n", - "Install Semantica from PyPI:\n", - "\n", - "```bash\n", - "pip install semantica\n", - "# Or with all optional dependencies:\n", - "pip install semantica[all]\n", - "```\n", + "1. Initialize and configure GraphStore with different backends\n", + "2. Perform CRUD operations on nodes and relationships\n", + "3. Execute Cypher queries for complex graph operations\n", + "4. Use graph analytics algorithms (shortest path, neighbors, centrality)\n", + "5. Update and delete graph data\n", + "6. Use batch operations for efficient data loading\n", + "7. Work with convenience functions and configuration management\n", + "8. Choose the right backend for your use case\n", "\n", "---\n", "\n", - "## Prerequisites\n", + "## Installation\n", "\n", - "Install the required graph database client:\n", + "### Core Installation\n", "\n", "```bash\n", - "# For Neo4j\n", + "# Install Semantica\n", + "pip install semantica\n", + "\n", + "# Or install with all optional dependencies\n", + "pip install semantica[all]\n", + "```\n", + "\n", + "### Backend-Specific Dependencies\n", + "\n", + "```bash\n", + "# For Neo4j (requires Neo4j server)\n", "pip install neo4j\n", "\n", "# For KuzuDB (embedded - no server required)\n", "pip install kuzu\n", "\n", - "# For FalkorDB\n", + "# For FalkorDB (requires Redis/FalkorDB server)\n", "pip install falkordb\n", - "# And run: docker run -p 6379:6379 -p 3000:3000 -it --rm falkordb/falkordb\n", - "```\n" + "```\n", + "\n", + "### Docker Setup (Optional)\n", + "\n", + "For FalkorDB, you can run it in Docker:\n", + "\n", + "```bash\n", + "docker run -p 6379:6379 -p 3000:3000 -it --rm \\\n", + " -v ./data:/var/lib/falkordb/data \\\n", + " falkordb/falkordb\n", + "```\n", + "\n", + "---\n", + "\n", + "## Backend Comparison\n", + "\n", + "| Backend | Best For | Deployment | Features |\n", + "|---------|----------|------------|----------|\n", + "| **Neo4j** | Enterprise applications, production systems | Server/Cloud | Full Cypher, APOC procedures, multi-database |\n", + "| **KuzuDB** | Analytics, embedded applications, development | Embedded (no server) | Fast analytical queries, zero-config |\n", + "| **FalkorDB** | LLM applications, real-time systems, high performance | Redis-based | Ultra-fast, sparse matrix operations |\n", + "\n", + "**Recommendation**: Start with **KuzuDB** for development and learning (no setup required), then move to **Neo4j** or **FalkorDB** for production.\n" ] }, { @@ -56,7 +90,7 @@ "source": [ "## Step 1: Initialize Graph Store\n", "\n", - "Create a graph store with your preferred backend.\n" + "Initialize a `GraphStore` instance with your preferred backend. For this tutorial, we'll use **KuzuDB** (embedded, no server setup required).\n" ] }, { @@ -67,7 +101,7 @@ "source": [ "from semantica.graph_store import GraphStore\n", "\n", - "# Option 1: Neo4j (requires Neo4j server)\n", + "# Option 1: Neo4j (requires Neo4j server running)\n", "# store = GraphStore(\n", "# backend=\"neo4j\",\n", "# uri=\"bolt://localhost:7687\",\n", @@ -75,7 +109,7 @@ "# password=\"password\"\n", "# )\n", "\n", - "# Option 2: KuzuDB (embedded - no server required)\n", + "# Option 2: KuzuDB (embedded - no server required) - Recommended for learning\n", "store = GraphStore(\n", " backend=\"kuzu\",\n", " database_path=\"./demo_graph_db\"\n", @@ -90,16 +124,21 @@ "# )\n", "\n", "# Connect to the database\n", - "store.connect()\n" + "store.connect()\n", + "print(\"Connected to graph database successfully!\")\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Step 2: Create Nodes\n", + "## Step 2: Node Operations\n", "\n", - "Create nodes with labels and properties.\n" + "### Creating Nodes\n", + "\n", + "Nodes represent entities in your graph. Each node can have:\n", + "- **Labels**: Categories/types (e.g., `Person`, `Company`, `Location`)\n", + "- **Properties**: Key-value pairs (e.g., `{\"name\": \"Alice\", \"age\": 30}`)\n" ] }, { @@ -108,24 +147,24 @@ "metadata": {}, "outputs": [], "source": [ - "# Create individual nodes\n", + "# Create individual nodes with labels and properties\n", "apple = store.create_node(\n", " labels=[\"Company\"],\n", " properties={\"name\": \"Apple Inc.\", \"founded\": 1976, \"industry\": \"Technology\"}\n", ")\n", - "print(f\"Created company node: {apple}\")\n", + "print(f\"Created company node: {apple.get('properties', {}).get('name')} (ID: {apple.get('id')})\")\n", "\n", "tim_cook = store.create_node(\n", " labels=[\"Person\"],\n", " properties={\"name\": \"Tim Cook\", \"title\": \"CEO\", \"age\": 63}\n", ")\n", - "print(f\"Created person node: {tim_cook}\")\n", + "print(f\"Created person node: {tim_cook.get('properties', {}).get('name')} (ID: {tim_cook.get('id')})\")\n", "\n", "cupertino = store.create_node(\n", " labels=[\"Location\"],\n", " properties={\"name\": \"Cupertino\", \"state\": \"California\", \"country\": \"USA\"}\n", ")\n", - "print(f\"Created location node: {cupertino}\")\n" + "print(f\"Created location node: {cupertino.get('properties', {}).get('name')} (ID: {cupertino.get('id')})\")\n" ] }, { @@ -134,7 +173,7 @@ "metadata": {}, "outputs": [], "source": [ - "# Create multiple nodes in batch\n", + "# Create multiple nodes in batch (more efficient for large datasets)\n", "other_companies = store.create_nodes([\n", " {\"labels\": [\"Company\"], \"properties\": {\"name\": \"Microsoft\", \"founded\": 1975}},\n", " {\"labels\": [\"Company\"], \"properties\": {\"name\": \"Google\", \"founded\": 1998}},\n", @@ -147,9 +186,14 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Step 3: Create Relationships\n", + "## Step 3: Relationship Operations\n", "\n", - "Create relationships between nodes.\n" + "### Creating Relationships\n", + "\n", + "Relationships connect nodes and represent connections between entities. Each relationship has:\n", + "- **Type**: The relationship type (e.g., `CEO_OF`, `LOCATED_IN`, `KNOWS`)\n", + "- **Properties**: Key-value pairs (e.g., `{\"since\": 2011}`)\n", + "- **Direction**: From `start_node_id` to `end_node_id`\n" ] }, { @@ -158,14 +202,14 @@ "metadata": {}, "outputs": [], "source": [ - "# Create relationships\n", + "# Create relationships between nodes\n", "ceo_rel = store.create_relationship(\n", " start_node_id=tim_cook[\"id\"],\n", " end_node_id=apple[\"id\"],\n", " rel_type=\"CEO_OF\",\n", " properties={\"since\": 2011}\n", ")\n", - "print(f\"Created CEO relationship: {ceo_rel}\")\n", + "print(f\"Created relationship: {ceo_rel.get('type')} (ID: {ceo_rel.get('id')})\")\n", "\n", "location_rel = store.create_relationship(\n", " start_node_id=apple[\"id\"],\n", @@ -173,16 +217,18 @@ " rel_type=\"HEADQUARTERED_IN\",\n", " properties={\"since\": 1977}\n", ")\n", - "print(f\"Created location relationship: {location_rel}\")\n" + "print(f\"Created relationship: {location_rel.get('type')} (ID: {location_rel.get('id')})\")\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Step 4: Query Nodes and Relationships\n", + "## Step 4: Querying Nodes and Relationships\n", "\n", - "Retrieve nodes and relationships from the graph.\n" + "### Retrieving Nodes\n", + "\n", + "You can query nodes by labels, properties, or node IDs.\n" ] }, { @@ -191,11 +237,18 @@ "metadata": {}, "outputs": [], "source": [ - "# Get all Company nodes\n", + "# Get nodes by label\n", "companies = store.get_nodes(labels=[\"Company\"], limit=10)\n", "print(f\"Found {len(companies)} companies:\")\n", "for company in companies:\n", - " print(f\" - {company.get('properties', {}).get('name', 'Unknown')}\")\n" + " name = company.get('properties', {}).get('name', 'Unknown')\n", + " founded = company.get('properties', {}).get('founded', 'N/A')\n", + " print(f\" - {name} (founded: {founded})\")\n", + "\n", + "# Get a specific node by ID\n", + "if apple.get('id'):\n", + " node = store.get_node(node_id=apple[\"id\"])\n", + " print(f\"\\nRetrieved node by ID: {node.get('properties', {}).get('name')}\")\n" ] }, { @@ -208,16 +261,29 @@ "relationships = store.get_relationships(node_id=apple[\"id\"], direction=\"both\")\n", "print(f\"Found {len(relationships)} relationships for Apple:\")\n", "for rel in relationships:\n", - " print(f\" - Type: {rel.get('type')}, Properties: {rel.get('properties')}\")\n" + " rel_type = rel.get('type', 'Unknown')\n", + " props = rel.get('properties', {})\n", + " print(f\" - {rel_type}: {props}\")\n", + "\n", + "# Get relationships by type and direction\n", + "if tim_cook.get('id'):\n", + " outgoing = store.get_relationships(\n", + " node_id=tim_cook[\"id\"],\n", + " rel_type=\"CEO_OF\",\n", + " direction=\"out\"\n", + " )\n", + " print(f\"\\nOutgoing CEO_OF relationships: {len(outgoing)}\")\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Step 5: Execute Cypher Queries\n", + "## Step 5: Cypher Query Execution\n", "\n", - "Use Cypher queries for complex graph operations.\n" + "### Executing Cypher Queries\n", + "\n", + "Cypher is a powerful graph query language that allows you to express complex graph patterns and operations. The Graph Store module supports **OpenCypher** syntax across all backends.\n" ] }, { @@ -226,14 +292,18 @@ "metadata": {}, "outputs": [], "source": [ - "# Execute a Cypher query\n", + "# Execute a Cypher query to find CEO relationships\n", "results = store.execute_query(\"\"\"\n", " MATCH (p:Person)-[r:CEO_OF]->(c:Company)\n", " RETURN p.name as person, c.name as company, r.since as since\n", "\"\"\")\n", "\n", + "print(\"CEO Relationships:\")\n", "for record in results.get(\"records\", []):\n", - " print(f\" {record}\")\n" + " person = record.get('person', 'Unknown')\n", + " company = record.get('company', 'Unknown')\n", + " since = record.get('since', 'N/A')\n", + " print(f\" - {person} is CEO of {company} since {since}\")\n" ] }, { @@ -242,14 +312,17 @@ "metadata": {}, "outputs": [], "source": [ - "# Parameterized query\n", + "# Parameterized query (safer and more efficient)\n", "results = store.execute_query(\n", - " \"MATCH (c:Company) WHERE c.founded > $year RETURN c.name, c.founded\",\n", + " \"MATCH (c:Company) WHERE c.founded > $year RETURN c.name, c.founded ORDER BY c.founded\",\n", " parameters={\"year\": 1990}\n", ")\n", "\n", + "print(\"Companies founded after 1990:\")\n", "for record in results.get(\"records\", []):\n", - " print(f\" {record}\")\n" + " name = record.get('c.name', 'Unknown')\n", + " founded = record.get('c.founded', 'N/A')\n", + " print(f\" - {name} (founded: {founded})\")\n" ] }, { @@ -258,7 +331,9 @@ "source": [ "## Step 6: Graph Analytics\n", "\n", - "Use built-in graph analytics functions.\n" + "### Built-in Analytics Algorithms\n", + "\n", + "The Graph Store module provides several graph analytics algorithms for analyzing your graph structure.\n" ] }, { @@ -267,16 +342,19 @@ "metadata": {}, "outputs": [], "source": [ - "# Get neighbors of a node\n", - "neighbors = store.get_neighbors(\n", - " node_id=apple[\"id\"],\n", - " direction=\"both\",\n", - " depth=2\n", - ")\n", - "\n", - "print(f\"Found {len(neighbors)} neighbors (up to depth 2):\")\n", - "for neighbor in neighbors:\n", - " print(f\" - {neighbor.get('properties', {}).get('name', 'Unknown')}\")\n" + "# Get neighbors of a node (traverse the graph)\n", + "if apple.get('id'):\n", + " neighbors = store.get_neighbors(\n", + " node_id=apple[\"id\"],\n", + " direction=\"both\",\n", + " depth=2\n", + " )\n", + " \n", + " print(f\"Found {len(neighbors)} neighbors (up to depth 2) for Apple:\")\n", + " for neighbor in neighbors:\n", + " name = neighbor.get('properties', {}).get('name', 'Unknown')\n", + " labels = neighbor.get('labels', [])\n", + " print(f\" - {name} ({', '.join(labels)})\")\n" ] }, { @@ -285,26 +363,32 @@ "metadata": {}, "outputs": [], "source": [ - "# Find shortest path (if nodes are connected)\n", - "path = store.shortest_path(\n", - " start_node_id=tim_cook[\"id\"],\n", - " end_node_id=cupertino[\"id\"],\n", - " max_depth=5\n", - ")\n", - "\n", - "if path:\n", - " print(f\"Shortest path length: {path.get('length')}\")\n", - " print(f\"Nodes in path: {len(path.get('nodes', []))}\")\n", - "else:\n" + "# Find shortest path between two nodes\n", + "if tim_cook.get('id') and cupertino.get('id'):\n", + " path = store.shortest_path(\n", + " start_node_id=tim_cook[\"id\"],\n", + " end_node_id=cupertino[\"id\"],\n", + " max_depth=5\n", + " )\n", + " \n", + " if path:\n", + " print(f\"Shortest path found:\")\n", + " print(f\" - Path length: {path.get('length')}\")\n", + " print(f\" - Nodes in path: {len(path.get('nodes', []))}\")\n", + " print(f\" - Relationships: {len(path.get('relationships', []))}\")\n", + " else:\n", + " print(\"No path found between the nodes\")\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Step 7: Get Graph Statistics\n", + "## Step 7: Update and Delete Operations\n", "\n", - "Get statistics about the graph.\n" + "### Updating Nodes\n", + "\n", + "You can update node properties using the `update_node` method.\n" ] }, { @@ -313,21 +397,173 @@ "metadata": {}, "outputs": [], "source": [ + "# Update node properties (merge mode - adds/updates properties)\n", + "if tim_cook.get('id'):\n", + " updated = store.update_node(\n", + " node_id=tim_cook[\"id\"],\n", + " properties={\"age\": 64, \"title\": \"CEO & President\"},\n", + " merge=True # Merge with existing properties\n", + " )\n", + " print(f\"Updated node: {updated.get('properties', {}).get('name')}\")\n", + " print(f\" New age: {updated.get('properties', {}).get('age')}\")\n", + " print(f\" New title: {updated.get('properties', {}).get('title')}\")\n", + "\n", + "# Example: Replace all properties (merge=False)\n", + "# updated = store.update_node(\n", + "# node_id=node_id,\n", + "# properties={\"name\": \"New Name\"},\n", + "# merge=False # Replace all properties\n", + "# )\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Step 8: Delete Operations\n", + "\n", + "### Deleting Nodes and Relationships\n", + "\n", + "You can delete nodes and relationships when needed.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Delete a relationship\n", + "if location_rel.get('id'):\n", + " deleted = store.delete_relationship(rel_id=location_rel[\"id\"])\n", + " if deleted:\n", + " print(f\"Deleted relationship (ID: {location_rel['id']})\")\n", + "\n", + "# Delete a node (with detach=True to also delete its relationships)\n", + "# WARNING: This will delete the node and all its relationships\n", + "# Uncomment to test:\n", + "# if cupertino.get('id'):\n", + "# deleted = store.delete_node(node_id=cupertino[\"id\"], detach=True)\n", + "# if deleted:\n", + "# print(f\"Deleted node: {cupertino.get('properties', {}).get('name')}\")\n", + "\n", + "print(\"\\nTip: Use detach=True to delete a node and all its relationships\")\n", + "print(\" Use detach=False to only delete the node (fails if relationships exist)\")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Step 9: Graph Statistics\n", + "\n", + "Get comprehensive statistics about your graph.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Get comprehensive graph statistics\n", "stats = store.get_stats()\n", "\n", - "print(f\" Node count: {stats.get('node_count', 'N/A')}\")\n", - "print(f\" Relationship count: {stats.get('relationship_count', 'N/A')}\")\n", - "print(f\" Label counts: {stats.get('label_counts', {})}\")\n", - "print(f\" Relationship types: {stats.get('relationship_type_counts', {})}\")\n" + "print(\"Graph Statistics:\")\n", + "print(f\" Total nodes: {stats.get('node_count', 'N/A')}\")\n", + "print(f\" Total relationships: {stats.get('relationship_count', 'N/A')}\")\n", + "print(f\"\\nNode labels:\")\n", + "for label, count in stats.get('label_counts', {}).items():\n", + " print(f\" - {label}: {count} nodes\")\n", + "print(f\"\\nRelationship types:\")\n", + "for rel_type, count in stats.get('relationship_type_counts', {}).items():\n", + " print(f\" - {rel_type}: {count} relationships\")\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Step 8: Clean Up\n", + "## Step 10: Convenience Functions\n", "\n", - "Close the connection when done.\n" + "The Graph Store module provides convenience functions for simpler, function-based operations.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Using convenience functions (alternative to class methods)\n", + "from semantica.graph_store import (\n", + " create_node,\n", + " create_relationship,\n", + " get_nodes,\n", + " execute_query,\n", + " shortest_path\n", + ")\n", + "\n", + "# These functions work with a default store instance\n", + "# For this example, we'll continue using the store instance we created\n", + "\n", + "# Example: Using convenience functions\n", + "# node = create_node(\n", + "# labels=[\"Person\"],\n", + "# properties={\"name\": \"Alice\", \"age\": 30}\n", + "# )\n", + "\n", + "print(\"Convenience functions available:\")\n", + "print(\" - create_node, create_nodes\")\n", + "print(\" - create_relationship, create_relationships\")\n", + "print(\" - get_nodes, get_relationships\")\n", + "print(\" - update_node, delete_node\")\n", + "print(\" - execute_query, shortest_path, get_neighbors\")\n", + "print(\" - run_analytics\")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Step 11: Index Management\n", + "\n", + "Create indexes to improve query performance, especially for large graphs.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Create an index on a node property for faster lookups\n", + "# This is especially useful for frequently queried properties\n", + "\n", + "index_created = store.create_index(\n", + " label=\"Company\",\n", + " property_name=\"name\",\n", + " index_type=\"btree\" # Default index type\n", + ")\n", + "\n", + "if index_created:\n", + " print(\"Created index on Company.name for faster queries\")\n", + "else:\n", + " print(\"Index may already exist or not be supported by this backend\")\n", + "\n", + "# Note: Index creation support varies by backend\n", + "# Neo4j: Full support for various index types\n", + "# KuzuDB: Automatic indexing on primary keys\n", + "# FalkorDB: Limited index support\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Step 12: Clean Up\n", + "\n", + "Always close the connection when you're done to free up resources.\n" ] }, { @@ -337,7 +573,8 @@ "outputs": [], "source": [ "# Close the connection\n", - "store.close()\n" + "store.close()\n", + "print(\"Connection closed successfully\")\n" ] }, { @@ -346,24 +583,21 @@ "source": [ "## Summary\n", "\n", - "You've learned how to use graph stores:\n", + "This notebook covered the Graph Store module, a unified interface for property graph databases supporting Neo4j, KuzuDB, and FalkorDB.\n", "\n", - "- **GraphStore**: Unified interface for property graph databases\n", - "- **Multiple Backends**: Neo4j, KuzuDB, FalkorDB support\n", - "- **Node Operations**: Create, read, update, delete nodes\n", - "- **Relationship Operations**: Create and query relationships\n", - "- **Cypher Queries**: Execute powerful graph queries\n", - "- **Graph Analytics**: Shortest path, neighbors, centrality\n", + "### What You Learned\n", "\n", - "### Backend Comparison\n", + "- **CRUD Operations**: Create, read, update, and delete nodes and relationships\n", + "- **Cypher Queries**: Execute complex graph queries with OpenCypher syntax\n", + "- **Graph Analytics**: Shortest path, neighbor traversal, and centrality algorithms\n", + "- **Batch Operations**: Efficient bulk data loading for large datasets\n", + "- **Index Management**: Performance optimization through indexing\n", "\n", - "| Backend | Best For | Deployment |\n", - "|---------|----------|------------|\n", - "| **Neo4j** | Enterprise, full features | Server/Cloud |\n", - "| **KuzuDB** | Analytics, embedded | Embedded (no server) |\n", - "| **FalkorDB** | LLM apps, real-time | Redis-based |\n", + "### Key Takeaways\n", "\n", - "Next: Learn how to visualize graphs in the Visualization notebook.\n" + "- **Backend Selection**: Use KuzuDB for development, Neo4j for production, FalkorDB for high-performance applications\n", + "- **Best Practices**: Use batch operations, parameterized queries, and proper connection management\n", + "- **Next Steps**: Explore advanced analytics, graph quality, and visualization modules\n" ] } ], diff --git a/cookbook/use_cases/advanced_rag/01_GraphRAG_Complete.ipynb b/cookbook/use_cases/advanced_rag/01_GraphRAG_Complete.ipynb index 1bc03cfb..78e146e5 100644 --- a/cookbook/use_cases/advanced_rag/01_GraphRAG_Complete.ipynb +++ b/cookbook/use_cases/advanced_rag/01_GraphRAG_Complete.ipynb @@ -732,7 +732,34 @@ "metadata": {}, "outputs": [], "source": [ - "# Optional: Store graph in persistent graph database# Uncomment to use KuzuDB (embedded, no server required)# graph_store = GraphStore(backend=\"kuzu\", database_path=\"./graphrag_db\")# graph_store.connect()# # # Store nodes# for node_id, node_data in knowledge_graph.nodes(data=True):# labels = [node_data.get('type', 'Entity')]# properties = {k: v for k, v in node_data.items() if k != 'type'}# graph_store.create_node(labels, properties)# # # Store relationships# for source, target, edge_data in knowledge_graph.edges(data=True):# rel_type = edge_data.get('type', 'RELATED_TO')# properties = {k: v for k, v in edge_data.items() if k != 'type'}# graph_store.create_relationship(source, target, rel_type, properties)# # graph_store.close()# print(\"Knowledge graph stored in database\")print(\"Graph storage is optional. The in-memory graph is ready for GraphRAG.\")\n" + "# Optional: Store graph in persistent graph database\n", + "# Uncomment to use KuzuDB (embedded, no server required)\n", + "# graph_store = GraphStore(backend=\"kuzu\", database_path=\"./graphrag_db\")\n", + "# graph_store.connect()\n", + "# \n", + "# # Store nodes and track node ID mapping\n", + "# node_id_map = {}\n", + "# for node_id, node_data in knowledge_graph.nodes(data=True):\n", + "# labels = [node_data.get('type', 'Entity')]\n", + "# properties = {k: v for k, v in node_data.items() if k != 'type'}\n", + "# created_node = graph_store.create_node(labels, properties)\n", + "# node_id_map[node_id] = created_node.get(\"id\")\n", + "# \n", + "# # Store relationships using mapped node IDs\n", + "# for source, target, edge_data in knowledge_graph.edges(data=True):\n", + "# if source in node_id_map and target in node_id_map:\n", + "# rel_type = edge_data.get('type', 'RELATED_TO')\n", + "# properties = {k: v for k, v in edge_data.items() if k != 'type'}\n", + "# graph_store.create_relationship(\n", + "# start_node_id=node_id_map[source],\n", + "# end_node_id=node_id_map[target],\n", + "# rel_type=rel_type,\n", + "# properties=properties\n", + "# )\n", + "# \n", + "# graph_store.close()\n", + "# print(\"Knowledge graph stored in database\")\n", + "print(\"Graph storage is optional. The in-memory graph is ready for GraphRAG.\")\n" ] }, { diff --git a/docs/examples.md b/docs/examples.md index ee0b0c2e..12d46647 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -185,9 +185,19 @@ store = GraphStore( store.connect() # Create nodes and relationships -apple = store.create_node(["Company"], {"name": "Apple Inc."}) -tim = store.create_node(["Person"], {"name": "Tim Cook"}) -store.create_relationship(tim["id"], apple["id"], "CEO_OF") +apple = store.create_node( + labels=["Company"], + properties={"name": "Apple Inc."} +) +tim = store.create_node( + labels=["Person"], + properties={"name": "Tim Cook"} +) +store.create_relationship( + start_node_id=tim["id"], + end_node_id=apple["id"], + rel_type="CEO_OF" +) store.close() ``` diff --git a/docs/modules.md b/docs/modules.md index b007fd4e..7411cd71 100644 --- a/docs/modules.md +++ b/docs/modules.md @@ -541,9 +541,20 @@ store = GraphStore(backend="neo4j", uri="bolt://localhost:7687") store.connect() # Create nodes and relationships -alice = store.create_node(["Person"], {"name": "Alice", "age": 30}) -bob = store.create_node(["Person"], {"name": "Bob", "age": 25}) -store.create_relationship(alice["id"], bob["id"], "KNOWS", {"since": 2020}) +alice = store.create_node( + labels=["Person"], + properties={"name": "Alice", "age": 30} +) +bob = store.create_node( + labels=["Person"], + properties={"name": "Bob", "age": 25} +) +store.create_relationship( + start_node_id=alice["id"], + end_node_id=bob["id"], + rel_type="KNOWS", + properties={"since": 2020} +) # Query with Cypher results = store.execute_query("MATCH (p:Person) RETURN p.name") diff --git a/docs/reference/graph_store.md b/docs/reference/graph_store.md index 93de9da3..22ff4904 100644 --- a/docs/reference/graph_store.md +++ b/docs/reference/graph_store.md @@ -74,7 +74,9 @@ ## Main Classes -### GraphStore +### Core Classes + +#### GraphStore The main facade for graph operations. @@ -82,9 +84,28 @@ The main facade for graph operations. | Method | Description | |--------|-------------| -| `execute_query(query, params)` | Run Cypher query | -| `create_node(labels, props)` | Add node | -| `create_relationship(start, end, type)` | Add edge | +| `connect(**options)` | Connect to the graph database | +| `close()` | Close connection to the graph database | +| `create_node(labels, properties, **options)` | Create a single node | +| `create_nodes(nodes, **options)` | Create multiple nodes in batch | +| `get_node(node_id, **options)` | Get a node by ID | +| `get_nodes(labels, properties, limit, **options)` | Get nodes matching criteria | +| `update_node(node_id, properties, merge, **options)` | Update node properties | +| `delete_node(node_id, detach, **options)` | Delete a node | +| `create_relationship(start_node_id, end_node_id, rel_type, properties, **options)` | Create a relationship | +| `get_relationships(node_id, rel_type, direction, limit, **options)` | Get relationships | +| `delete_relationship(rel_id, **options)` | Delete a relationship | +| `execute_query(query, parameters, **options)` | Execute a Cypher/OpenCypher query | +| `shortest_path(start_node_id, end_node_id, rel_type, max_depth, **options)` | Find shortest path between nodes | +| `get_neighbors(node_id, rel_type, direction, depth, **options)` | Get neighboring nodes | +| `get_stats()` | Get graph statistics | +| `create_index(label, property_name, index_type, **options)` | Create an index | + +**Properties:** +- `nodes` - Access to NodeManager +- `relationships` - Access to RelationshipManager +- `query_engine` - Access to QueryEngine +- `analytics` - Access to GraphAnalytics **Example:** @@ -92,51 +113,262 @@ The main facade for graph operations. from semantica.graph_store import GraphStore store = GraphStore(backend="neo4j") +store.connect() store.execute_query( "MATCH (n:Person {name: $name}) RETURN n", - params={"name": "Alice"} + parameters={"name": "Alice"} ) +store.close() ``` -### Neo4jAdapter +#### GraphManager -Enterprise-grade backend. +Manager for graph store operations. Provides access to node, relationship, query, and analytics managers. + +**Methods:** +- `get_stats()` - Get graph statistics +- `create_index(label, property_name, index_type, **options)` - Create an index + +#### NodeManager + +Manager for node CRUD operations. + +**Methods:** +- `create(labels, properties, **options)` - Create a node +- `create_batch(nodes, **options)` - Create multiple nodes +- `get(node_id, labels, properties, limit, **options)` - Get node(s) +- `update(node_id, properties, merge, **options)` - Update a node +- `delete(node_id, detach, **options)` - Delete a node + +#### RelationshipManager + +Manager for relationship CRUD operations. + +**Methods:** +- `create(start_node_id, end_node_id, rel_type, properties, **options)` - Create a relationship +- `get(node_id, rel_type, direction, limit, **options)` - Get relationships +- `delete(rel_id, **options)` - Delete a relationship + +#### QueryEngine + +Engine for query execution and optimization. + +**Methods:** +- `execute(query, parameters, use_cache, **options)` - Execute a Cypher/OpenCypher query +- `clear_cache()` - Clear query cache +- `enable_cache()` - Enable query caching +- `disable_cache()` - Disable query caching + +#### GraphAnalytics + +Graph analytics and algorithms. + +**Methods:** +- `shortest_path(start_node_id, end_node_id, rel_type, max_depth, **options)` - Find shortest path +- `get_neighbors(node_id, rel_type, direction, depth, **options)` - Get neighboring nodes +- `degree_centrality(labels, rel_type, direction, **options)` - Calculate degree centrality +- `connected_components(labels, **options)` - Find connected components + +### Adapter Classes + +#### Neo4jAdapter + +Enterprise-grade Neo4j backend adapter. **Features:** - Bolt protocol support - Cluster awareness - APOC procedure integration +- Multi-database support +- Transaction support -### KuzuAdapter +**Related Classes:** +- `Neo4jDriver` - Neo4j driver wrapper +- `Neo4jSession` - Session management wrapper +- `Neo4jTransaction` - Transaction wrapper -Embedded, in-process backend. +#### KuzuAdapter + +Embedded, in-process KuzuDB backend adapter. **Features:** - No external server required - Columnar storage for speed - Zero-copy integration with Arrow +- Schema-based node and relationship tables +- High-performance analytical queries -### FalkorDBAdapter +**Related Classes:** +- `KuzuDatabase` - Database wrapper +- `KuzuConnection` - Connection wrapper +- `KuzuQuery` - Query execution wrapper -High-performance Redis module. +**Special Methods:** +- `create_node_table(table_name, properties, primary_key, **options)` - Create node table with schema +- `create_rel_table(table_name, from_table, to_table, properties, **options)` - Create relationship table +- `bulk_load_nodes(table_name, file_path, **options)` - Bulk load nodes from CSV + +#### FalkorDBAdapter + +High-performance Redis-based FalkorDB backend adapter. **Features:** - Sparse matrix representation - Ultra-low latency - Redis protocol +- Multi-graph support +- Linear algebra based querying + +**Related Classes:** +- `FalkorDBClient` - Client wrapper +- `FalkorDBGraph` - Graph wrapper with operations +- `FalkorDBQuery` - Query execution wrapper + +**Special Methods:** +- `select_graph(graph_name)` - Select or create a graph +- `list_graphs()` - List all available graphs +- `delete_graph(graph_name)` - Delete a graph + +### Configuration and Registry Classes + +#### GraphStoreConfig + +Configuration manager for graph store module. Supports environment variables, config files (YAML, JSON, TOML), and programmatic configuration. + +**Methods:** +- `get(key, default)` - Get configuration value +- `set(key, value)` - Set configuration value +- `update(config)` - Update configuration with dictionary +- `get_method_config(method_name)` - Get method-specific configuration +- `set_method_config(method_name, config)` - Set method-specific configuration +- `get_all()` - Get all configuration +- `get_neo4j_config()` - Get Neo4j-specific configuration +- `get_kuzu_config()` - Get KuzuDB-specific configuration +- `get_falkordb_config()` - Get FalkorDB-specific configuration +- `reset()` - Reset configuration to defaults + +**Global Instance:** +- `graph_store_config` - Global configuration instance + +#### MethodRegistry + +Registry for custom graph store methods, enabling extensibility. + +**Methods:** +- `register(task, method_name, method_func, **metadata)` - Register a method +- `unregister(task, method_name)` - Unregister a method +- `get(task, method_name)` - Get a registered method +- `list_all(task)` - List all registered methods +- `has(task, method_name)` - Check if a method is registered +- `get_metadata(task, method_name)` - Get metadata for a registered method + +**Supported Task Types:** +- `node` - Node CRUD methods +- `relationship` - Relationship CRUD methods +- `query` - Query execution methods +- `traversal` - Graph traversal methods +- `analytics` - Graph analytics methods +- `bulk` - Bulk operation methods + +**Global Instance:** +- `method_registry` - Global method registry instance --- ## Convenience Functions -```python -from semantica.graph_store import execute_query, create_node +The module provides convenience functions for common graph operations. These functions use a global GraphStore instance and support method registration for extensibility. -# Quick query -results = execute_query("MATCH (n) RETURN count(n) as count") +### Node Operations + +| Function | Description | +|----------|-------------| +| `create_node(labels, properties, method, **options)` | Create a single node | +| `create_nodes(nodes, method, **options)` | Create multiple nodes in batch | +| `get_nodes(labels, properties, limit, method, **options)` | Get nodes matching criteria | +| `update_node(node_id, properties, merge, method, **options)` | Update node properties | +| `delete_node(node_id, detach, method, **options)` | Delete a node | + +### Relationship Operations + +| Function | Description | +|----------|-------------| +| `create_relationship(start_id, end_id, rel_type, properties, method, **options)` | Create a relationship | +| `create_relationships(relationships, method, **options)` | Create multiple relationships in batch | +| `get_relationships(node_id, rel_type, direction, limit, method, **options)` | Get relationships matching criteria | +| `update_relationship(rel_id, properties, method, **options)` | Update relationship properties | +| `delete_relationship(rel_id, method, **options)` | Delete a relationship | + +### Query Operations + +| Function | Description | +|----------|-------------| +| `execute_query(query, parameters, method, **options)` | Execute a Cypher/OpenCypher query | + +### Analytics Operations + +| Function | Description | +|----------|-------------| +| `shortest_path(start_node_id, end_node_id, rel_type, max_depth, method, **options)` | Find shortest path between nodes | +| `get_neighbors(node_id, rel_type, direction, depth, method, **options)` | Get neighboring nodes | +| `run_analytics(algorithm, method, **options)` | Run graph analytics algorithm | + +### Utility Functions + +| Function | Description | +|----------|-------------| +| `get_graph_store_method(task, method_name)` | Get graph store method by task and name | +| `list_available_methods(task)` | List all available graph store methods | + +**Example:** + +```python +from semantica.graph_store import ( + create_node, + create_nodes, + create_relationship, + execute_query, + shortest_path, + get_neighbors, + run_analytics +) # Quick node creation -create_node(["Person"], {"name": "Bob"}) +alice = create_node(["Person"], {"name": "Alice", "age": 30}) +bob = create_node(["Person"], {"name": "Bob", "age": 25}) + +# Batch node creation +people = create_nodes([ + {"labels": ["Person"], "properties": {"name": "Charlie"}}, + {"labels": ["Person"], "properties": {"name": "Diana"}} +]) + +# Create relationship +rel = create_relationship( + start_id=alice["id"], + end_id=bob["id"], + rel_type="KNOWS", + properties={"since": 2020} +) + +# Quick query +results = execute_query("MATCH (n:Person) RETURN count(n) as count") + +# Find shortest path +path = shortest_path( + start_node_id=alice["id"], + end_node_id=bob["id"], + max_depth=5 +) + +# Get neighbors +neighbors = get_neighbors(node_id=alice["id"], depth=2) + +# Run analytics +centrality = run_analytics( + algorithm="degree_centrality", + labels=["Person"] +) ``` --- @@ -189,7 +421,7 @@ MATCH (n)-[r]-(m) WHERE elementId(n) IN $ids RETURN n, r, m """ -subgraph = graph_store.execute_query(query, params={"ids": node_ids}) +subgraph = graph_store.execute_query(query, parameters={"ids": node_ids}) ``` --- diff --git a/semantica/graph_store/__init__.py b/semantica/graph_store/__init__.py index cd796cfa..e8627fc3 100644 --- a/semantica/graph_store/__init__.py +++ b/semantica/graph_store/__init__.py @@ -84,7 +84,7 @@ Example Usage: >>> from semantica.graph_store import GraphStore, create_node, create_relationship, execute_query >>> # Using convenience functions >>> node_id = create_node(labels=["Person"], properties={"name": "Alice", "age": 30}) - >>> rel_id = create_relationship(start_id=node1_id, end_id=node2_id, type="KNOWS", properties={"since": 2020}) + >>> rel_id = create_relationship(start_id=node1_id, end_id=node2_id, rel_type="KNOWS", properties={"since": 2020}) >>> results = execute_query("MATCH (p:Person) WHERE p.age > 25 RETURN p.name") >>> # Using classes directly >>> store = GraphStore(backend="neo4j", uri="bolt://localhost:7687") diff --git a/semantica/graph_store/graph_store_usage.md b/semantica/graph_store/graph_store_usage.md index 7be7fa84..3988f7fa 100644 --- a/semantica/graph_store/graph_store_usage.md +++ b/semantica/graph_store/graph_store_usage.md @@ -455,6 +455,81 @@ from semantica.graph_store import ( ) ``` +## Configuration Management + +### Using GraphStoreConfig + +The `GraphStoreConfig` class provides centralized configuration management: + +```python +from semantica.graph_store import GraphStoreConfig, graph_store_config + +# Get configuration value +default_backend = graph_store_config.get("default_backend", default="neo4j") + +# Set configuration value +graph_store_config.set("default_backend", "falkordb") + +# Update multiple values +graph_store_config.update({ + "batch_size": 2000, + "timeout": 60 +}) + +# Get backend-specific configuration +neo4j_config = graph_store_config.get_neo4j_config() +kuzu_config = graph_store_config.get_kuzu_config() +falkordb_config = graph_store_config.get_falkordb_config() + +# Get all configuration +all_config = graph_store_config.get_all() + +# Reset to defaults +graph_store_config.reset() +``` + +### Method Registry + +The `MethodRegistry` class allows you to register custom methods for extensibility: + +```python +from semantica.graph_store import MethodRegistry, method_registry + +# Register a custom node creation method +def validated_create_node(labels, properties, **options): + """Custom node creation with validation.""" + if "name" not in properties: + raise ValueError("name is required") + + from semantica.graph_store import _get_store + store = _get_store() + return store.create_node(labels, properties, **options) + +# Register the custom method +method_registry.register("node", "validated", validated_create_node) + +# Use the custom method +from semantica.graph_store import create_node +node = create_node( + labels=["Person"], + properties={"name": "Alice"}, + method="validated" +) + +# List available methods +available = method_registry.list_all("node") +# Returns: {"node": ["validated"]} + +# Check if a method exists +exists = method_registry.has("node", "validated") + +# Get method metadata +metadata = method_registry.get_metadata("node", "validated") + +# Unregister a method +method_registry.unregister("node", "validated") +``` + ## Advanced Usage ### Context Manager @@ -470,7 +545,7 @@ with GraphStore(backend="neo4j", uri="bolt://localhost:7687") as store: ### Custom Method Registration ```python -from semantica.graph_store import method_registry +from semantica.graph_store import method_registry, GraphStore def custom_create_node(labels, properties, **options): """Custom node creation with validation.""" @@ -479,9 +554,12 @@ def custom_create_node(labels, properties, **options): raise ValueError("name is required") # Call default implementation - from semantica.graph_store import _get_store - store = _get_store() - return store.create_node(labels, properties, **options) + store = GraphStore() + store.connect() + try: + return store.create_node(labels, properties, **options) + finally: + store.close() # Register custom method method_registry.register("node", "validated", custom_create_node) diff --git a/semantica/graph_store/methods.py b/semantica/graph_store/methods.py index 26dd0d5e..03bc4e87 100644 --- a/semantica/graph_store/methods.py +++ b/semantica/graph_store/methods.py @@ -54,7 +54,7 @@ Main Functions: Example Usage: >>> from semantica.graph_store.methods import create_node, create_relationship, execute_query >>> node_id = create_node(labels=["Person"], properties={"name": "Alice"}) - >>> rel = create_relationship(start_id=node1_id, end_id=node2_id, type="KNOWS") + >>> rel = create_relationship(start_id=node1_id, end_id=node2_id, rel_type="KNOWS") >>> results = execute_query("MATCH (p:Person) RETURN p.name") """