ison-graph
ISONGraph - A token-efficient, in-memory property graph store built on ISON format.
No external database required. Designed for LLM context windows and agentic AI workflows.
Why ISONGraph?
| Challenge | ISONGraph Solution |
|---|---|
| Graph databases are heavy | Pure Python, zero dependencies beyond ison-py |
| JSON graphs waste tokens | 70% smaller than JSON-LD |
| Need multi-hop traversal | Built-in N-hop queries |
| Path finding | BFS shortest path, DFS all paths |
| LLM context limits | Token-optimized serialization |
Installation
pip install ison-graph
Quick Start
from ison_graph import ISONGraph, Direction
# Create a graph
graph = ISONGraph(name="social")
# Add nodes with properties
graph.add_node('person', 1, name='Alice', age=30)
graph.add_node('person', 2, name='Bob', age=25)
graph.add_node('person', 3, name='Charlie', age=35)
graph.add_node('company', 100, name='Acme', industry='tech')
# Add edges (relationships)
graph.add_edge('KNOWS', ('person', 1), ('person', 2), since=2020)
graph.add_edge('KNOWS', ('person', 2), ('person', 3), since=2021)
graph.add_edge('WORKS_AT', ('person', 1), ('company', 100), role='Engineer')
# Traverse: direct neighbors
friends = graph.neighbors(('person', 1), 'KNOWS')
# [('person', 2)]
# Multi-hop: friends of friends (2 hops)
fof = graph.multi_hop(('person', 1), 'KNOWS', hops=2)
# [('person', 3)]
# Path finding
path = graph.shortest_path(('person', 1), ('person', 3))
print(path) # Path(:person:1 -> :person:2 -> :person:3)
# Save to file
graph.save('social.isong')
Architecture
┌─────────────────────────────────────────────────────────────────────────┐
│ ISONGraph │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ Data Model (Property Graph) │
│ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ │
│ │ Node │ │ Edge │ │ Path │ │
│ │ - type: str │ │ - rel_type: str │ │ - nodes: [] │ │
│ │ - id: int|str │ │ - source: ref │ │ - edges: [] │ │
│ │ - properties:{} │ │ - target: ref │ │ - length │ │
│ │ - ref: (t, id) │ │ - properties:{} │ │ - start, end │ │
│ └──────────────────┘ └──────────────────┘ └──────────────────┘ │
│ │
│ In-Memory Storage (O(1) Lookup) │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ _nodes: Dict[type, Dict[id, Node]] Node lookup │ │
│ │ _edges: Dict[rel_type, List[Edge]] Edges by type │ │
│ │ _out_edges: Dict[NodeRef, List[Edge]] Outgoing index │ │
│ │ _in_edges: Dict[NodeRef, List[Edge]] Incoming index │ │
│ │ _edge_set: Set[EdgeKey] Uniqueness check │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ Operations │
│ ┌────────────────┐ ┌────────────────┐ ┌────────────────────────────┐ │
│ │ CRUD │ │ Traversal │ │ Path Finding │ │
│ │ - add_node │ │ - neighbors │ │ - shortest_path (BFS) │ │
│ │ - get_node │ │ - multi_hop │ │ - all_paths (DFS) │ │
│ │ - add_edge │ │ - multi_hop_rng│ │ - path_exists │ │
│ │ - remove_* │ │ - traverse │ │ │ │
│ └────────────────┘ └────────────────┘ └────────────────────────────┘ │
│ │
│ ┌────────────────┐ ┌────────────────┐ ┌────────────────────────────┐ │
│ │ Analysis │ │ Query APIs │ │ Persistence │ │
│ │ - is_connected │ │ - query() │ │ - to_ison() / from_ison() │ │
│ │ - has_cycle │ │ - start().hop()│ │ - to_isonl() / from_isonl()│ │
│ │ - components │ │ .filter() │ │ - save() / load() │ │
│ │ - degree │ │ .collect() │ │ │ │
│ └────────────────┘ └────────────────┘ └────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
ISON Graph Format
ISONGraph serializes to a compact, human-readable format:
nodes.person
id name age
1 Alice 30
2 Bob 25
3 Charlie 35
nodes.company
id name industry
100 Acme tech
edges.KNOWS
source target since
:person:1 :person:2 2020
:person:2 :person:3 2021
edges.WORKS_AT
source target role
:person:1 :company:100 Engineer
Key syntax:
nodes.{type}- Node block headeredges.{REL_TYPE}- Edge block header:type:id- Node reference (used in edges)- Whitespace-separated values
Core Concepts
Node References
Nodes are uniquely identified by a (type, id) tuple:
NodeRef = Tuple[str, Union[int, str]]
# Examples
('person', 1) # Person with ID 1
('company', 100) # Company with ID 100
('article', 'abc') # Article with string ID
Direction
Edges can be traversed in three directions:
from ison_graph import Direction
Direction.OUT # Follow edge from source to target (default)
Direction.IN # Follow edge from target to source
Direction.BOTH # Follow both directions
Node Operations
# Create node with properties
node = graph.add_node('person', 1,
name='Alice',
age=30,
email='alice@example.com'
)
# Get node
node = graph.get_node('person', 1)
print(node.type) # 'person'
print(node.id) # 1
print(node.properties) # {'name': 'Alice', 'age': 30, ...}
print(node.ref) # ('person', 1)
# Update properties (merges with existing)
graph.update_node('person', 1, age=31, title='Senior Engineer')
# Check existence
if graph.has_node('person', 1):
...
# Remove node (also removes all connected edges)
graph.remove_node('person', 1)
# Iterate all nodes
for node in graph.nodes():
print(node)
# Iterate nodes of specific type
for node in graph.nodes('person'):
print(f"{node.properties['name']}: {node.properties.get('age')}")
# Count nodes
total = graph.node_count() # All nodes
people = graph.node_count('person') # Just people
# Get all node types
types = graph.node_types() # ['person', 'company', ...]
Edge Operations
# Create edge with properties
edge = graph.add_edge('KNOWS',
('person', 1), # source
('person', 2), # target
since=2020,
strength=0.9
)
# Get edge
edge = graph.get_edge('KNOWS', ('person', 1), ('person', 2))
print(edge.rel_type) # 'KNOWS'
print(edge.source) # ('person', 1)
print(edge.target) # ('person', 2)
print(edge.properties) # {'since': 2020, 'strength': 0.9}
# Check existence
if graph.has_edge('KNOWS', ('person', 1), ('person', 2)):
...
# Remove edge
graph.remove_edge('KNOWS', ('person', 1), ('person', 2))
# Iterate edges
for edge in graph.edges(): # All edges
print(edge)
for edge in graph.edges('KNOWS'): # By relationship type
print(f"{edge.source} knows {edge.target}")
for edge in graph.edges(source=('person', 1)): # By source
print(f"From person 1: {edge}")
# Count edges
total = graph.edge_count()
knows_count = graph.edge_count('KNOWS')
# Get all relationship types
rel_types = graph.edge_types() # ['KNOWS', 'WORKS_AT', ...]
Traversal Operations
Neighbors (1-hop)
# Outgoing neighbors (default)
friends = graph.neighbors(('person', 1), 'KNOWS')
# [('person', 2)]
# Incoming neighbors
followers = graph.neighbors(('person', 1), 'KNOWS', Direction.IN)
# Both directions
connections = graph.neighbors(('person', 1), 'KNOWS', Direction.BOTH)
# Any relationship type
all_neighbors = graph.neighbors(('person', 1))
Multi-Hop Traversal
# Exactly N hops away
two_hops = graph.multi_hop(('person', 1), 'KNOWS', hops=2)
three_hops = graph.multi_hop(('person', 1), 'KNOWS', hops=3)
# Range of hops (1 to 3 inclusive)
reachable = graph.multi_hop_range(('person', 1), 'KNOWS',
min_hops=1,
max_hops=3
)
# Any relationship type
all_reachable = graph.multi_hop(('person', 1), hops=2)
# Traverse incoming edges
predecessors = graph.multi_hop(('person', 5), 'KNOWS', hops=2,
direction=Direction.IN
)
Pattern Traversal
Follow a sequence of relationship types:
# "Find companies where Alice's friends work"
# Pattern: person:1 -[:KNOWS]-> person -[:WORKS_AT]-> company
companies = graph.traverse(
('person', 1),
[('KNOWS', Direction.OUT), ('WORKS_AT', Direction.OUT)]
)
# With filter function
# "Find friends over 25 who work at tech companies"
tech_companies = graph.traverse(
('person', 1),
[('KNOWS', Direction.OUT), ('WORKS_AT', Direction.OUT)],
filter_fn=lambda node: node.properties.get('industry') == 'tech'
)
Path Finding
Shortest Path (BFS)
path = graph.shortest_path(('person', 1), ('person', 5))
if path:
print(f"Length: {path.length}") # Number of hops
print(f"Start: {path.start}") # First node
print(f"End: {path.end}") # Last node
print(f"Nodes: {path.nodes}") # All nodes in path
print(f"Edges: {path.edges}") # All edges in path
print(path) # Path(:person:1 -> :person:2 -> ... -> :person:5)
# With constraints
path = graph.shortest_path(
('person', 1),
('person', 5),
rel_type='KNOWS', # Only follow KNOWS edges
max_hops=10, # Maximum path length
direction=Direction.OUT
)
All Paths (DFS)
# Find all possible paths
paths = graph.all_paths(('person', 1), ('person', 5))
for path in paths:
print(f"Path of length {path.length}: {path}")
# With constraints
paths = graph.all_paths(
('person', 1),
('person', 5),
rel_type='KNOWS',
max_hops=5
)
Path Existence
if graph.path_exists(('person', 1), ('person', 5)):
print("Path exists!")
Fluent API
Chain traversal operations for readable queries:
# Find friends of friends who are over 30
result = graph.start(('person', 1)) \
.hop('KNOWS') \
.hop('KNOWS') \
.filter(lambda n: n.properties.get('age', 0) > 30) \
.collect()
# Multiple hops at once
distant = graph.start(('person', 1)) \
.hops(3, 'KNOWS') \
.collect()
# Get Node objects instead of references
nodes = graph.start(('person', 1)) \
.hop('KNOWS') \
.collect_nodes()
for node in nodes:
print(node.properties['name'])
# Count results
count = graph.start(('person', 1)) \
.hop('KNOWS') \
.count()
# Get first result
first = graph.start(('person', 1)) \
.hop('KNOWS') \
.first()
# Chain with direction control
result = graph.start(('person', 5)) \
.hop('KNOWS', direction=Direction.IN) \
.hop('WORKS_AT') \
.collect()
Query Pattern Syntax
Execute queries using a pattern string:
# Direct neighbors
graph.query(":person:1 -[:KNOWS]-> *")
# Returns: [('person', 2), ('person', 3)]
# Exactly 2 hops
graph.query(":person:1 -[:KNOWS*2]-> *")
# Returns: nodes exactly 2 hops away
# Range: 1 to 3 hops
graph.query(":person:1 -[:KNOWS*1..3]-> *")
# Returns: all nodes within 1-3 hops
Pattern syntax:
:type:id- Starting node reference-[:REL]->- Relationship type-[:REL*N]->- Exactly N hops-[:REL*M..N]->- M to N hops (range)*- Match any node
Graph Analysis
Connectivity
# Check if all nodes are reachable from any node
if graph.is_connected():
print("Graph is connected!")
# Get connected components
components = graph.connected_components()
print(f"Found {len(components)} components")
for i, component in enumerate(components):
print(f"Component {i}: {len(component)} nodes")
Cycle Detection
# Check for any cycles
if graph.has_cycle():
print("Graph contains cycles (not a DAG)")
# Check for cycles via specific relationship
if graph.has_cycle('REPORTS_TO'):
print("Org chart has cycles! (invalid)")
Node Degree
# Incoming edges
in_deg = graph.in_degree(('person', 1))
# Outgoing edges
out_deg = graph.out_degree(('person', 1))
# Total degree
total_deg = graph.degree(('person', 1))
Persistence
Save and Load
# Save to ISON format (default)
graph.save('graph.isong')
# Load from file
graph = ISONGraph.load('graph.isong')
# Save to ISONL streaming format
graph.save('graph.isonl')
graph = ISONGraph.load('graph.isonl')
# Auto-detect format from extension
graph.save('data.isong') # Uses ISON
graph.save('data.isonl') # Uses ISONL
Manual Serialization
# To string
ison_str = graph.to_ison()
isonl_str = graph.to_isonl()
# From string
graph = ISONGraph.from_ison(ison_str)
graph = ISONGraph.from_isonl(isonl_str)
graph = ISONGraph.parse(ison_str) # Alias for from_ison
Directed vs Undirected Graphs
# Directed graph (default)
directed = ISONGraph(name="social", directed=True)
# Undirected graph - edges work both ways
undirected = ISONGraph(name="network", directed=False)
# In undirected graphs, adding A->B also adds B->A
undirected.add_edge('CONNECTED', ('node', 1), ('node', 2))
# Now both directions exist
Error Handling
from ison_graph import (
GraphError,
NodeNotFoundError,
EdgeNotFoundError,
DuplicateNodeError,
DuplicateEdgeError
)
# Handle node not found
try:
node = graph.get_node('person', 999)
except NodeNotFoundError as e:
print(f"Node not found: {e.node_ref}")
# Handle duplicate node
try:
graph.add_node('person', 1, name='Alice')
graph.add_node('person', 1, name='Duplicate') # Raises
except DuplicateNodeError as e:
print(f"Node already exists: {e.node_ref}")
# Handle edge errors
try:
graph.add_edge('KNOWS', ('person', 1), ('person', 999)) # Target missing
except NodeNotFoundError:
print("Cannot create edge: target node missing")
Complete API Reference
ISONGraph Class
Constructor
| Parameter | Type | Default | Description |
|---|---|---|---|
name |
str | "graph" | Graph name (used in serialization) |
directed |
bool | True | Whether edges are directed |
Node Methods
| Method | Returns | Description |
|---|---|---|
add_node(type, id, **props) |
Node | Add node with properties |
get_node(type, id) |
Node | Get node (raises if not found) |
get_node_by_ref(ref) |
Node | Get node by (type, id) tuple |
has_node(type, id) |
bool | Check if node exists |
remove_node(type, id) |
None | Remove node and its edges |
update_node(type, id, **props) |
Node | Update node properties |
nodes(type=None) |
Iterator | Iterate nodes |
node_count(type=None) |
int | Count nodes |
node_types() |
List[str] | Get all node types |
Edge Methods
| Method | Returns | Description |
|---|---|---|
add_edge(rel, src, tgt, **props) |
Edge | Add edge with properties |
get_edge(rel, src, tgt) |
Edge | Get edge (raises if not found) |
has_edge(rel, src, tgt) |
bool | Check if edge exists |
remove_edge(rel, src, tgt) |
None | Remove edge |
edges(rel=None, src=None, tgt=None) |
Iterator | Iterate edges |
edge_count(rel=None) |
int | Count edges |
edge_types() |
List[str] | Get all relationship types |
Traversal Methods
| Method | Returns | Description |
|---|---|---|
neighbors(ref, rel, dir) |
List[NodeRef] | Get 1-hop neighbors |
multi_hop(ref, rel, hops, dir) |
List[NodeRef] | Get N-hop neighbors |
multi_hop_range(ref, rel, min, max, dir) |
List[NodeRef] | Get range of hops |
traverse(ref, pattern, filter) |
List[NodeRef] | Follow pattern |
Path Finding Methods
| Method | Returns | Description |
|---|---|---|
shortest_path(start, end, rel, max, dir) |
Path | BFS shortest path |
all_paths(start, end, rel, max, dir) |
List[Path] | DFS all paths |
path_exists(start, end, rel, max) |
bool | Check if path exists |
Analysis Methods
| Method | Returns | Description |
|---|---|---|
in_degree(ref) |
int | Count incoming edges |
out_degree(ref) |
int | Count outgoing edges |
degree(ref) |
int | Total edge count |
is_connected() |
bool | Check if graph is connected |
has_cycle(rel=None) |
bool | Check for cycles |
connected_components() |
List[Set] | Get connected components |
Persistence Methods
| Method | Returns | Description |
|---|---|---|
save(path, format='auto') |
None | Save to file |
load(path, format='auto') |
ISONGraph | Load from file (classmethod) |
to_ison() |
str | Serialize to ISON |
to_isonl() |
str | Serialize to ISONL |
from_ison(text, name) |
ISONGraph | Parse ISON (classmethod) |
from_isonl(text, name) |
ISONGraph | Parse ISONL (classmethod) |
parse(text, name) |
ISONGraph | Alias for from_ison |
Query Methods
| Method | Returns | Description |
|---|---|---|
start(ref) |
GraphTraversal | Start fluent traversal |
query(pattern) |
List[NodeRef] | Execute pattern query |
GraphTraversal Class
| Method | Returns | Description |
|---|---|---|
hop(rel, dir, where) |
self | Traverse one hop |
hops(n, rel, dir) |
self | Traverse N hops |
filter(fn) |
self | Filter current nodes |
collect() |
List[NodeRef] | Get current node refs |
collect_nodes() |
List[Node] | Get current Node objects |
count() |
int | Count current nodes |
first() |
NodeRef | Get first node or None |
Data Classes
Node
| Property | Type | Description |
|---|---|---|
type |
str | Node type |
id |
int|str | Node ID |
properties |
Dict | Node properties |
ref |
NodeRef | (type, id) tuple |
Edge
| Property | Type | Description |
|---|---|---|
rel_type |
str | Relationship type |
source |
NodeRef | Source node |
target |
NodeRef | Target node |
properties |
Dict | Edge properties |
key |
EdgeKey | (rel_type, source, target) |
Path
| Property | Type | Description |
|---|---|---|
nodes |
List[NodeRef] | Nodes in path |
edges |
List[Edge] | Edges in path |
length |
int | Number of hops |
start |
NodeRef | First node |
end |
NodeRef | Last node |
Performance Characteristics
| Operation | Time Complexity | Notes |
|---|---|---|
add_node |
O(1) | Hash table insert |
get_node |
O(1) | Hash table lookup |
add_edge |
O(1) | With index update |
has_edge |
O(1) | Set lookup |
neighbors |
O(degree) | Iterate edge list |
multi_hop(N) |
O(V + E) per hop | BFS traversal |
shortest_path |
O(V + E) | BFS |
all_paths |
O(V!) worst | DFS with backtracking |
is_connected |
O(V + E) | BFS from any node |
has_cycle |
O(V + E) | DFS with recursion stack |
Token Efficiency
Benchmark Results (50 Graph Questions)
| Format | Tokens | Accuracy | Acc/1K Tokens |
|---|---|---|---|
| ISONGraph | 639 | 92.0% | 143.97 |
| ISON | 685 | 88.0% | 128.47 |
| TOON | 856 | 80.0% | 93.46 |
| JSON Compact | 1,072 | 82.0% | 76.49 |
| JSON | 2,039 | 84.0% | 41.20 |
ISONGraph provides 3.5x more value per token than JSON.
Format Comparison
ISONGraph (34 tokens) JSON-LD (120+ tokens)
───────────────────── ─────────────────────
nodes.person {"@context": {...},
id name age "nodes": [
1 Alice 30 {"@type": "Person",
2 Bob 25 "@id": "person/1",
"name": "Alice",
edges.KNOWS "age": 30}, ...
source target ],
:person:1 :person:2 "edges": [...]}
ISONQL Query Language
ISONGraph includes ISONQL, a query language for property graphs:
from ison_graph import ISONGraph
from ison_graph.query import QueryEngine, QueryBuilder
graph = ISONGraph("social")
# ... add nodes and edges ...
engine = QueryEngine(graph)
# Query nodes with conditions
results = engine.execute("NODES person WHERE age > 25")
results = engine.execute("NODES person WHERE name = 'Alice'")
results = engine.execute("NODES company WHERE name STARTS_WITH 'Acme'")
# Query edges
results = engine.execute("EDGES KNOWS WHERE since > 2020")
# Traverse relationships
results = engine.execute("TRAVERSE person:1 -> KNOWS -> person")
# Multi-hop traversal (up to 2 hops)
results = engine.execute("TRAVERSE person:1 -> KNOWS -> person MAX 2")
# Deeper traversal (up to 3 hops)
results = engine.execute("TRAVERSE person:1 -> KNOWS -> person MAX 3")
# Find paths
results = engine.execute("PATH person:1 TO person:5 VIA KNOWS MAX 5")
# Aggregations
results = engine.execute("COUNT person WHERE age > 25")
results = engine.execute("AVG person.age")
results = engine.execute("SUM person.age WHERE city = 'NYC'")
results = engine.execute("MIN person.age")
results = engine.execute("MAX person.age")
Fluent Query Builder
# Node queries: start from engine.match()
result = (engine.match("person")
.where("age", ">", 25)
.where("city", "=", "NYC")
.order_by("name", "DESC")
.limit(10)
.return_fields("name", "email")
.execute())
# Edge queries: start from engine.match_edges()
result = (engine.match_edges("KNOWS")
.where("since", ">=", 2020)
.execute())
# Traversals and paths use the string query form
result = engine.execute("TRAVERSE person:1 -> KNOWS -> person MAX 2")
ISONQL Operators
| Operator | Description | Example |
|---|---|---|
= |
Equals | WHERE name = 'Alice' |
!= |
Not equals | WHERE status != 'inactive' |
> |
Greater than | WHERE age > 25 |
>= |
Greater or equal | WHERE age >= 18 |
< |
Less than | WHERE price < 100 |
<= |
Less or equal | WHERE count <= 10 |
IN |
In list | WHERE city IN ('NYC', 'LA') |
NOT IN |
Not in list | WHERE city NOT IN ('NYC') |
CONTAINS |
List contains | WHERE tags CONTAINS 'tech' |
STARTS_WITH |
Prefix match | WHERE name STARTS_WITH 'A' |
ENDS_WITH |
Suffix match | WHERE email ENDS_WITH '.com' |
MATCHES |
Regex match | WHERE email MATCHES '^[a-z]+' |
EXISTS |
Field present | WHERE email EXISTS |
Schema Validation
Define and validate graph schemas:
from ison_graph.schema import (
GraphSchema, NodeType, EdgeType,
String, Int, Float, Bool, Ref,
Cardinality
)
# Define node types
Person = NodeType("person") \
.id(Int()) \
.field("name", String().required().max(100)) \
.field("age", Int().min(0).max(150)) \
.field("email", String().email())
Company = NodeType("company") \
.id(Int()) \
.field("name", String().required()) \
.field("founded", Int().min(1800))
# Define edge types
Knows = EdgeType("KNOWS") \
.from_node(Person) \
.to_node(Person) \
.field("since", Int()) \
.no_self_loop() \
.unique()
WorksAt = EdgeType("WORKS_AT") \
.from_node(Person) \
.to_node(Company) \
.field("role", String()) \
.cardinality(Cardinality.MANY_TO_ONE)
# Create schema
schema = GraphSchema("social") \
.node_types(Person, Company) \
.edge_types(Knows, WorksAt) \
.no_orphans()
# Validate graph
result = schema.validate(graph)
if not result.valid:
for error in result.errors:
print(f"[{error.location}] {error.code}: {error.message}")
Field Validators
# String fields
String().required() # Required field
String().min(5).max(100) # Length constraints
String().pattern(r"^[A-Z]") # Regex pattern
String().email() # Email validation
String().enum("active", "inactive") # Enum values
# Numeric fields
Int().required().min(0).max(150)
Float().range(0.0, 100.0)
# Boolean fields
Bool().required()
# Reference fields
Ref("person") # Reference to person node
Cardinality Constraints
from ison_graph.schema import Cardinality
# One person can work at one company (many employees per company)
EdgeType("WORKS_AT").cardinality(Cardinality.MANY_TO_ONE)
# One company has one CEO
EdgeType("CEO_OF").cardinality(Cardinality.ONE_TO_ONE)
# One manager can manage many employees
EdgeType("MANAGES").cardinality(Cardinality.ONE_TO_MANY)
# Many people can know many people
EdgeType("KNOWS").cardinality(Cardinality.MANY_TO_MANY)
Graph-Level Constraints
schema = GraphSchema("social") \
.node_types(Person, Company) \
.edge_types(Knows, WorksAt) \
.connected() # Require graph to be connected
.no_orphans() # All nodes must have at least one edge
Integration with ISON Ecosystem
┌────────────────────────────────────────────────────────────────────┐
│ ISON Ecosystem │
├────────────────────────────────────────────────────────────────────┤
│ │
│ ison-py → Core parser (loads, dumps) │
│ ↓ │
│ ison-graph → Property graph store (nodes, edges, paths) │
│ ↓ │
│ isongraphantic → Graph schema validation (constraints) │
│ ↓ │
│ ison-graph-embeddings → Semantic search + graph traversal │
│ │
└────────────────────────────────────────────────────────────────────┘
Related Packages
| Package | Purpose |
|---|---|
ison-py |
Core ISON parser |
isonantic |
Data validation for ISON |
isongraphantic |
Graph schema validation |
ison-graph-embeddings |
Semantic search + embeddings |
Use Cases
Knowledge Graphs
graph = ISONGraph(name="knowledge")
graph.add_node('company', 'apple', name='Apple', industry='tech')
graph.add_node('person', 'tim', name='Tim Cook', role='CEO')
graph.add_node('product', 'iphone', name='iPhone')
graph.add_edge('WORKS_AT', ('person', 'tim'), ('company', 'apple'))
graph.add_edge('PRODUCES', ('company', 'apple'), ('product', 'iphone'))
# Query: What products does Tim Cook's company make?
products = graph.traverse(
('person', 'tim'),
[('WORKS_AT', Direction.OUT), ('PRODUCES', Direction.OUT)]
)
Social Networks
graph = ISONGraph(name="social")
# Add users and follow relationships
# Multi-hop: friends of friends
fof = graph.multi_hop(('user', 1), 'FOLLOWS', hops=2)
Org Charts
graph = ISONGraph(name="org")
# REPORTS_TO edges form a DAG
if graph.has_cycle('REPORTS_TO'):
raise ValueError("Invalid org structure!")
Dependency Graphs
graph = ISONGraph(name="deps")
# Package dependency resolution
path = graph.shortest_path(('pkg', 'app'), ('pkg', 'lodash'))
Testing
# Run all tests
pytest tests/ -v
# Run with coverage
pytest tests/ --cov=ison_graph --cov-report=term-missing
License
MIT License - see LICENSE for details.
Author
Mahesh Vaikri
- Website: www.ison.dev
- Documentation: www.getison.com
- GitHub: @maheshvaikri-code
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 ison_graph-1.0.0.tar.gz.
File metadata
- Download URL: ison_graph-1.0.0.tar.gz
- Upload date:
- Size: 62.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.12.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b405147ffbd6f8627995ff4c9c69e1cf5f04a39ad72deaaf3a630920c1d4d6da
|
|
| MD5 |
a4dba9d08f2df2cc79a36be06a1b3854
|
|
| BLAKE2b-256 |
239a8155178478b800948df2073701e25d354ebc40560d8b009b5e093d329fa9
|
File details
Details for the file ison_graph-1.0.0-py3-none-any.whl.
File metadata
- Download URL: ison_graph-1.0.0-py3-none-any.whl
- Upload date:
- Size: 35.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.12.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
db8118de5e87b761574168a2a8c799893d3fa40f883f9c3d1fbb6ab2b9236c60
|
|
| MD5 |
2648012d60296d78b9c95163e6af07ec
|
|
| BLAKE2b-256 |
9c70eb06194cbf97caaa10b54b04adedff26d2c3e2768e4a45631ee715483869
|