Skip to main content

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 QuerySet and Q filters
  • 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 for IntegerField).
  • 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.Relationship and BaseRelation (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, delete via Repository)
  • 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 QuerySet using Q objects 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=True on fields you’ll use to locate/update nodes reliably.
  • Lazy relations: Access relationship descriptors after fetching instances via a Repository so the session hint is present.
  • Unit of Work: Prefer Repository.add/update followed by session.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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

neoloom-0.1.0.tar.gz (17.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

neoloom-0.1.0-py3-none-any.whl (23.0 kB view details)

Uploaded Python 3

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

Hashes for neoloom-0.1.0.tar.gz
Algorithm Hash digest
SHA256 f078689cf3c8cf74f536c85d831c92a60168eba9343ab6035a3a9de47ce384f0
MD5 eed98c4e740a2f15cf0ee514fe654a88
BLAKE2b-256 301ba93b4441c702641540fa1af105c7c38573bd5f0d8890578339fa95e5d367

See more details on using hashes here.

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

Hashes for neoloom-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 1519ccafb89f2bc2dcba303ce4e7d5eb07544acca96db6ffd4aca2c2787bc242
MD5 6ba39e244e88a42df0716fa1890b93c7
BLAKE2b-256 7e3a56f6828a4b1a7d4893d4fc83d51c61cbfb981e16b4e19a2c13b766c9f5aa

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page