Design and testability: applying the Repository Pattern for Neo4j Python apps


I'm interested in building complex Python app based on Neo4j.
As I'm also influenced by software architecture, TDD and testability, I recently found an amazing online book "Architecture Patterns with Python"

Among the architectural patterns presented, the repository pattern struck me as essential in order to write my Python app with testability in mind

Repository Pattern

The intent of the pattern is to add an abstraction between the core parts of our application and the database to increase flexibility and testability, through the dependency inversion principle.

Example from the book with SQLAlchemy


  • decouples the Domain Model from Infrastructure concerns
    • dependency inversion: the ORM should import our model, not the other way around
  • switching between Neo4j drivers is now easy and doesn't involve a refactoring of our model
  • building a fake repository for unit test is now a trivial task

Repository Pattern with py2neo

This is my attempt at building this pattern for my application with py2neo:

First I defined my model:
Even though it should be clear from infrastructure definitions, I still had to implement the idea of a graph database here, by specifying 2 classes GraphNode and GraphRelationship.

Property = # TODO

class GraphNode:

class GraphRelationship:

# represents our users
class User(GraphNode):
    name = Property()

Then I created an abstract repository interface, with a single match method for now:

from model import GraphNode, GraphRelationship
from abc import ABC, abstractmethod

class AbstractGraphRepository(ABC):

    def match(object: Union[GraphNode, GraphRelationship]):
        raise NotImplementedError()

And implemented a concrete class based on py2neo

from repo import AbstractGraphRepository
from py2neo import Graph

class Py2NeoRepository(AbstractGraphRepository):

    def __init__(url: str):
        self._graph = Graph(url)

    def match(object: Union[GraphNode, GraphRelationship]):
        # how to map my domain model to py2neo OGM ?

I'm stuck at this step above :arrow_double_up: , figuring out what's the best way to define a mapping between my domain model, and py2neo's OGM.


If we take a look how they implemented this pattern in book, based on SQLAlchemy, we can see the following definitions:

from sqlalchemy.orm import mapper, relationship

import model  # dependency-inversion: the ORM depends on the model

metadata = MetaData()

order_lines = Table(  #(2)
    Column("id", Integer, primary_key=True, autoincrement=True),
    Column("sku", String(255)),
    Column("qty", Integer, nullable=False),
    Column("orderid", String(255)),


def start_mappers():
    # mapping is defined between the OrderLine model object and the order_lines table
    lines_mapper = mapper(model.OrderLine, order_lines)  

And this allows them to implement a concrete SQLAlchemyRepository really easily:

from model import Batch

class SqlAlchemyRepository(AbstractRepository):
    def __init__(self, session):
        self.session = session

    def add(self, batch: model.Batch):
        self.session.add(batch) # the mapping and translation from the Domain Model to the ORM is done transparently, thanks to the mapping !

    def get(self, reference):
        return self.session.query(model.Batch).filter_by(reference=reference).one()

    def list(self):
        return self.session.query(model.Batch).all()

:arrow_right: how to implement the same pattern with py2neo or neomodel as concrete repositories ?
:arrow_right: how to translate from a domain model to the OGM while keeping the overall code simple and efficient by design ?

Thank you in advance !

pinging @technige and and maybe Robin Edwards from neomodel

I can't immediately answer your question but I watched this (rather dated?) Neo4j video earlier today

The TDD bits start at 25 minutes, and is Java focused. But might give you some ideas.

1 Like

Hey Simon,

thank you for your answer ! I'm glad to see other people interested by testability.

The video details unit testing with the help of an in-memory database (a fake), which is also exactly what I am attempting to do with Neo4j Python here.

The question is how to design a good enough interface to honor the repository pattern and be permissive enough to reflect Neo4j's capabilities.