Skill: kaiyuelv/graph-db-toolkit (version 1.0.0)

--- name: graph-db-toolkit description: Graph database toolkit for Neo4j and Cypher-based graph analytics. Use when working with knowledge graphs, relationship queries, graph traversal, Neo4j operations, social network analysis, recommendation systems, or any task requiring nodes, edges, and graph patterns. Triggers on phrases like "graph database", "Neo4j", "Cypher query", "knowledge graph", "graph traversal", "relationship analysis", "graph analytics". --- # Graph Database Toolkit Operations toolkit for graph databases with focus on Neo4j and Cypher. ## Quick Start ```python from scripts.neo4j_client import Neo4jClient client = Neo4jClient(uri="bolt://localhost:7687", user="neo4j", [REDACTED_SECRET]") client.create_node("Person", {"name": "Alice", "age": 30}) client.create_node("Person", {"name": "Bob", "age": 25}) client.create_relationship("Alice", "Person", "KNOWS", "Bob", "Person", {"since": 2020}) results = client.query(""" MATCH (a:Person)-[r:KNOWS]->(b:Person) RETURN a.name, b.name, r.since """) ``` ## Scripts - `scripts/neo4j_client.py` - Neo4j connection and CRUD operations - `scripts/cypher_builder.py` - Cypher query builder utilities - `scripts/graph_analytics.py` - Graph algorithms (centrality, paths, community) ## References - `references/cypher_cheatsheet.md` - Common Cypher patterns - `references/neo4j_patterns.md` - Neo4j design patterns

path: README.md
content: # Graph Database Toolkit

Graph database toolkit for Neo4j and Cypher-based operations.

## 功能 | Features

- Neo4j 连接与 CRUD | Neo4j connection and CRUD
- Cypher 查询构建器 | Cypher query builder
- 图算法 | Graph algorithms (centrality, shortest path, community)
- 知识图谱操作 | Knowledge graph operations
- 批量导入 | Batch import utilities

## 安装 | Installation

```bash
pip install -r requirements.txt
```

## 快速开始 | Quick Start

```python
from scripts.neo4j_client import Neo4jClient

client = Neo4jClient(uri="bolt://localhost:7687", user="neo4j", [REDACTED_SECRET]")
client.create_node("Person", {"name": "Alice"})
client.create_relationship("Alice", "Person", "KNOWS", "Bob", "Person")
results = client.query("MATCH (n:Person) RETURN n.name")
```

## 目录结构 | Structure

```
graph-db-toolkit/
├── SKILL.md
├── README.md
├── requirements.txt
├── scripts/
│   ├── neo4j_client.py
│   ├── cypher_builder.py
│   └── graph_analytics.py
├── examples/
│   └── basic_usage.py
└── tests/
    └── test_neo4j_client.py
```


path: examples/basic_usage.py
content: #!/usr/bin/env python3
"""
Basic usage examples for graph-db-toolkit.
"""
import sys
sys.path.insert(0, "../scripts")

from cypher_builder import CypherBuilder
from graph_analytics import GraphAnalytics

def demo_cypher_builder():
    qb = CypherBuilder()
    qb.match_node("a", "Person", {"name": "Alice"})
    qb.match_path("a", "KNOWS", "b")
    qb.returns("a.name", "b.name")
    print("Generated Cypher:", qb.build())
    print("Parameters:", qb.parameters)

def demo_graph_analytics():
    ga = GraphAnalytics()
    nodes = [{"id": "A"}, {"id": "B"}, {"id": "C"}]
    rels = [{"source": "A", "target": "B"}, {"source": "B", "target": "C"}, {"source": "A", "target": "C"}]
    ga.from_neo4j_results(nodes, rels)
    print("Degree centrality:", ga.degree_centrality())
    print("Shortest path A->C:", ga.shortest_path("A", "C"))

if __name__ == "__main__":
    demo_cypher_builder()
    demo_graph_analytics()


path: references/cypher_cheatsheet.md
content: # Cypher Cheatsheet

## Create

```cypher
CREATE (n:Person {name: "Alice", age: 30})
CREATE (a:Person)-[r:KNOWS {since: 2020}]->(b:Person)
```

## Match

```cypher
MATCH (n:Person) RETURN n
MATCH (a:Person)-[:KNOWS]->(b:Person) RETURN a.name, b.name
MATCH (n:Person {name: "Alice"}) RETURN n
```

## Update

```cypher
MATCH (n:Person {name: "Alice"}) SET n.age = 31
MATCH (n:Person {name: "Alice"}) SET n += {city: "NYC"}
```

## Delete

```cypher
MATCH (n:Person {name: "Alice"}) DELETE n
MATCH (n:Person) DETACH DELETE n  // delete node + all relationships
MATCH ()-[r:KNOWS]->() DELETE r
```

## Advanced

```cypher
// Shortest path
MATCH path = shortestPath((a:Person {name:"Alice"})-[*..4]-(b:Person {name:"Bob"})) RETURN path

// Count
MATCH (n:Person) RETURN count(n)

// Aggregation
MATCH (n:Person) RETURN n.city, count(n) as cnt ORDER BY cnt DESC
```


path: references/neo4j_patterns.md
content: # Neo4j Design Patterns

## Node Labels

- Use nouns for labels: `Person`, `Company`, `Product`
- Avoid overly generic labels like `Node`

## Relationship Types

- Use verbs: `WORKS_AT`, `BOUGHT`, `KNOWS`
- Direction matters: `(a)-[:KNOWS]->(b)` implies a knows b

## Indexing

```cypher
CREATE INDEX person_name FOR (n:Person) ON (n.name)
```

## Constraints

```cypher
CREATE CONSTRAINT person_id FOR (n:Person) REQUIRE n.id IS UNIQUE
```

## Idempotency

Use MERGE for upsert operations:

```cypher
MERGE (n:Person {name: "Alice"})
ON CREATE SET n.created = timestamp()
ON MATCH SET n.updated = timestamp()
```


path: requirements.txt
content: neo4j>=5.15.0
networkx>=3.2.0
numpy>=1.24.0


path: scripts/cypher_builder.py
content: """
Cypher query builder utilities.
"""
from typing import List, Dict, Any, Optional

class CypherBuilder:
    def __init__(self):
        self._match = []
        self._where = []
        self._return = []
        self._order = []
        self._limit = None
        self._parameters = {}

    def match_node(self, var: str, label: Optional[str] = None, props: Optional[Dict] = None):
        label_str = f":{label}" if label else ""
        if props:
            self._parameters.update({f"{var}_{k}": v for k, v in props.items()})
            prop_str = "{" + ", ".join([f"{k}: ${var}_{k}" for k in props]) + "}"
            self._match.append(f"({var}{label_str} {prop_str})")
        else:
            self._match.append(f"({var}{label_str})")
        return self

    def match_path(self, start: str, rel: str, end: str, rel_props: Optional[Dict] = None):
        if rel_props:
            rp = "{" + ", ".join([f"{k}: ${rel}_{k}" for k in rel_props]) + "}"
            self._parameters.update({f"{rel}_{k}": v for k, v in rel_props.items()})
            self._match.append(f"({start})-[:{rel} {rp}]->({end})")
        else:
            self._match.append(f"({start})-[:{rel}]->({end})")
        return self

    def where(self, condition: str):
        self._where.append(condition)
        return self

    def returns(self, *fields: str):
        self._return.extend(fields)
        return self

    def order_by(self, field: str, desc: bool = False):
        self._order.append(f"{field} DESC" if desc else field)
        return self

    def limit(self, n: int):
        self._limit = n
        return self

    def build(self) -> str:
        parts = []
        if self._match:
            parts.append("MATCH " + ", ".join(self._match))
        if self._where:
            parts.append("WHERE " + " AND ".join(self._where))
        if self._return:
            parts.append("RETURN " + ", ".join(self._return))
        if self._order:
            parts.append("ORDER BY " + ", ".join(self._order))
        if self._limit:
            parts.append(f"LIMIT {self._limit}")
        return " ".join(parts)

    @property
    def parameters(self) -> Dict[str, Any]:
        return self._parameters

    @staticmethod
    def shortest_path(start_label: str, start_key: str, start_val: Any,
                      end_label: str, end_key: str, end_val: Any, max_hops: int = 4) -> str:
        return f"""
            MATCH path = shortestPath(
                (a:{start_label} {{{start_key}: $start_val}})-[*..{max_hops}]-(b:{end_label} {{{end_key}: $end_val}})
            )
            RETURN path
        """


path: scripts/graph_analytics.py
content: """
Graph analytics algorithms using NetworkX fallback.
"""
from typing import Dict, List, Any, Optional
import numpy as np

try:
    import networkx as nx
except ImportError:
    nx = None

class GraphAnalytics:
    def __init__(self):
        self.G = nx.DiGraph() if nx else None

    def from_neo4j_results(self, nodes: List[Dict], relationships: List[Dict]):
        """Load from Neo4j-style results."""
        if self.G is None:
            raise ImportError("networkx not installed")
        for n in nodes:
            nid = n.get("id", n.get("name"))
            self.G.add_node(nid, **n)
        for r in relationships:
            src = r.get("source") or r.get("from")
            tgt = r.get("target") or r.get("to")
            self.G.add_edge(src, tgt, **{k: v for k, v in r.items() if k not in ("source", "target", "from", "to")})

    def degree_centrality(self) -> Dict[str, float]:
        if self.G is None:
            raise ImportError("networkx not installed")
        return nx.degree_centrality(self.G)

    def betweenness_centrality(self) -> Dict[str, float]:
        if self.G is None:
            raise ImportError("networkx not installed")
        return nx.betweenness_centrality(self.G)

    def pagerank(self, alpha: float = 0.85) -> Dict[str, float]:
        if self.G is None:
            raise ImportError("networkx not installed")
        return nx.pagerank(self.G, alpha=alpha)

    def shortest_path(self, source: str, target: str) -> List[str]:
        if self.G is None:
            raise ImportError("networkx not installed")
        try:
            return nx.shortest_path(self.G, source=source, target=target)
        except nx.NetworkXNoPath:
            return []

    def community_louvain(self) -> Dict[str, int]:
        if self.G is None:
            raise ImportError("networkx not installed")
        try:
            import community as community_louvain
            return community_louvain.best_partition(self.G.to_undirected())
        except ImportError:
            # fallback: connected components
            comps = list(nx.connected_components(self.G.to_undirected()))
            membership = {}
            for i, comp in enumerate(comps):
                for node in comp:
                    membership[node] = i
            return membership


path: scripts/neo4j_client.py
content: """
Neo4j client wrapper for graph database operations.
"""
from typing import List, Dict, Any, Optional

try:
    from neo4j import GraphDatabase
except ImportError:
    GraphDatabase = None

class Neo4jClient:
    def __init__(self, uri: str = "bolt://localhost:7687", user: str = "neo4j", password: str = "password"):
        if GraphDatabase is None:
            raise ImportError("neo4j not installed. Run: pip install neo4j")
        self.driver = GraphDatabase.driver(uri, auth=(user, password))

    def close(self):
        self.driver.close()

    def create_node(self, label: str, properties: Dict[str, Any]) -> str:
        cypher = f"CREATE (n:{label} $props) RETURN id(n) as node_id"
        with self.driver.session() as session:
            result = session.run(cypher, props=properties)
            record = result.single()
            return record["node_id"] if record else None

    def create_relationship(self, from_name: str, from_label: str, rel_type: str,
                          to_name: str, to_label: str, properties: Optional[Dict] = None,
                          match_key: str = "name") -> bool:
        props = properties or {}
        cypher = f"""
            MATCH (a:{from_label} {{{match_key}: $from_name}})
            MATCH (b:{to_label} {{{match_key}: $to_name}})
            CREATE (a)-[r:{rel_type} $props]->(b)
            RETURN r
        """
        with self.driver.session() as session:
            result = session.run(cypher, from_name=from_name, to_name=to_name, props=props)
            return result.single() is not None

    def query(self, cypher: str, parameters: Optional[Dict] = None) -> List[Dict]:
        with self.driver.session() as session:
            result = session.run(cypher, parameters or {})
            return [dict(record) for record in result]

    def delete_all(self):
        with self.driver.session() as session:
            session.run("MATCH (n) DETACH DELETE n")

    def get_node(self, label: str, key: str, value: Any) -> Optional[Dict]:
        cypher = f"MATCH (n:{label} {{{key}: $value}}) RETURN n"
        results = self.query(cypher, {"value": value})
        return results[0] if results else None

    def update_node(self, label: str, key: str, value: Any, new_props: Dict[str, Any]) -> bool:
        cypher = f"MATCH (n:{label} {{{key}: $value}}) SET n += $new_props RETURN n"
        results = self.query(cypher, {"value": value, "new_props": new_props})
        return len(results) > 0


path: tests/test_neo4j_client.py
content: """
Unit tests for graph-db-toolkit.
"""
import sys
import os
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "../scripts"))

import unittest
from cypher_builder import CypherBuilder
from graph_analytics import GraphAnalytics

class TestCypherBuilder(unittest.TestCase):
    def test_simple_match(self):
        qb = CypherBuilder()
        qb.match_node("n", "Person", {"name": "Alice"})
        qb.returns("n.name")
        self.assertIn("MATCH", qb.build())
        self.assertIn("Person", qb.build())
        self.assertEqual(qb.parameters["n_name"], "Alice")

    def test_path_match(self):
        qb = CypherBuilder()
        qb.match_path("a", "KNOWS", "b")
        cypher = qb.build()
        self.assertIn("(a)-[:KNOWS]->(b)", cypher)

    def test_shortest_path(self):
        cypher = CypherBuilder.shortest_path("Person", "name", "Alice", "Person", "name", "Bob", max_hops=3)
        self.assertIn("shortestPath", cypher)
        self.assertIn("$start_val", cypher)
        self.assertIn("$end_val", cypher)

class TestGraphAnalytics(unittest.TestCase):
    def test_centrality(self):
        ga = GraphAnalytics()
        nodes = [{"id": "A"}, {"id": "B"}, {"id": "C"}]
        rels = [{"source": "A", "target": "B"}, {"source": "B", "target": "C"}]
        ga.from_neo4j_results(nodes, rels)
        cent = ga.degree_centrality()
        self.assertIn("A", cent)
        self.assertIn("B", cent)

    def test_shortest_path(self):
        ga = GraphAnalytics()
        nodes = [{"id": "A"}, {"id": "B"}, {"id": "C"}]
        rels = [{"source": "A", "target": "B"}, {"source": "B", "target": "C"}]
        ga.from_neo4j_results(nodes, rels)
        path = ga.shortest_path("A", "C")
        self.assertEqual(path, ["A", "B", "C"])

if __name__ == "__main__":
    unittest.main()


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: clean (LOW)