Last updated:
0 purchases
pyneo4jogm 0.6.0
pyneo4j-ogm
pyneo4j-ogm is a asynchronous Object-Graph-Mapper for Neo4j 5+ and Python 3.10+. It is inspired by beanie and build on top of proven technologies like Pydantic 1.10+ and 2+ and the Neo4j Python Driver. It saves you from writing ever-repeating boilerplate queries and allows you to focus on the stuff that actually matters. It is designed to be simple and easy to use, but also flexible and powerful.
🎯 Features
pyneo4j-ogm has a lot to offer, including:
Fully typed: pyneo4j-ogm is fully typed out of the box.
Powerful validation: Since we use Pydantic under the hood, you can use it's powerful validation and serialization features without any issues.
Focus on developer experience: Designed to be simple to use, pyneo4j-ogm provides features for both simple queries and more advanced use-cases while keeping it's API as simple as possible.
Build-in migration tooling: Shipped with simple, yet flexible migration tooling.
Fully asynchronous: Completely asynchronous code, thanks to the Neo4j Python Driver.
Supports Neo4j 5+: pyneo4j-ogm supports Neo4j 5+ and is tested against the latest version of Neo4j.
Multi-version Pydantic support: Both Pydantic 1.10+ and 2+ fully supported.
📣 Announcements
Things to come in the future. Truly exiting stuff! If you have feature requests which you think might improve pyneo4j-ogm, feel free to open up a feature request.
MemGraph support.
📦 Installation
Using pip:
pip install pyneo4j-ogm
or when using Poetry:
poetry add pyneo4j-ogm
🚀 Quickstart
Before we can get going, we have to take care of some things:
We need to define our models, which will represent the nodes and relationships inside our database.
We need a database client, which will do the actual work for us.
Defining our data structures
Since every developer has a coffee addiction one way or another, we are going to use Coffee and Developers for this guide. So let's start by defining what our data should look like:
from pyneo4j_ogm import (
NodeModel,
RelationshipModel,
RelationshipProperty,
RelationshipPropertyCardinality,
RelationshipPropertyDirection,
WithOptions,
)
from pydantic import Field
from uuid import UUID, uuid4
class Developer(NodeModel):
"""
This class represents a `Developer` node inside the graph. All interactions
with nodes of this type will be handled by this class.
"""
uid: WithOptions(UUID, unique=True) = Field(default_factory=uuid4)
name: str
age: int
coffee: RelationshipProperty["Coffee", "Consumed"] = RelationshipProperty(
target_model="Coffee",
relationship_model="Consumed",
direction=RelationshipPropertyDirection.OUTGOING,
cardinality=RelationshipPropertyCardinality.ZERO_OR_MORE,
allow_multiple=True,
)
class Settings:
# Hooks are available for all methods that interact with the database.
post_hooks = {
"coffee.connect": lambda self, *args, **kwargs: print(f"{self.name} chugged another one!")
}
class Coffee(NodeModel):
"""
This class represents a node with the labels `Beverage` and `Hot`. Notice
that the labels of this model are explicitly defined in the `Settings` class.
"""
flavor: str
sugar: bool
milk: bool
developers: RelationshipProperty["Developer", "Consumed"] = RelationshipProperty(
target_model=Developer,
relationship_model="Consumed",
direction=RelationshipPropertyDirection.INCOMING,
cardinality=RelationshipPropertyCardinality.ZERO_OR_MORE,
allow_multiple=True,
)
class Settings:
labels = {"Beverage", "Hot"}
class Consumed(RelationshipModel):
"""
Unlike the models above, this class represents a relationship between two
nodes. In this case, it represents the relationship between the `Developer`
and `Coffee` models. Like with node-models, the `Settings` class allows us to
define some configuration for this relationship.
Note that the relationship itself does not define it's start- and end-nodes,
making it reusable for other models as well.
"""
liked: bool
class Settings:
type = "CHUGGED"
Until now everything seems pretty standard if you have worked with other ORM's before. But if you haven't, we are going to go over what happened above:
We defined 2 node models Developer and Coffee, and a relationship Consumed.
Some models define a special inner Settings class. This is used to customize the behavior of our models inside the graph. More on these settings can be found here.
The WithOptions function has been used to define constraints and indexes (more about them here) on model properties.
Creating a database client
In pyneo4j-ogm, the real work is done by a database client. One of these bad-boys can be created by initializing a Pyneo4jClient instance. But for models to work as expected, we have to let our client know that we want to use them like so:
from pyneo4j_ogm import Pyneo4jClient
async def main():
# We initialize a new `Pyneo4jClient` instance and connect to the database.
client = Pyneo4jClient()
# Replace `<connection-uri-to-database>`, `<username>` and `<password>` with the
# actual values.
await client.connect(uri="<connection-uri-to-database>", auth=("<username>", "<password>"))
# To use our models for running queries later on, we have to register
# them with the client.
# **Note**: You only have to register the models that you want to use
# for queries and you can even skip this step if you want to use the
# `Pyneo4jClient` instance for running raw queries.
await client.register_models([Developer, Coffee, Consumed])
Interacting with the database
Now the fun stuff begins! We are ready to interact with our database. For the sake of this quickstart guide we are going to keep it nice and simple, but this is just the surface of what pyneo4j-ogm has to offer.
We are going to create a new Developer and some Coffee and give him something to drink:
# Imagine your models have been defined above...
async def main():
# And your client has been initialized and connected to the database...
# We create a new `Developer` node and the `Coffee` he is going to drink.
john = Developer(name="John", age=25)
await john.create()
cappuccino = Coffee(flavor="Cappuccino", milk=True, sugar=False)
await cappuccino.create()
# Here we create a new relationship between `john` and his `cappuccino`.
# Additionally, we set the `liked` property of the relationship to `True`.
await john.coffee.connect(cappuccino, {"liked": True}) # Will print `John chugged another one!`
Full example
import asyncio
from pyneo4j_ogm import (
NodeModel,
Pyneo4jClient,
RelationshipModel,
RelationshipProperty,
RelationshipPropertyCardinality,
RelationshipPropertyDirection,
WithOptions,
)
from pydantic import Field
from uuid import UUID, uuid4
class Developer(NodeModel):
"""
This class represents a `Developer` node inside the graph. All interaction
with nodes of this type will be handled by this class.
"""
uid: WithOptions(UUID, unique=True) = Field(default_factory=uuid4)
name: str
age: int
coffee: RelationshipProperty["Coffee", "Consumed"] = RelationshipProperty(
target_model="Coffee",
relationship_model="Consumed",
direction=RelationshipPropertyDirection.OUTGOING,
cardinality=RelationshipPropertyCardinality.ZERO_OR_MORE,
allow_multiple=True,
)
class Settings:
# Hooks are available for all methods that interact with the database.
post_hooks = {
"coffee.connect": lambda self, *args, **kwargs: print(f"{self.name} chugged another one!")
}
class Coffee(NodeModel):
"""
This class represents a node with the labels `Beverage` and `Hot`. Notice
that the labels of this model are explicitly defined in the `Settings` class.
"""
flavor: str
sugar: bool
milk: bool
developers: RelationshipProperty["Developer", "Consumed"] = RelationshipProperty(
target_model=Developer,
relationship_model="Consumed",
direction=RelationshipPropertyDirection.INCOMING,
cardinality=RelationshipPropertyCardinality.ZERO_OR_MORE,
allow_multiple=True,
)
class Settings:
labels = {"Beverage", "Hot"}
class Consumed(RelationshipModel):
"""
Unlike the models above, this class represents a relationship between two
nodes. In this case, it represents the relationship between the `Developer`
and `Coffee` models. Like with node-models, the `Settings` class allows us to
define some settings for this relationship.
Note that the relationship itself does not define it's start- and end-nodes,
making it reusable for other models as well.
"""
liked: bool
class Settings:
type = "CHUGGED"
async def main():
# We initialize a new `Pyneo4jClient` instance and connect to the database.
client = Pyneo4jClient()
await client.connect(uri="<connection-uri-to-database>", auth=("<username>", "<password>"))
# To use our models for running queries later on, we have to register
# them with the client.
# **Note**: You only have to register the models that you want to use
# for queries and you can even skip this step if you want to use the
# `Pyneo4jClient` instance for running raw queries.
await client.register_models([Developer, Coffee, Consumed])
# We create a new `Developer` node and the `Coffee` he is going to drink.
john = Developer(name="John", age=25)
await john.create()
cappuccino = Coffee(flavor="Cappuccino", milk=True, sugar=False)
await cappuccino.create()
# Here we create a new relationship between `john` and his `cappuccino`.
# Additionally, we set the `liked` property of the relationship to `True`.
await john.coffee.connect(cappuccino, {"liked": True}) # Will print `John chugged another one!`
# Be a good boy and close your connections after you are done.
await client.close()
asyncio.run(main())
And that's it! You should now see a Developer and a Hot/Beverage node, connected by a CONSUMED relationship. If you want to learn more about the library, you can check out the full Documentation.
📚 Documentation
In the following we are going to take a closer look at the different parts of pyneo4j-ogm and how to use them. We will cover everything pyneo4j-ogm has to offer, from the Pyneo4jClient to the NodeModel and RelationshipModel classes all the way to the Query filters and Auto-fetching relationship-properties.
Table of contents
pyneo4j-ogm
🎯 Features
📣 Announcements
📦 Installation
🚀 Quickstart
Defining our data structures
Creating a database client
Interacting with the database
Full example
Running the test suite
📚 Documentation
Basic concepts
A note on Pydantic version support
Database client
Connecting to the database
Closing an existing connection
Registering models
Executing Cypher queries
Batching cypher queries
Using bookmarks (Enterprise Edition only)
Manual indexing and constraints
Client utilities
Models
Indexes, constraints and properties
Reserved properties
Configuration settings
NodeModel configuration
RelationshipModel configuration
Available methods
Instance.update()
Instance.delete()
Instance.refresh()
Model.find_one()
Model.find_many()
Model.update_one()
Model.update_many()
Model.delete_one()
Model.delete_many()
Model.count()
NodeModelInstance.create()
NodeModelInstance.find_connected_nodes()
RelationshipModelInstance.start_node()
RelationshipModelInstance.end_node()
Serializing models
Hooks
Pre-hooks
Post-hooks
Model settings
Relationship-properties
Available methods
RelationshipProperty.relationships()
RelationshipProperty.connect()
RelationshipProperty.disconnect()
RelationshipProperty.disconnect_all()
RelationshipProperty.replace()
RelationshipProperty.find_connected_nodes()
Hooks with relationship properties
Queries
Filtering queries
Comparison operators
String operators
List operators
Logical operators
Element operators
Pattern matching
Multi-hop filters
Projections
Query options
Auto-fetching relationship-properties
Migrations
Initializing migrations for your project
Creating a new migration
Running migrations
Listing migrations
Programmatic usage
Logging
Running the test suite
To run the test suite, you have to install the development dependencies and run the tests using pytest. The tests are located in the tests directory. Some tests will require you to have a Neo4j instance running on localhost:7687 with the credentials (neo4j:password). This can easily be done using the provided docker-compose.yml file.
poetry run pytest tests --asyncio-mode=auto -W ignore::DeprecationWarning
Note: The -W ignore::DeprecationWarning can be omitted but will result in a lot of deprication warnings by Neo4j itself about the usage of the now deprecated ID.
As for running the tests with a different pydantic version, you can just install a different pydantic version with the following command:
poetry add pydantic@<version>
For personal and professional use. You cannot resell or redistribute these repositories in their original state.
There are no reviews.