Skip to content

Commit c9c4b9f

Browse files
committed
Allow Driver as argument to from_neo4j
1 parent c01c10f commit c9c4b9f

File tree

4 files changed

+78
-14
lines changed

4 files changed

+78
-14
lines changed

changelog.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88

99
## New features
1010

11+
* Allow passing a `neo4j.Driver` instance as input to `from_neo4j`, in which case the driver will be used internally to fetch the graph data using a simple query
12+
1113

1214
## Bug fixes
1315

docs/source/integration.rst

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -184,13 +184,16 @@ Once you have installed the additional dependency, you can use the :doc:`from_ne
184184
to import query results from Neo4j.
185185

186186
The ``from_neo4j`` method takes one mandatory positional parameter:
187-
188-
* A ``result`` representing the query result either in form of `neo4j.graph.Graph` or `neo4j.Result`.
187+
A ``data`` argument representing either a query result in the shape of a ``neo4j.graph.Graph`` or ``neo4j.Result``, or a
188+
``neo4j.Driver`` in which case a simple default query will be executed internally to retrieve the graph data.
189189

190190
We can also provide an optional ``size_property`` parameter, which should refer to a node property,
191191
and will be used to determine the sizes of the nodes in the visualization.
192192

193-
The ``node_caption`` and ``relationship_caption`` parameters are also optional, and indicate the node and relationship properties to use for the captions of each element in the visualization.
193+
The ``node_caption`` and ``relationship_caption`` parameters are also optional, and indicate the node and relationship
194+
properties to use for the captions of each element in the visualization.
195+
By default, the captions will be set to the node labels relationship types, but you can specify any property that
196+
exists on these entities.
194197

195198
The last optional property, ``node_radius_min_max``, can be used (and is used by default) to scale the node sizes for
196199
the visualization.

python-wrapper/src/neo4j_viz/neo4j.py

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from typing import Optional, Union
44

55
import neo4j.graph
6-
from neo4j import Result
6+
from neo4j import Driver, Result, RoutingControl
77
from pydantic import BaseModel, ValidationError
88

99
from neo4j_viz.node import Node
@@ -20,14 +20,15 @@ def _parse_validation_error(e: ValidationError, entity_type: type[BaseModel]) ->
2020

2121

2222
def from_neo4j(
23-
result: Union[neo4j.graph.Graph, Result],
23+
data: Union[neo4j.graph.Graph, Result, Driver],
2424
size_property: Optional[str] = None,
2525
node_caption: Optional[str] = "labels",
2626
relationship_caption: Optional[str] = "type",
2727
node_radius_min_max: Optional[tuple[float, float]] = (3, 60),
28+
row_limit: int = 10_000,
2829
) -> VisualizationGraph:
2930
"""
30-
Create a VisualizationGraph from a Neo4j Graph or Neo4j Result object.
31+
Create a VisualizationGraph from a Neo4j `Graph`, Neo4j `Result` or Neo4j `Driver`.
3132
3233
All node and relationship properties will be included in the visualization graph.
3334
If the properties are named as the fields of the `Node` or `Relationship` classes, they will be included as
@@ -36,8 +37,9 @@ def from_neo4j(
3637
3738
Parameters
3839
----------
39-
result : Union[neo4j.graph.Graph, Result]
40-
Query result either in shape of a Graph or result.
40+
data : Union[neo4j.graph.Graph, neo4j.Result, neo4j.Driver]
41+
Either a query result in the shape of a `neo4j.graph.Graph` or `neo4j.Result`, or a `neo4j.Driver` in
42+
which case a simple default query will be executed internally to retrieve the graph data.
4143
size_property : str, optional
4244
Property to use for node size, by default None.
4345
node_caption : str, optional
@@ -47,14 +49,23 @@ def from_neo4j(
4749
node_radius_min_max : tuple[float, float], optional
4850
Minimum and maximum node radius, by default (3, 60).
4951
To avoid tiny or huge nodes in the visualization, the node sizes are scaled to fit in the given range.
52+
row_limit : int, optional
53+
Maximum number of rows to return from the query, by default 10_000.
54+
This is only used if a `neo4j.Driver` is passed as `result` argument, otherwise the limit is ignored.
5055
"""
5156

52-
if isinstance(result, Result):
53-
graph = result.graph()
54-
elif isinstance(result, neo4j.graph.Graph):
55-
graph = result
57+
if isinstance(data, Result):
58+
graph = data.graph()
59+
elif isinstance(data, neo4j.graph.Graph):
60+
graph = data
61+
elif isinstance(data, Driver):
62+
graph = data.execute_query(
63+
f"MATCH (n)-[r]->(m) RETURN n,r,m LIMIT {row_limit}",
64+
routing_=RoutingControl.READ,
65+
result_transformer_=Result.graph,
66+
)
5667
else:
57-
raise ValueError(f"Invalid input type `{type(result)}`. Expected `neo4j.Graph` or `neo4j.Result`")
68+
raise ValueError(f"Invalid input type `{type(data)}`. Expected `neo4j.Graph`, `neo4j.Result` or `neo4j.Driver`")
5869

5970
all_node_field_aliases = Node.all_validation_aliases()
6071
all_rel_field_aliases = Relationship.all_validation_aliases()

python-wrapper/tests/test_neo4j.py

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import neo4j
44
import pytest
5-
from neo4j import Session
5+
from neo4j import Driver, Session
66

77
from neo4j_viz.neo4j import from_neo4j
88
from neo4j_viz.node import Node
@@ -201,3 +201,51 @@ def test_from_neo4j_rel_error(neo4j_session: Session) -> None:
201201
match="Error for relationship property 'caption_align' with provided input 'banana'. Reason: Input should be 'top', 'center' or 'bottom'",
202202
):
203203
from_neo4j(graph)
204+
205+
206+
@pytest.mark.requires_neo4j_and_gds
207+
def test_from_neo4j_graph_driver(neo4j_session: Session, neo4j_driver: Driver) -> None:
208+
graph = neo4j_session.run("MATCH (a:_CI_A|_CI_B)-[r]->(b) RETURN a, b, r ORDER BY a").graph()
209+
210+
# Note that this tests requires an empty Neo4j database, as it just fetches everything
211+
VG = from_neo4j(neo4j_driver)
212+
213+
sorted_nodes: list[neo4j.graph.Node] = sorted(graph.nodes, key=lambda x: dict(x.items())["name"])
214+
node_ids: list[str] = [node.element_id for node in sorted_nodes]
215+
216+
expected_nodes = [
217+
Node(
218+
id=node_ids[0],
219+
caption="_CI_A",
220+
properties=dict(
221+
labels=["_CI_A"],
222+
name="Alice",
223+
height=20,
224+
id=42,
225+
_id=1337,
226+
caption="hello",
227+
),
228+
),
229+
Node(
230+
id=node_ids[1],
231+
caption="_CI_A:_CI_B",
232+
size=11,
233+
properties=dict(
234+
labels=["_CI_A", "_CI_B"],
235+
name="Bob",
236+
height=10,
237+
id=84,
238+
__labels=[1, 2],
239+
),
240+
),
241+
]
242+
243+
assert len(VG.nodes) == 2
244+
assert sorted(VG.nodes, key=lambda x: x.properties["name"]) == expected_nodes
245+
246+
assert len(VG.relationships) == 2
247+
vg_rels = sorted([(e.source, e.target, e.caption) for e in VG.relationships], key=lambda x: x[2] if x[2] else "foo")
248+
assert vg_rels == [
249+
(node_ids[0], node_ids[1], "KNOWS"),
250+
(node_ids[1], node_ids[0], "RELATED"),
251+
]

0 commit comments

Comments
 (0)