Object-Graph Mapping for FalkorDB with Spring Data-inspired patterns
FalkorDB Python ORM provides intuitive, annotation-based object-graph mapping for FalkorDB, enabling developers to work with graph databases using familiar ORM patterns inspired by Spring Data.
Production Ready! β The FalkorDB Python ORM is fully implemented, tested, and documented.
- π·οΈ Decorator-based Entity Mapping: Use
@nodeandpropertydecorators for intuitive object-graph mapping - π¦ Repository Pattern: Complete CRUD operations with type-safe
Repository[T] - π ID Management: Auto-generated or manual IDs with
generated_id() - π Type Conversion: Built-in converters for common Python types
- π― Multiple Labels: Support for multiple node labels per entity
- π¨ Type Safety: Full type hints and generic repositories for IDE support
- π Derived Query Methods: Auto-generate queries from method names (e.g.,
find_by_name(),count_by_age_greater_than()) - π Comparison Operators: 14+ operators (equals, greater_than, less_than, between, in, etc.)
- π Logical Operators: AND/OR combinations in queries
- π String Operations: CONTAINS, STARTS WITH, ENDS WITH, regex patterns
- π Sorting & Limiting: ORDER BY multiple fields, first/top_N result limiting
- β‘ Query Caching: Automatic QuerySpec caching for performance
- π Custom Cypher Queries:
@querydecorator with parameter binding - π Aggregation Methods: Built-in
sum(),avg(),min(),max()functions
- π Relationship Declaration:
relationship()decorator with full type support - π€ Lazy Loading: Relationships loaded on-demand with automatic caching
- β‘ Eager Loading: Solve N+1 queries with
fetch=['rel1', 'rel2']parameter - π Cascade Operations: Auto-save related entities with
cascade=True βοΈ Bidirectional Relationships: Full support for complex relationship graphs- π Reverse Relationships:
direction='INCOMING'for inverse traversal - π Circular Reference Handling: Safe handling of circular relationships
- β‘ AsyncRepository: Full async/await support for all CRUD operations
- π Async Relationships: Async lazy loading with
AsyncLazyListandAsyncLazySingle - π Async Derived Queries: Auto-generated async query methods
- π Framework Ready: Perfect for FastAPI, aiohttp, and async Python applications
- β‘ Transaction Support: Context managers with identity map and change tracking
- ποΈ Index Management:
@indexedand@uniquedecorators with schema validation - π Pagination: Full pagination with sorting and navigation (
Pageable,Page[T]) - π Relationship Updates: Automatic deletion of old edges when relationships change
- π Role-Based Access Control (RBAC): Enterprise-grade security with fine-grained permissions
- π₯ User & Role Management: Built-in user, role, and privilege entities
- π‘οΈ Declarative Security:
@secureddecorator for entity-level access control - π Property-Level Security: Control access to individual properties
- π SecureSession: Security-aware sessions with automatic permission enforcement
- π¨βπΌ Admin API: Comprehensive RBACManager for runtime administration
- π Audit Logging: Complete audit trail for all security operations
- π Impersonation: Test permissions safely with context managers
- β‘ Performance: <10ms overhead with intelligent privilege caching
- π Comprehensive Documentation: Complete API reference and migration guides
- π§ Enhanced Exceptions: Contextual error messages with structured error information
- π CI/CD Workflows: Automated testing, linting, and publishing
- πΎ Memory Optimization: Interned strings for repeated values with
@interneddecorator - π§ͺ Integration Tests: Full end-to-end tests with real FalkorDB
- π¦ Migration System: Schema version management and migrations
- π Query Result Caching: Result caching for performance
- βοΈ Batch Optimization: UNWIND-based bulk operations
from falkordb_orm import node, property, relationship, Repository
from typing import Optional, List
@node(labels=["Person", "Individual"])
class Person:
id: Optional[int] = None
name: str = property("full_name") # Maps to 'full_name' in graph
email: str
age: int
friends: List["Person"] = relationship(type="KNOWS", direction="OUTGOING")
company: Optional["Company"] = relationship(type="WORKS_FOR", direction="OUTGOING")
@node("Company")
class Company:
id: Optional[int] = None
name: str
employees: List[Person] = relationship(type="WORKS_FOR", direction="INCOMING")from falkordb import FalkorDB
# Connect to FalkorDB
db = FalkorDB(host='localhost', port=6379)
graph = db.select_graph('social')
# Create repository
repo = Repository(graph, Person)
# Create and save
person = Person(name="Alice", email="alice@example.com", age=25)
saved = repo.save(person)
# Derived queries (auto-implemented)
adults = repo.find_by_age_greater_than(18)
alice = repo.find_by_name("Alice")
count = repo.count_by_age(25)
exists = repo.exists_by_email("alice@example.com")
# Eager loading relationships (prevents N+1 queries)
person_with_friends = repo.find_by_id(1, fetch=["friends", "company"])
all_with_friends = repo.find_all(fetch=["friends"]) # Single query!
# Cascade save (auto-saves related entities)
company = Company(name="Acme Corp")
employee = Employee(name="Bob", company=company)
repo.save(employee) # Company automatically saved!import asyncio
from falkordb.asyncio import FalkorDB
from falkordb_orm import node, AsyncRepository
from typing import Optional
@node("Person")
class Person:
id: Optional[int] = None
name: str
age: int
async def main():
# Connect to FalkorDB with async client
from redis.asyncio import BlockingConnectionPool
pool = BlockingConnectionPool(max_connections=16, timeout=None, decode_responses=True)
db = FalkorDB(connection_pool=pool)
graph = db.select_graph('social')
# Create async repository
repo = AsyncRepository(graph, Person)
# All operations are async
person = Person(name="Alice", age=25)
saved = await repo.save(person)
# Async derived queries
adults = await repo.find_by_age_greater_than(18)
count = await repo.count()
# Async eager loading
person_with_friends = await repo.find_by_id(1, fetch=["friends"])
print(f"Found {count} people")
asyncio.run(main())from falkordb_orm import Session
# Use session for transactions with identity map
with Session(graph) as session:
# Get entity (cached in identity map)
person = session.get(Person, 1)
person.age = 31
session._dirty.add(person) # Mark as modified
# Add new entities
new_person = Person(name="Bob", age=25)
session.add(new_person)
# Auto-commit on success, auto-rollback on error
session.commit()from falkordb_orm import node, indexed, unique, IndexManager
@node("User")
class User:
email: str = unique(required=True) # Unique constraint
age: int = indexed() # Regular index
bio: str = indexed(index_type="FULLTEXT") # Full-text search
# Create indexes
manager = IndexManager(graph)
manager.create_indexes(User, if_not_exists=True)
# Schema validation
from falkordb_orm import SchemaManager
schema_manager = SchemaManager(graph)
result = schema_manager.validate_schema([User, Product])
if not result.is_valid:
schema_manager.sync_schema([User, Product])from falkordb_orm import Pageable
# Create pageable (page 0, 10 items, sorted by age)
pageable = Pageable(page=0, size=10, sort_by="age", direction="ASC")
# Get paginated results
page = repo.find_all_paginated(pageable)
print(f"Page {page.page_number + 1} of {page.total_pages}")
print(f"Total: {page.total_elements} items")
for person in page.content:
print(person.name)
# Navigate pages
if page.has_next():
next_page = repo.find_all_paginated(pageable.next())For a complete walkthrough, see QUICKSTART.md.
Define a secured entity:
from falkordb_orm import node, generated_id
from falkordb_orm.security import secured
@node("Person")
@secured(
read=["reader", "admin"],
write=["editor", "admin"],
deny_read_properties={
"ssn": ["*"], # Nobody can read
"salary": ["reader"] # Readers cannot read salary
}
)
class Person:
id: int | None = generated_id()
name: str
email: str
ssn: str
salary: floatCreate roles, users, and grant privileges:
from datetime import datetime
from falkordb_orm.repository import Repository
from falkordb_orm.security import Role, User, SecurityPolicy
role_repo = Repository(graph, Role)
user_repo = Repository(graph, User)
reader = Role(name="reader", description="Read-only", created_at=datetime.now())
editor = Role(name="editor", description="Edit", created_at=datetime.now())
role_repo.save(reader)
role_repo.save(editor)
alice = User(username="alice", email="alice@example.com", created_at=datetime.now())
alice.roles = [reader]
user_repo.save(alice)
policy = SecurityPolicy(graph)
policy.grant("READ", "Person", to="reader")
policy.grant("WRITE", "Person", to="editor")
policy.deny("READ", "Person.ssn", to="reader")Use SecureSession:
from falkordb_orm.security import SecureSession
session = SecureSession(graph, alice)
repo = session.get_repository(Person)
p = repo.find_by_id(1)
print(p.name) # β Allowed
print(p.ssn) # None (filtered)Admin API example:
from falkordb_orm.security import RBACManager
admin_session = SecureSession(graph, admin_user)
rbac = RBACManager(graph, admin_session.security_context)
rbac.create_user("bob", "bob@example.com", roles=["editor"]) # create
rbac.assign_role("alice", "editor") # assign
rbac.grant_privilege("editor", "WRITE", "NODE", "Document") # grant
logs = rbac.query_audit_logs(limit=10) # auditfrom falkordb_orm import query
class PersonRepository(Repository[Person]):
@query(
"MATCH (p:Person)-[:KNOWS]->(f:Person) WHERE p.name = $name RETURN f",
returns=Person
)
def find_friends(self, name: str) -> List[Person]:
pass
@query(
"MATCH (p:Person) WHERE p.age BETWEEN $min AND $max RETURN p",
returns=Person
)
def find_by_age_range(self, min: int, max: int) -> List[Person]:
passfrom falkordb import FalkorDB
db = FalkorDB(host='localhost', port=6379)
g = db.select_graph('social')
# Create manually
g.query('''
CREATE (p:Person {name: $name, email: $email, age: $age})
RETURN p, id(p)
''', {'name': 'Alice', 'email': 'alice@example.com', 'age': 25})
# Query manually
result = g.query('''
MATCH (p:Person) WHERE p.age > $min_age RETURN p
''', {'min_age': 18})
# Manual result processing
for record in result.result_set:
person = record[0]
print(person.properties['name'])from falkordb import FalkorDB
from falkordb_orm import node, Repository
@node("Person")
class Person:
id: Optional[int] = None
name: str
email: str
age: int
db = FalkorDB(host='localhost', port=6379)
graph = db.select_graph('social')
repo = Repository(graph, Person)
# Create with ORM
person = Person(name="Alice", email="alice@example.com", age=25)
saved = repo.save(person)
# Query with derived method
adults = repo.find_by_age_greater_than(18)
# Automatic object mapping
for person in adults:
print(person.name)The full Sphinx documentation can be browsed locally. To build the HTML docs:
poetry install --with docs
poetry run sphinx-build -W -b html docs docs/_build/htmlAfter building, open docs/_build/html/index.html in your browser. Key documentation sections include:
- Quick Start Guide - Get started in minutes
- Design Document - Architecture, metadata, and mapping patterns
- Core Concepts - Node mapping, property mappings, and relationships
- Data Access - Repositories, derived queries, and custom Cypher
- Transactions & Sessions - Identity maps, dirty tracking, and unit of work
- Security & RBAC - Enterprise-grade access control, secure sessions, and audit logging
- Asynchronous Support - Non-blocking repository operations and async lazy loading
- Migration Guide - Migrating from the raw FalkorDB client
This project is inspired by Spring Data FalkorDB and aligned with the broader FalkorDB ORM ecosystem, including falkordb-go-orm.
| Capability | spring-data-falkordb |
falkordb-go-orm |
falkordb-orm (Python) |
|---|---|---|---|
| Entity mapping | @Node, @Property, @Relationship, @Id |
Struct tags (falkor:"...") + Labels() |
@node, property(), relationship(), generated_id() |
| Repository abstraction | FalkorDBRepository<T, ID> |
Repository[T] |
Repository[T], AsyncRepository[T] |
| Derived query methods | β Spring Data method derivation | β
Method-name parsing (ExecuteDerivedQuery) |
β Method-name derivation |
| Custom queries | β
@Query |
β
Fluent QueryBuilder / Cypher |
β
@query |
| Pagination | β
Pageable, Page<T> |
β
Pageable, Page[T] |
β
Pageable, Page[T] |
| Relationship support | β
RelationshipManager + lazy relationship wrappers |
β Lazy/eager loading + cascade save | |
| Session / transactions | @Transactional support documented; full transaction integration marked in progress |
β
Session API |
β
Session, AsyncSession |
| Index management | β
IndexManager |
β
@indexed, @unique, IndexManager |
|
| Schema validation/sync | β
SchemaManager |
β
SchemaManager |
|
| RBAC / security | @Secured, row-level security) |
β
Optional security package (action, property, row-level metadata) |
β
Built-in RBAC module (@secured, SecureSession, RBACManager) |
| Async model | β | β Go concurrency model (no async repository abstraction) | β Async repositories/sessions |
| Framework integration | β Spring Boot starter + auto-configuration | β Framework-agnostic Go library | β Framework-agnostic Python library (examples with FastAPI/Flask patterns) |
| Language/runtime | Java 17+ | Go 1.21+ | Python 3.9+ |
Notes:
- Spring Data statuses above reflect its own README implementation notes (including items explicitly marked βin progressβ and βexperimentalβ).
- Go ORM statuses reflect
README.mdandAPI.mdin../falkordb-go-orm.
- Developer Productivity: Reduce boilerplate code and manual Cypher writing
- Type Safety: Leverage Python type hints for better IDE support
- Familiarity: Use patterns developers know from SQLAlchemy, Django ORM, Spring Data
- Performance: Optimize queries, support batching, implement lazy loading
- Flexibility: Support both simple and complex use cases
- Python: 3.9+
- FalkorDB Client: falkordb-py
- Type Hints: For IDE support and validation
- Decorators: For entity and query definition
- Generics: For type-safe repositories
pip install falkordb-ormOr install from source:
git clone https://github.com/FalkorDB/falkordb-py-orm.git
cd falkordb-py-orm
pip install -e .- Convention over Configuration: Sensible defaults with customization options
- Explicit is Better: Clear, readable API over implicit magic
- Performance Matters: Optimize for common use cases
- Pythonic: Follow Python idioms and best practices
- Compatibility: Work seamlessly with existing falkordb-py code
MIT License - See LICENSE file for details
- Inspired by Spring Data FalkorDB
- Built on top of falkordb-py
- Follows patterns from SQLAlchemy and Django ORM
Contributions are welcome! Please see CONTRIBUTING.md for guidelines.
- FalkorDB: falkordb.com
- Discord: FalkorDB Community
- GitHub: FalkorDB Organization