A graph-based data structure designed for querying CSV files in Joern format in Python
Project description
cpg2py: Graph-Based Query Engine for Joern CSV Files
cpg2py is a Python library that provides a lightweight graph-based query engine for analyzing Code Property Graphs (CPG) extracted from Joern CSV files. The library offers an abstract base class (ABC) architecture, allowing users to extend and implement their own custom graph queries.
🚀 Features
- MultiDiGraph Representation: A directed multi-graph with support for multiple edges between nodes.
- CSV-Based Graph Construction: Reads
nodes.csvandrels.csvto construct a graph structure. - Type-Safe Generic Types: Uses Python generics for type-safe graph operations (similar to Java generics).
- Extensible Abstract Base Classes (ABC):
AbcGraphQuerierfor implementing custom graph queries with generic type support.AbcNodeQuerierfor interacting with nodes.AbcEdgeQuerierfor interacting with edges.
- Built-in Query Mechanisms:
- Retrieve all nodes and edges with type-safe iteration.
- Get incoming and outgoing edges of a node.
- Find successors and predecessors with type preservation.
- Traverse AST, Control Flow, and Data Flow Graphs.
- Concrete Implementation:
CpgGraph,CpgNode, andCpgEdgeprovide ready-to-use implementations.
📚 Installation
Using pip
To install the package, use:
pip install git+https://github.com/samhsu-dev/cpg2py.git
Using uv (Recommended)
This project uses uv for fast and reliable package management.
Install uv:
curl -LsSf https://astral.sh/uv/install.sh | sh
Clone and install:
git clone https://github.com/samhsu-dev/cpg2py.git
cd cpg2py
uv sync --dev # Install with dev dependencies
For development:
uv sync --dev
uv run pytest tests/ # Run tests
Or clone the pip repository:
pip install cpg2py
📂 File Structure
nodes.csv(Example):
id:int labels:label type flags:string_array lineno:int code childnum:int funcid:int classname namespace endlineno:int name doccomment
0 Filesystem Directory "input"
1 Filesystem File "example.php"
2 AST AST_TOPLEVEL TOPLEVEL_FILE 1 "" 25 "/input/example.php"
rels.csv(Example):
start end type
2 3 ENTRY
2 4 EXIT
6 7 ENTRY
6 9 PARENT_OF
🎯 Type Safety with Generics
cpg2py uses Python's generic types (similar to Java generics) to provide type-safe operations:
from typing import Iterable
from cpg2py import cpg_graph, CpgGraph, CpgNode, CpgEdge
# Type checker knows graph is CpgGraph[CpgNode, CpgEdge]
graph: CpgGraph = cpg_graph("nodes.csv", "rels.csv")
# Type checker knows node is CpgNode (not just AbcNodeQuerier)
node: CpgNode = graph.node("5")
# Type checker knows successors are Iterable[CpgNode]
successors: Iterable[CpgNode] = graph.succ(node)
for succ in successors:
succ.code # Type-safe: IDE knows succ is CpgNode
This ensures that:
- Return types are preserved throughout graph operations
- IDE autocomplete works correctly
- Type checkers (mypy, pyright) can verify type correctness
For more details, see Generics Documentation.
📚 Usage
1️⃣ Load Graph from Joern CSVs
from cpg2py import cpg_graph
# Load graph from CSV files
graph = cpg_graph("nodes.csv", "rels.csv")
The cpg_graph function returns a CpgGraph instance, which is the concrete implementation of the graph querier.
2️⃣ Query Nodes & Edges
from cpg2py import CpgGraph, CpgNode, CpgEdge
# Get a specific node (returns CpgNode)
node: CpgNode = graph.node("2")
print(node.name, node.type) # Example output: "/tmp/example.php" AST_TOPLEVEL
# Get a specific edge (returns CpgEdge)
edge: CpgEdge = graph.edge("2", "3", "ENTRY")
print(edge.type) # Output: ENTRY
3️⃣ Get Node Connections
# Get all outgoing edges from a node
outgoing_edges = graph.succ(node)
for out_node in outgoing_edges:
print(out_node.id, out_node.name) # out_node is CpgNode
# Get all incoming edges to a node
incoming_edges = graph.prev(node)
for in_node in incoming_edges:
print(in_node.id, in_node.name) # in_node is CpgNode
4️⃣ AST and Flow Queries
# Get top-level file node for a given node
top_file: CpgNode = graph.topfile_node("5")
print(top_file.name) # Output: "example.php"
# Get child nodes in the AST hierarchy
children = graph.children(node)
print([child.id for child in children]) # children are CpgNode instances
# Get data flow successors
flow_successors = graph.flow_to(node)
print([succ.id for succ in flow_successors]) # successors are CpgNode instances
🛠 Abstract Base Classes (ABC)
The following abstract base classes (ABC) provide interfaces for extending node, edge, and graph querying behavior. All ABCs are imported directly from the main cpg2py package.
🔹 AbcNodeQuerier (Abstract Node Interface)
This class defines how nodes interact with the graph storage.
from cpg2py import AbcNodeQuerier, Storage
class MyNodeQuerier(AbcNodeQuerier):
def __init__(self, graph: Storage, nid: str):
super().__init__(graph, nid)
@property
def name(self):
return self.get_property("name")
🔹 AbcEdgeQuerier (Abstract Edge Interface)
Defines the querying mechanisms for edges in the graph.
from cpg2py import AbcEdgeQuerier, Storage
class MyEdgeQuerier(AbcEdgeQuerier):
def __init__(self, graph: Storage, f_nid: str, t_nid: str, e_type: str):
super().__init__(graph, f_nid, t_nid, e_type)
@property
def type(self):
return self.get_property("type")
🔹 AbcGraphQuerier (Abstract Graph Interface)
This class provides an interface for implementing custom graph query mechanisms. It's a generic class that supports type-safe operations.
from cpg2py import AbcGraphQuerier, Storage
from typing import Optional
class MyGraphQuerier(AbcGraphQuerier[MyNodeQuerier, MyEdgeQuerier]):
def node(self, nid: str) -> Optional[MyNodeQuerier]:
return MyNodeQuerier(self.storage, nid)
def edge(self, fid: str, tid: str, eid: str) -> Optional[MyEdgeQuerier]:
return MyEdgeQuerier(self.storage, fid, tid, eid)
Note: AbcGraphQuerier is a generic class parameterized by node and edge types, ensuring type safety throughout graph operations. The concrete implementation CpgGraph is defined as AbcGraphQuerier[CpgNode, CpgEdge].
🔍 Querying The Graph
After implementing the abstract classes, you can perform advanced queries:
from cpg2py import Storage
storage = Storage()
graph = MyGraphQuerier(storage)
# Query node properties
node = graph.node("5")
print(node.name) # Example Output: "main"
# Query edge properties
edge = graph.edge("5", "6", "FLOWS_TO")
print(edge.type) # Output: "FLOWS_TO"
Using the Built-in CpgGraph
You can also use the built-in CpgGraph implementation directly:
from typing import Iterable
from cpg2py import cpg_graph, CpgGraph, CpgNode, CpgEdge
# Load from CSV files
graph: CpgGraph = cpg_graph("nodes.csv", "rels.csv")
# Type-safe operations
node: CpgNode = graph.node("5")
edge: CpgEdge = graph.edge("5", "6", "FLOWS_TO")
# Type-safe iteration
successors: Iterable[CpgNode] = graph.succ(node)
for succ in successors:
print(succ.code) # Type checker knows succ is CpgNode
🐝 API Reference
For more detailed API documentation, please see our APIs doc.
Main Package Exports
All public APIs are available directly from the cpg2py package:
from cpg2py import (
# Factory function
cpg_graph,
# Concrete implementations
CpgGraph,
CpgNode,
CpgEdge,
# Abstract base classes
AbcGraphQuerier,
AbcNodeQuerier,
AbcEdgeQuerier,
Storage,
# Exceptions
CPGError,
NodeNotFoundError,
EdgeNotFoundError,
TopFileNotFoundError,
)
Graph Functions
cpg_graph(node_csv: Path, edge_csv: Path, verbose: bool = False) -> CpgGraph: Loads graph from CSV files and returns aCpgGraphinstance.graph.node(nid: str) -> Optional[CpgNode]: Retrieves a node by ID (returnsCpgNode).graph.edge(fid: str, tid: str, eid: str) -> Optional[CpgEdge]: Retrieves an edge (returnsCpgEdge).graph.succ(node: CpgNode) -> Iterable[CpgNode]: Gets successor nodes.graph.prev(node: CpgNode) -> Iterable[CpgNode]: Gets predecessor nodes.graph.children(node: CpgNode) -> Iterable[CpgNode]: Gets child nodes via PARENT_OF edges.graph.parent(node: CpgNode) -> Iterable[CpgNode]: Gets parent nodes via PARENT_OF edges.graph.flow_to(node: CpgNode) -> Iterable[CpgNode]: Gets data flow successors.graph.flow_from(node: CpgNode) -> Iterable[CpgNode]: Gets data flow predecessors.graph.topfile_node(nid: str) -> CpgNode: Finds the top-level file node.
Node Properties (CpgNode)
.id: Node ID (string)..name: Node name..type: Node type..code: Source code content..label: Node label..line_num: Source code line number..flags: List of node flags..children_num: Number of children..func_id: Function ID..class_name: Class name..namespace: Namespace..end_num: End line number..comment: Documentation comment.
Edge Properties (CpgEdge)
.id: Edge ID tuple(from_node, to_node, edge_type)..start: Edge start position..end: Edge end position..type: Edge type..var: Variable name (if applicable).
🌟 License
This project is licensed under the MIT License.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file cpg2py-1.1.0.tar.gz.
File metadata
- Download URL: cpg2py-1.1.0.tar.gz
- Upload date:
- Size: 24.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.8.20
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0fcea61829285e3cfa2a6f9b00dcd65ff2e9dab8a48ce5e8755af4e341133214
|
|
| MD5 |
26a6bd3614a58c7666a53ff0c5e2ae3d
|
|
| BLAKE2b-256 |
b218af1306dc12ce22ec201bfca52d421a4588a7c95be4d0895389fe6b6d268e
|
File details
Details for the file cpg2py-1.1.0-py3-none-any.whl.
File metadata
- Download URL: cpg2py-1.1.0-py3-none-any.whl
- Upload date:
- Size: 16.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.8.20
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
246b9ef002e6c54f365a76921d2440d290949374b1c78c58a6394b8889860eab
|
|
| MD5 |
d455ee2d5cd7c99155bbeb9fc53a975a
|
|
| BLAKE2b-256 |
ecbcc1bd4517bbef780626e24efcaca220d3b364e2940f3c6e0667bb99b59fdf
|