Skip to content

Commit 3ce3473

Browse files
Merge pull request #874 from neo4j-contrib/rc/5.5.0
Rc/5.5.0
2 parents 96a8cbc + c53ee0e commit 3ce3473

File tree

11 files changed

+403
-91
lines changed

11 files changed

+403
-91
lines changed

Changelog

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
Version 5.5.0 2025-06
2+
* Add traverse method which merges traverse_relations and fetch_relations and improves them
3+
* Deprecate traverse_relations and fetch_relations
4+
* Fix issue where WITH * is inserted in wrong places
5+
16
Version 5.4.5 2025-03
27
* Add method unique_variables to force reuse of variables in traversals
38
* Fix bug when filtering on relationship property

doc/requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
sphinx_copybutton
2-
neo4j~=5.19.0
2+
neo4j~=5.28.1
33

doc/source/advanced_query_operations.rst

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,9 @@ Here is a full example::
7272
await Coffee.nodes.fetch_relations("suppliers")
7373
.intermediate_transform(
7474
{
75-
"coffee": "coffee",
76-
"suppliers": NodeNameResolver("suppliers"),
77-
"r": RelationNameResolver("suppliers"),
75+
"coffee": {"source": "coffee"},
76+
"suppliers": {"source": NodeNameResolver("suppliers")},
77+
"r": {"source": RelationNameResolver("suppliers")},
7878
"coffee": {"source": "coffee", "include_in_return": True}, # Only coffee will be returned
7979
"suppliers": {"source": NodeNameResolver("suppliers")},
8080
"r": {"source": RelationNameResolver("suppliers")},
@@ -146,3 +146,15 @@ In some cases though, it is not possible to set explicit aliases, for example wh
146146
.. note::
147147

148148
When using the resolvers in combination with a traversal as in the example above, it will resolve the variable name of the last element in the traversal - the Species node for NodeNameResolver, and Coffee--Species relationship for RelationshipNameResolver.
149+
150+
Another example is to reference the root node itself::
151+
152+
subquery = await Coffee.nodes.subquery(
153+
Coffee.nodes.traverse_relations(suppliers="suppliers")
154+
.intermediate_transform(
155+
{"suppliers": {"source": "suppliers"}}, ordering=["suppliers.delivery_cost"]
156+
)
157+
.annotate(supps=Last(Collect("suppliers"))),
158+
["supps"],
159+
[NodeNameResolver("self")], # This is the root Coffee node
160+
)

doc/source/configuration.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ Adjust driver configuration - these options are only available for this connecti
3232
config.MAX_TRANSACTION_RETRY_TIME = 30.0 # default
3333
config.RESOLVER = None # default
3434
config.TRUST = neo4j.TRUST_SYSTEM_CA_SIGNED_CERTIFICATES # default
35-
config.USER_AGENT = neomodel/v5.4.5 # default
35+
config.USER_AGENT = neomodel/v5.5.0 # default
3636

3737
Setting the database name, if different from the default one::
3838

doc/source/getting_started.rst

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -261,10 +261,10 @@ Retrieving additional relations
261261
.. note::
262262

263263
You can fetch one or more relations within the same call
264-
to `.fetch_relations()` and you can mix optional and non-optional
264+
to `.traverse()` and you can mix optional and non-optional
265265
relations, like::
266266

267-
Person.nodes.fetch_relations('city__country', Optional('country')).all()
267+
Person.nodes.traverse('city__country', Path(value='country', optional=True)).all()
268268

269269
.. note::
270270

@@ -368,12 +368,12 @@ The example below will show you how you can mix and match query operations, as d
368368
full_nodeset = (
369369
await Student.nodes.filter(name__istartswith="m", lives_in__name="Eiffel Tower") # Combine filters
370370
.order_by("name")
371-
.fetch_relations(
371+
.traverse(
372372
"parents",
373-
Optional("children__preferred_course"),
374-
) # Combine fetch_relations
373+
Path(value="children__preferred_course", optional=True)
374+
) # Combine traversals
375375
.subquery(
376-
Student.nodes.fetch_relations("courses") # Root variable student will be auto-injected here
376+
Student.nodes.traverse("courses") # Root variable student will be auto-injected here
377377
.intermediate_transform(
378378
{"rel": RelationNameResolver("courses")},
379379
ordering=[

doc/source/traversal.rst

Lines changed: 68 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ Path traversal
66

77
Neo4j is about traversing the graph, which means leveraging nodes and relations between them. This section will show you how to traverse the graph using neomodel.
88

9-
We will cover two methods : `traverse_relations` and `fetch_relations`. Those two methods are *mutually exclusive*, so you cannot chain them.
9+
For this, the method to use is `traverse`.
10+
11+
Note that until version 6, two other methods are available, but deprecated : `traverse_relations` and `fetch_relations`. Those two methods are *mutually exclusive*, so you cannot chain them.
1012

1113
For the examples in this section, we will be using the following model::
1214

@@ -27,6 +29,59 @@ For the examples in this section, we will be using the following model::
2729
Traverse relations
2830
------------------
2931

32+
The `traverse` allows you to define multiple, multi-hop traversals, optionally returning traversed elements.
33+
34+
For example, to find all `Coffee` nodes that have a supplier, and retrieve the country of that supplier, you can do::
35+
36+
Coffee.nodes.traverse("suppliers__country").all()
37+
38+
This will generate a Cypher MATCH clause which traverses `Coffee<--Supplier-->Country`, and by default will return all traversed nodes and relationships.
39+
40+
This method allows you to define a more complex `Path` object, giving you more control over the traversal.
41+
42+
You can specify which elements to return, like::
43+
44+
# Return only the traversed nodes, not the relationships
45+
Coffee.nodes.traverse(Path(value="suppliers__country", include_rels_in_return=False))
46+
47+
# Return only the traversed relationships, not the nodes
48+
Coffee.nodes.traverse(Path(value="suppliers__country", include_nodes_in_return=False))
49+
50+
You can specify that your traversal should be optional, like::
51+
52+
# Return only the traversed nodes, not the relationships
53+
Coffee.nodes.traverse(Path(value="suppliers__country", optional=True))
54+
55+
You can also alias the path, so that you can reference it later in the query, like::
56+
57+
Coffee.nodes.traverse(Path(value="suppliers__country", alias="supplier_country"))
58+
59+
The `Country` nodes matched will be made available for the rest of the query, with the variable name `country`. Note that this aliasing is optional. See :ref:`Advanced query operations` for examples of how to use this aliasing.
60+
61+
.. note::
62+
63+
The `traverse` method can be used to traverse multiple paths, like::
64+
65+
Coffee.nodes.traverse('suppliers__country', 'pub__city').all()
66+
67+
This will generate a Cypher MATCH clause that traverses both paths `Coffee<--Supplier-->Country` and `Coffee<--Pub-->City`.
68+
69+
.. note::
70+
71+
When using `include_rels_in_return=True` (default), any relationship that you traverse using this method **MUST have a model defined**, even if only the default StructuredRel, like::
72+
73+
class Person(StructuredNode):
74+
country = RelationshipTo(Country, 'IS_FROM', model=StructuredRel)
75+
76+
Otherwise, neomodel will not be able to determine which relationship model to resolve into, and will fail.
77+
78+
Traverse relations (deprecated)
79+
-------------------------------
80+
81+
.. deprecated:: 5.5.0
82+
83+
This method is set to disappear in version 6, use `traverse` instead.
84+
3085
The `traverse_relations` method allows you to filter on the existence of more complex traversals. For example, to find all `Coffee` nodes that have a supplier, and retrieve the country of that supplier, you can do::
3186

3287
Coffee.nodes.traverse_relations(country='suppliers__country').all()
@@ -43,8 +98,12 @@ The `Country` nodes matched will be made available for the rest of the query, wi
4398

4499
This will generate a Cypher MATCH clause that enforces the existence of at least one path like `Coffee<--Supplier-->Country` and `Coffee<--Pub-->City`.
45100

46-
Fetch relations
47-
---------------
101+
Fetch relations (deprecated)
102+
----------------------------
103+
104+
.. deprecated:: 5.5.0
105+
106+
This method is set to disappear in version 6, use `traverse` instead.
48107

49108
The syntax for `fetch_relations` is similar to `traverse_relations`, except that the generated Cypher will return all traversed objects (nodes and relations)::
50109

@@ -59,8 +118,12 @@ The syntax for `fetch_relations` is similar to `traverse_relations`, except that
59118

60119
Otherwise, neomodel will not be able to determine which relationship model to resolve into, and will fail.
61120

62-
Optional match
63-
--------------
121+
Optional match (deprecated)
122+
---------------------------
123+
124+
.. deprecated:: 5.5.50
125+
126+
This method is set to disappear in version 6, use `traverse` instead.
64127

65128
With both `traverse_relations` and `fetch_relations`, you can force the use of an ``OPTIONAL MATCH`` statement using the following syntax::
66129

neomodel/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "5.4.5"
1+
__version__ = "5.5.0"

0 commit comments

Comments
 (0)