A lightweight Data Mapper-based OGM for Neo4j with Unit of Work, Repository, and Query Builder
Project description
neoloom
A lightweight Data Mapper-based OGM for Neo4j featuring:
- Unit of Work and Repository patterns
- Identity Map
- Lazy-loading relationships
- Simple Query Builder with a
QuerySetandQfilters - Field descriptors with validation
This README shows how to use the published PyPI package neoloom in your application code.
Install
pip install neoloom
Requirements:
- Python 3.10+
- A running Neo4j instance (e.g., bolt://localhost:7687) and credentials
Quick start
from neoloom import Session, Repository
from neoloom.nodes.node import BaseNode
from neoloom.fields.node import StringField, IntegerField
class User(BaseNode):
name = StringField(nullable=False)
age = IntegerField(nullable=True)
session = Session(uri="bolt://localhost:7687", user="neo4j", password="password")
users = Repository(session, User)
# Create
alice = User(name="Alice", age=30)
users.add(alice)
session.commit()
# Read
fetched = users.get(name="Alice")
print(fetched.name)
Defining nodes and fields
Use BaseNode and field descriptors from neoloom.fields.node:
from neoloom.nodes.node import BaseNode
from neoloom.fields.node import StringField, IntegerField, BooleanField, FloatField, DateTimeField
class Product(BaseNode):
sku = StringField(unique=True, nullable=False)
title = StringField(nullable=False)
price = FloatField(nullable=False)
in_stock = BooleanField(default=True)
Notes:
- Validation: Fields enforce types and optional constraints (e.g., max_length for
StringField, min/max forIntegerField). - Defaults: If a field has
default, it is applied on instantiation. - Uniqueness: Unique fields are validated during
save()for inserts.
Relationships
neoloom supports two relationship layers you can choose from:
- Descriptor-based lazy relationships via
neoloom.fields.relation.RelationshipandBaseRelation(recommended for navigation/lazy-load). - Explicit relationship models via
neoloom.relationships(for creating/deleting relationships with properties).
Lazy relationships (descriptor)
Define relationships on nodes using Relationship. To avoid forward-reference issues, reference a class defined earlier:
from neoloom.nodes.node import BaseNode
from neoloom.fields.node import StringField
from neoloom.fields.relation import Relationship
class Group(BaseNode):
name = StringField(nullable=False)
class User(BaseNode):
name = StringField(nullable=False)
# Outgoing relationship to Group via type MEMBER_OF
groups = Relationship(related_node=Group, rel_type="MEMBER_OF", direction="->")
Access is lazy and requires the instance to be attached to a session (repositories do this automatically via an internal hint). Example traversal:
from neoloom import Session, Repository
session = Session(uri="bolt://localhost:7687", user="neo4j", password="password")
users = Repository(session, User)
alice = users.get(name="Alice")
for g in alice.groups: # triggers a lazy query
print(g.name)
To assign relationships for creation during save, set the descriptor value to a node or list of nodes before session.commit():
devs = Group(name="Developers")
groups = Repository(session, Group)
groups.add(devs)
# Link Alice -> Developers on commit
alice.groups = devs
users.update(alice)
session.commit() # creates the MEMBER_OF relationship
Relationship models (create/delete with properties)
If you need to create/delete relationships with properties, use neoloom.relationships:
from neoloom.relationships import RelationshipTo
class MemberOf(RelationshipTo):
pass # optionally define relationship property fields here
# Create relationship
MemberOf.create(session.ogm(), from_node=alice, to_node=devs)
# Delete relationship
MemberOf.delete(session.ogm(), from_node=alice, to_node=devs)
Sessions and repositories
Session wraps the database driver and coordinates:
- Unit of Work (
add,update,deleteviaRepository) - Identity Map (basic caching by element id)
- Attaching an OGM hint to instances for lazy relationships
from neoloom import Session, Repository
session = Session(uri="bolt://localhost:7687", user="neo4j", password="password")
users = Repository(session, User)
# Add
users.add(User(name="Carol"))
# Update (marks dirty)
carol = users.get(name="Carol")
carol.name = "Caroline"
users.update(carol)
# Commit all pending changes
session.commit()
Querying
There are two ways to query:
- High-level repository methods (
get,find) - Low-level
QuerySetusingQobjects for compound conditions and ordering
Repository queries
# Find many (equality filters)
matches = users.find(name="Caroline")
# Get single (first match)
caroline = users.get(name="Caroline")
QuerySet + Q filters
from neoloom import Q
from neoloom.query import QuerySet
qs = QuerySet(User, session.ogm())
# Basic filters
qs = qs.filter(name="Alice").order_by("-age").limit(10)
# Using Q for complex filters
q = Q.from_kwargs(age__gte=18) & Q.from_kwargs(name="Alice")
qs = QuerySet(User, session.ogm()).filter(q)
results = list(qs)
Supported lookups in Q keys:
field(equals)field__gt,field__gte,field__lt,field__lte- Combine with
&(AND) and|(OR)
Creating and updating nodes directly
You can also operate through mappers and save on models:
from neoloom.mappers import NodeMapper
mapper = NodeMapper(User)
alice = User(name="Alice")
mapper.insert(session.ogm(), alice) # create
alice.age = 31
mapper.update(session.ogm(), alice) # update (uses unique fields if defined)
Or by calling save through the Unit of Work via Repository as shown earlier.
Serialization
Nodes provide serialize() and deserialize() to convert to/from Python dicts. DateTime values returned from Neo4j are normalized to Python datetime via the built-in serializer.
payload = alice.serialize()
clone = User.deserialize(payload)
Raw Cypher and APOC iterate
For advanced needs:
from datetime import datetime
ogm = session.ogm()
# Build a query
rows = (
ogm.match_node(alias="u", label="User")
.where(alias="u", field="age", operator="gte", value=18)
.return_("u{.*}")
.execute()
)
# Execute raw cypher
rows = ogm.execute_raw_cypher("MATCH (u:User) RETURN u LIMIT $n", {"n": 5})
# Use APOC periodic iterate with currently built read/write parts
ogm.match_node(alias="u", label="User")
.return_("u")
.create_node(alias="x", label="Log", properties={"ts": datetime.utcnow()})
.call_apoc_periodic_iterate({"batchSize": 1000})
.execute()
Best practices
- Unique fields: Set
unique=Trueon fields you’ll use to locate/update nodes reliably. - Lazy relations: Access relationship descriptors after fetching instances via a
Repositoryso the session hint is present. - Unit of Work: Prefer
Repository.add/updatefollowed bysession.commit()to group writes. - Small batches: Chain builder methods and call
execute()once per logical operation. - Closing: Call
session.close()when done.
License
MIT
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 neoloom-0.1.0.tar.gz.
File metadata
- Download URL: neoloom-0.1.0.tar.gz
- Upload date:
- Size: 17.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.16
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f078689cf3c8cf74f536c85d831c92a60168eba9343ab6035a3a9de47ce384f0
|
|
| MD5 |
eed98c4e740a2f15cf0ee514fe654a88
|
|
| BLAKE2b-256 |
301ba93b4441c702641540fa1af105c7c38573bd5f0d8890578339fa95e5d367
|
File details
Details for the file neoloom-0.1.0-py3-none-any.whl.
File metadata
- Download URL: neoloom-0.1.0-py3-none-any.whl
- Upload date:
- Size: 23.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.16
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1519ccafb89f2bc2dcba303ce4e7d5eb07544acca96db6ffd4aca2c2787bc242
|
|
| MD5 |
6ba39e244e88a42df0716fa1890b93c7
|
|
| BLAKE2b-256 |
7e3a56f6828a4b1a7d4893d4fc83d51c61cbfb981e16b4e19a2c13b766c9f5aa
|