age-orm
A Python ORM for Apache AGE, providing SQLAlchemy-like abstractions for graph database operations.
Status: v0.1.0 — Core implemented (models, CRUD, query builder, relationships, async)
Features
Core
- Pydantic v2-based model definitions for vertices and edges
- Graph, Vertex, and Edge abstractions
- Automatic schema creation (labels, indexes)
- CRUD operations (create, read, update, delete)
- Connection pooling via psycopg3
Query Building
- Fluent Cypher query builder (filter, sort, limit, skip)
- Safe parameter substitution
- Raw Cypher support
- Bulk mutations (update/delete via query)
Advanced
- Sync + Async support (psycopg3's unified API)
- Relationship descriptors with lazy loading
- Event system (pre/post hooks for add, update, delete)
- Bulk import (direct SQL INSERT for performance)
- Graph traversal helpers (expand, traverse)
- Dirty tracking (only update changed fields)
- Migrations and schema versioning
- Integration with AGEFreighter for bulk loads
Installation
# With uv
uv add age-orm
# With pip
pip install age-orm
Requires Python 3.12+ and a running Apache AGE instance.
Quick Start
from age_orm import Database, Vertex, Edge, relationship
# Define models
class Person(Vertex):
__label__ = "Person"
name: str
age: int
email: str | None = None
class Knows(Edge):
__label__ = "KNOWS"
since: int
relationship_type: str = "friend"
# Connect
db = Database("postgresql://ageuser:agepassword@localhost:5433/agedb")
graph = db.graph("social", create=True)
# Create vertices
alice = Person(name="Alice", age=30)
bob = Person(name="Bob", age=25)
graph.add(alice)
graph.add(bob)
# Create edge
knows = Knows(since=2020)
graph.connect(alice, knows, bob)
# Query
people = graph.query(Person).filter("n.age > $min_age", min_age=20).sort("n.name").all()
alice = graph.query(Person).filter_by(name="Alice").one()
# Traverse
friends = graph.traverse(alice, "KNOWS", depth=2, target_class=Person)
# Raw Cypher
results = graph.cypher(
"MATCH (n:Person)-[:KNOWS]->(m) RETURN n.name, m.name",
columns=["a", "b"]
)
# Cleanup
db.close()
Async Usage
from age_orm import AsyncDatabase
async with AsyncDatabase("postgresql://...") as db:
graph = await db.graph("social", create=True)
alice = Person(name="Alice", age=30)
await graph.add(alice)
q = await graph.query(Person)
people = await q.filter("n.age > $min", min=20).all()
Relationships
class Person(Vertex):
__label__ = "Person"
name: str
friends: list["Person"] = relationship("Person", "KNOWS", direction="outbound")
employer: "Company" = relationship("Company", "WORKS_AT", uselist=False)
Relationships are lazy-loaded on access when the entity is bound to a graph.
Event Hooks
from age_orm import listen, listens_for
@listens_for(Person, "pre_add")
def validate_person(target, event, **kwargs):
if target.age < 0:
raise ValueError("Age cannot be negative")
Dependencies
psycopg[binary,pool] >= 3.2— PostgreSQL driver with connection poolingpydantic >= 2.3— Data validation and models
Project Structure
age_orm/
├── __init__.py # Public API exports
├── exceptions.py # Custom exceptions
├── event.py # Event system (pre/post hooks)
├── database.py # Connection + pool management
├── graph.py # Graph class + CRUD + traversal
├── references.py # Relationship descriptors
├── models/
│ ├── base.py # AgeModel base class
│ ├── vertex.py # Vertex model
│ └── edge.py # Edge model
├── query/
│ └── builder.py # Cypher query builder
└── utils/
└── serialization.py # Agtype serialization helpers
License
MIT
Metadata
Release files for age-orm 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| age_orm-0.2.0.tar.gz | 28.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| age_orm-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 52.4 kB
Release files / age_orm-0.2.0.tar.gz
| Download URL | age_orm-0.2.0.tar.gz |
|---|---|
| Size | 28.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
1e9c86b192f5e440d37a513c666bc734463366e39884240a6628d4cf4c5f7809
|
|
BLAKE2b-256 checksum How to use checksums |
9e4a2dc27e68a684c0ab2917862cb29ffe054ae89128c088712e19f6000ce5ca
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.9.25 {"installer":{"name":"uv","version":"0.9.25","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / age_orm-0.2.0-py3-none-any.whl
| Download URL | age_orm-0.2.0-py3-none-any.whl |
|---|---|
| Size | 23.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
3a976e5482cdcb11f212bc083ba421af8a5097c1dadd0180123192623977ec00
|
|
BLAKE2b-256 checksum How to use checksums |
fa2909d0620f7319c3fff18e06e50c63a28b561a1fbcb8e429d5d20587d7ac6c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.9.25 {"installer":{"name":"uv","version":"0.9.25","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|