Skip to content

Repository files navigation

FalkorDB Python ORM

Object-Graph Mapping for FalkorDB with Spring Data-inspired patterns

Python Version License FalkorDB PyPI

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.

🎯 Project Status

Production Ready! βœ… The FalkorDB Python ORM is fully implemented, tested, and documented.

πŸš€ Features

Core Capabilities

  • 🏷️ Decorator-based Entity Mapping: Use @node and property decorators 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

Query Features

  • πŸ” 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: @query decorator with parameter binding
  • πŸ“Š Aggregation Methods: Built-in sum(), avg(), min(), max() functions

Relationships

  • πŸ”— 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

Async Support

  • ⚑ AsyncRepository: Full async/await support for all CRUD operations
  • πŸ”„ Async Relationships: Async lazy loading with AsyncLazyList and AsyncLazySingle
  • πŸ“Š Async Derived Queries: Auto-generated async query methods
  • 🌐 Framework Ready: Perfect for FastAPI, aiohttp, and async Python applications

Advanced Features

  • ⚑ Transaction Support: Context managers with identity map and change tracking
  • πŸ—‚οΈ Index Management: @indexed and @unique decorators with schema validation
  • πŸ“„ Pagination: Full pagination with sorting and navigation (Pageable, Page[T])
  • πŸ”„ Relationship Updates: Automatic deletion of old edges when relationships change

Security Features πŸ”’

  • πŸ” Role-Based Access Control (RBAC): Enterprise-grade security with fine-grained permissions
  • πŸ‘₯ User & Role Management: Built-in user, role, and privilege entities
  • πŸ›‘οΈ Declarative Security: @secured decorator 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

Production Features

  • πŸ“š 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 @interned decorator
  • πŸ§ͺ Integration Tests: Full end-to-end tests with real FalkorDB

πŸ“‹ Future Enhancements

  • πŸ“¦ Migration System: Schema version management and migrations
  • πŸ” Query Result Caching: Result caching for performance
  • βš™οΈ Batch Optimization: UNWIND-based bulk operations

πŸ“œ Usage

Entity Definition

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")

Repository Usage

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!

Async Usage

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())

Transaction Support

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()

Index Management

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])

Pagination

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())

Getting Started

For a complete walkthrough, see QUICKSTART.md.

Security Quick Start

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: float

Create 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)                            # audit

Custom Queries

from 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]:
        pass

πŸ“‹ Comparison with Current Approach

Before (Raw Cypher)

from 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'])

After (ORM)

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)

πŸ“š Documentation

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/html

After building, open docs/_build/html/index.html in your browser. Key documentation sections include:

🀝 Comparison with other FalkorDB ORM solutions

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 ⚠️ Mapping supported; automatic relationship persistence marked in progress βœ… 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 ⚠️ No dedicated index manager documented βœ… IndexManager βœ… @indexed, @unique, IndexManager
Schema validation/sync ⚠️ No dedicated schema manager documented βœ… SchemaManager βœ… SchemaManager
RBAC / security ⚠️ Optional/experimental (@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.md and API.md in ../falkordb-go-orm.

🎯 Goals

  1. Developer Productivity: Reduce boilerplate code and manual Cypher writing
  2. Type Safety: Leverage Python type hints for better IDE support
  3. Familiarity: Use patterns developers know from SQLAlchemy, Django ORM, Spring Data
  4. Performance: Optimize queries, support batching, implement lazy loading
  5. Flexibility: Support both simple and complex use cases

πŸ”§ Technology Stack

  • 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

πŸ“¦ Installation

pip install falkordb-orm

Or install from source:

git clone https://github.com/FalkorDB/falkordb-py-orm.git
cd falkordb-py-orm
pip install -e .

πŸ’‘ Design Principles

  1. Convention over Configuration: Sensible defaults with customization options
  2. Explicit is Better: Clear, readable API over implicit magic
  3. Performance Matters: Optimize for common use cases
  4. Pythonic: Follow Python idioms and best practices
  5. Compatibility: Work seamlessly with existing falkordb-py code

πŸ“„ License

MIT License - See LICENSE file for details

πŸ™ Acknowledgments

🀝 Contributing

Contributions are welcome! Please see CONTRIBUTING.md for guidelines.

πŸ“¦ Resources

About

Object Mapping library for Graph data, leveraging FalkorDB as a backend

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

10 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages