Neo4j Client¶
The Neo4j client provides async access to the Neo4j graph database with connection pooling and CRUD operations.
Overview¶
The Neo4j client is a singleton that maintains a connection pool to the Neo4j database. It provides high-level CRUD operations and low-level query execution capabilities through cypherantic integration.
Basic Usage¶
from imbi_common import neo4j, models
# Initialize the client (creates indexes/constraints)
await neo4j.initialize()
# Create a node
org = models.Organization(name="My Org", slug="my-org")
created = await neo4j.create_node(org)
# Fetch a single node
org = await neo4j.fetch_node(
models.Organization, {"slug": "my-org"}
)
# Fetch multiple nodes
async for team in neo4j.fetch_nodes(
models.Team, order_by="name"
):
print(team.name)
# Upsert a node
await neo4j.upsert(org, constraint={"slug": org.slug})
# Create a relationship
await neo4j.create_relationship(
from_node=team,
to_node=org,
rel_type="BELONGS_TO"
)
# Delete a node
await neo4j.delete_node(
models.Organization, {"slug": "my-org"}
)
# Close the connection
await neo4j.aclose()
API Reference¶
Lifecycle¶
initialize
async
¶
aclose
async
¶
session
async
¶
Return a Neo4j AsyncSession for use in queries
Source code in src/imbi_common/neo4j/__init__.py
CRUD Operations¶
create_node
async
¶
Create a node in the graph.
This method uses cypherantic to create a node with:
- Labels extracted from model's cypherantic_config or class name
- Automatic unique constraints for fields marked with
Field(unique=True)
- Properties from the model's fields (excluding relationship fields)
:param model: Pydantic model instance to create as a node :returns: The created node as a Pydantic model with round-trip values
Source code in src/imbi_common/neo4j/__init__.py
fetch_node
async
¶
Fetch a single node from the graph by its unique key fields
Source code in src/imbi_common/neo4j/__init__.py
fetch_nodes
async
¶
fetch_nodes(
model: type[ModelType],
parameters: dict[str, Any] | None = None,
order_by: str | list[str] | None = None,
) -> typing.AsyncGenerator[ModelType, None]
Fetch nodes from the graph, optionally filtered by parameters
Source code in src/imbi_common/neo4j/__init__.py
upsert
async
¶
upsert(
node: BaseModel,
constraint: dict[str, Any],
auto_increment: list[str] | None = None,
) -> str
Save a node to the graph, returning the elementId.
:param node: Pydantic model instance to upsert
:param constraint: Dict of properties for the MERGE match
:param auto_increment: Optional list of field names (not aliases)
to increment atomically server-side using
coalesce(node.field, 0) + 1 instead of client values.
The model instance is updated in-place with the new values.
:returns: The elementId of the upserted node
Source code in src/imbi_common/neo4j/__init__.py
357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 | |
delete_node
async
¶
Delete a node from the graph by matching parameters.
This method deletes a node that matches the given parameters. The node label is extracted from the model class name.
:param model: Pydantic model class (label extracted from class name)
:param parameters: Dict of properties to match
(e.g., {'slug': 'my-slug', 'type': 'Project'})
:returns: True if node was deleted, False if not found
Example::
from imbi_common import models, neo4j
# Delete a blueprint by slug and type
deleted = await neo4j.delete_node(
models.Blueprint,
{'slug': 'my-blueprint', 'type': 'Project'}
)
if deleted:
print('Blueprint deleted successfully')
else:
print('Blueprint not found')
Source code in src/imbi_common/neo4j/__init__.py
Relationships¶
create_relationship
async
¶
create_relationship(
from_node: SourceNode,
to_node: TargetNode,
rel_props: RelationshipProperties | None = None,
*,
rel_type: str | None = None,
) -> neo4j.graph.Relationship
Create a relationship between two nodes.
This method creates a relationship by:
- Matching nodes by their unique key fields
(
Field(unique=True)metadata) - Using relationship type from
rel_props.cypherantic_configorrel_typeparameter - Attaching properties from
rel_propsmodel if provided
Either rel_props (a Pydantic model with relationship properties
and config) or rel_type (a string relationship type) must be
provided.
:param from_node: Source node model instance :param to_node: Target node model instance :param rel_props: Optional Pydantic model containing relationship properties :param rel_type: Optional explicit relationship type name :returns: The created Neo4j relationship object
Source code in src/imbi_common/neo4j/__init__.py
refresh_relationship
async
¶
Lazy-load and populate a relationship property on a model instance.
This is a lazy-loading mechanism that:
- Fetches related nodes from the graph based on relationship metadata
- Populates the specified relationship property on the model instance
- Validates the property is a Sequence with
Relationshipmetadata
The relationship property must be annotated as a sequence type
(e.g., list[EdgeType]) and have Relationship metadata that
specifies the relationship type and direction.
Use this when you have a model instance and need to populate its relationship properties from the graph database.
:param model: Model instance to populate :param rel_property: Name of the relationship property to refresh
Example::
from typing import Annotated
from cypherantic import Relationship
class Person(NodeModel):
name: Annotated[str, Field(unique=True)]
friends: Annotated[
list[FriendEdge],
Relationship(rel_type='FRIENDS_WITH', direction='OUTGOING')
] = []
person = Person(name='Alice')
await refresh_relationship(person, 'friends')
# person.friends now contains list of FriendEdge instances
Source code in src/imbi_common/neo4j/__init__.py
retrieve_relationship_edges
async
¶
retrieve_relationship_edges(
model: SourceNode,
rel_name: str,
direction: Literal[
'INCOMING', 'OUTGOING', 'UNDIRECTED'
],
edge_cls: type[EdgeType],
) -> list[EdgeType]
Retrieve relationship edges (nodes + properties) from the graph.
This method fetches related nodes along with their relationship properties, returning them as "edge" instances that contain both the target node and the relationship properties.
The edge_cls must be a type (typically a NamedTuple or dataclass)
with:
- node attribute: The target node Pydantic model class
- properties attribute: The relationship properties Pydantic
model class
:param model: Source node model instance to query from :param rel_name: Relationship type name to traverse :param direction: Direction to traverse (INCOMING, OUTGOING, or UNDIRECTED) :param edge_cls: Edge type class containing node and properties :returns: List of edge instances, each containing a node and its relationship properties
Example::
from typing import NamedTuple
class FriendEdge(NamedTuple):
node: Person
properties: FriendshipProperties
person = Person(name='Alice')
edges = await retrieve_relationship_edges(
person, 'FRIENDS_WITH', 'OUTGOING', FriendEdge
)
for edge in edges:
print(f"Friend: {edge.node.name}, since: {edge.properties.since}")
Source code in src/imbi_common/neo4j/__init__.py
Low-Level¶
run
async
¶
Run a Cypher query and return the result as an AsyncResult
Source code in src/imbi_common/neo4j/__init__.py
convert_neo4j_types ¶
Convert Neo4j-specific types to Python native types.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
Any
|
Data to convert (can be dict, list, or primitive type) |
required |
Returns:
| Type | Description |
|---|---|
Any
|
Data with Neo4j types converted to Python native types |