Skip to content

Commit 7f960cc

Browse files
Merge pull request #895 from neo4j-contrib/rc/5.5.1
Rc/5.5.1
2 parents bbf7a68 + 377a4ac commit 7f960cc

23 files changed

+1261
-115
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
venv
22
venv2
33
venv3
4+
.venv
45
build
56
doc/build
67
dist
@@ -21,4 +22,4 @@ pyvenv.cfg
2122
coverage_report/
2223
.coverage*
2324
.DS_STORE
24-
cov.xml
25+
cov.xml

Changelog

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
Version 5.5.1 2025-09
2+
* Fix cardinality enforcement for RelationshipFrom. Many thanks to @billycalladine
3+
* Change order of WITH and CALL{} statements in generated Cypher (intermediate_transform & subquery)
4+
* Add py.typed file to enable support from mypy
5+
* Bump to neo4j 5.28.2, uniform version pinning for rust-ext
6+
17
Version 5.5.0 2025-06
28
* Add traverse method which merges traverse_relations and fetch_relations and improves them
39
* Deprecate traverse_relations and fetch_relations

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.5.0 # default
35+
config.USER_AGENT = neomodel/v5.5.1 # default
3636

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

doc/source/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ Contents
7777
filtering_ordering
7878
traversal
7979
advanced_query_operations
80+
semantic_indexes
8081
cypher
8182
transactions
8283
hooks

doc/source/schema_management.rst

Lines changed: 2 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -46,38 +46,11 @@ Indexes
4646
The following indexes are supported:
4747

4848
- ``index=True``: This will create the default Neo4j index on the property (currently RANGE).
49-
- ``fulltext_index=FulltextIndex()``: This will create a FULLTEXT index on the property. Only available for Neo4j version 5.16 or higher. With this one, you can define the following options:
50-
- ``analyzer``: The analyzer to use. The default is ``standard-no-stop-words``.
51-
- ``eventually_consistent``: Whether the index should be eventually consistent. The default is ``False``.
52-
53-
Please refer to the `Neo4j documentation <https://neo4j.com/docs/cypher-manual/current/indexes/semantic-indexes/full-text-indexes/#configuration-settings>`_. for more information on fulltext indexes.
54-
55-
- ``vector_index=VectorIndex()``: This will create a VECTOR index on the property. Only available for Neo4j version 5.15 (node) and 5.18 (relationship) or higher. With this one, you can define the following options:
56-
- ``dimensions``: The dimension of the vector. The default is 1536.
57-
- ``similarity_function``: The similarity algorithm to use. The default is ``cosine``.
58-
59-
Those indexes are available for both node- and relationship properties.
49+
- :ref:`Semantic Indexes`
6050

6151
.. note::
6252
Yes, you can create multiple indexes of a different type on the same property. For example, a default index and a fulltext index.
6353

64-
.. note::
65-
For the semantic indexes (fulltext and vector), this allows you to create indexes, but searching those indexes require using Cypher queries.
66-
This is because Cypher only supports querying those indexes through a specific procedure for now.
67-
68-
Full example: ::
69-
70-
from neomodel import StructuredNode, StringProperty, FulltextIndex, VectorIndex
71-
class VeryIndexedNode(StructuredNode):
72-
name = StringProperty(
73-
index=True,
74-
fulltext_index=FulltextIndex(analyzer='english', eventually_consistent=True)
75-
)
76-
name_embedding = ArrayProperty(
77-
FloatProperty(),
78-
vector_index=VectorIndex(dimensions=512, similarity_function='euclidean')
79-
)
80-
8154
Constraints
8255
===========
8356

@@ -93,4 +66,4 @@ Extracting the schema from a database
9366
=====================================
9467

9568
You can extract the schema from an existing database using the ``neomodel_inspect_database`` script (:ref:`inspect_database_doc`).
96-
This script will output the schema in the neomodel format, including indexes and constraints.
69+
This script will output the schema in the neomodel format, including indexes and constraints.

doc/source/semantic_indexes.rst

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
.. _Semantic Indexes:
2+
3+
==================================
4+
Semantic Indexes
5+
==================================
6+
7+
Full Text Index
8+
----------------
9+
From version x.x (version number tbc) neomodel provides a way to interact with neo4j `Full Text indexing <https://neo4j.com/docs/cypher-manual/current/indexes/semantic-indexes/full-text-indexes/>`_.
10+
The Full Text Index can be be created for both node and relationship properties. Only available for Neo4j version 5.16 or higher.
11+
12+
Defining a Full Text Index on a Property
13+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
14+
Within neomodel, indexing is a decision that is made at class definition time as the index needs to be built. A Full Text index is defined using :class:`~neomodel.properties.FulltextIndex`
15+
To define a property with a full text index we use the following symantics::
16+
17+
StringProperty(fulltext_index=FulltextIndex(analyzer="standard-no-stop-words", eventually_consistent=False)
18+
19+
Where,
20+
- ``analyzer``: The analyzer to use. The default is ``standard-no-stop-words``.
21+
- ``eventually_consistent``: Whether the index should be eventually consistent. The default is ``False``.
22+
23+
The index must then be built, this occurs when the function :func:`~neomodel.sync_.core.install_all_labels` is run.
24+
25+
Please refer to the `Neo4j documentation <https://neo4j.com/docs/cypher-manual/current/indexes/semantic-indexes/full-text-indexes/#configuration-settings>`_ for more information on fulltext indexes.
26+
27+
Querying a Full Text Index on a Property
28+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
29+
30+
This is not currently implemented as a native neomodel query type. If you would like this please submit a github issue highlighting your useage pattern
31+
32+
Alternatively, whilst this has not bbeen implemetned yet you can still leverage `db.cypher_query` with the correct syntax to perform your required query.
33+
34+
Vector Index
35+
------------
36+
From version x.x (version number tbc) neomodel provides a way to interact with neo4j `vector indexing <https://neo4j.com/docs/cypher-manual/current/indexes/semantic-indexes/vector-indexes/>`_.
37+
38+
The Vector Index can be created on both node and relationship properties. Only available for Neo4j version 5.15 (node) and 5.18 (relationship) or higher.
39+
40+
Defining a Vector Index on a Property
41+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
42+
43+
Within neomodel, indexing is a decision that is made at class definition time as the index needs to be built. A vector index is defined using :class:`~neomodel.properties.VectorIndex`.
44+
To define a property with a vector index we use the following symantics::
45+
46+
ArrayProperty(base_property=FloatProperty(), vector_index=VectorIndex(dimensions=512, similarity_function="cosine")
47+
48+
Where,
49+
- ``dimensions``: The dimension of the vector. The default is 1536.
50+
- ``similarity_function``: The similarity algorithm to use. The default is ``cosine``.
51+
52+
The index must then be built, this occurs when the function :func:`~neomodel.sync_.core.install_all_labels` is run
53+
54+
The vector indexes will then have the name "vector_index_{node.__label__}_{propertyname_with_vector_index}".
55+
56+
.. attention::
57+
Neomodel creates a new vectorindex for each specified property, thus you cannot have two distinct properties being placed into the same index.
58+
59+
Querying a Vector Index on a Property
60+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
61+
62+
Node Property
63+
^^^^^^^^^^^^^
64+
The following node vector index property::
65+
66+
class someNode(StructuredNode):
67+
vector = ArrayProperty(base_property=FloatProperty(), vector_index=VectorIndex(dimensions=512, similarity_function="cosine")
68+
name = StringProperty()
69+
70+
Can be queried using :class:`~neomodel.sematic_filters.VectorFilter`. Such as::
71+
72+
from neomodel.semantic_filters import VectorFilter
73+
result = someNode.nodes.filter(vector_filter=VectorFilter(topk=3, vector_attribute_name="vector")).all()
74+
75+
Where the result will be a list of length topk of tuples having the form (someNode, score).
76+
77+
The :class:`~neomodel.semantic_filters.VectorFilter` can be used in conjunction with the normal filter types.
78+
79+
.. attention::
80+
If you use VectorFilter in conjunction with normal filter types, only nodes that fit the filters will return thus, you may get less than the topk specified.
81+
Furthermore, all node filters **should** work with VectorFilter, relationship filters will also work but WILL NOT return the vector similiarty score alongside the relationship filter, instead the topk nodes and their appropriate relationships will be returned.
82+
83+
RelationshipProperty
84+
^^^^^^^^^^^^^^^^^^^^
85+
Currently neomodel has not implemented an OGM method for querying vector indexes on relationships.
86+
If this is something that you like please submit a github issue requirements highlighting your usage pattern.
87+
88+
Alternatively, whilst this has not been implemented yet you can still leverage `db.cypher_query` with the correct syntax to perform your required query.
89+

neomodel/_version.py

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

neomodel/async_/cardinality.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ class AsyncZeroOrOne(AsyncRelationshipManager):
1515

1616
description = "zero or one relationship"
1717

18+
async def _check_cardinality(self, node: "AsyncStructuredNode") -> None:
19+
if await self.get_len():
20+
raise AttemptedCardinalityViolation(
21+
f"Node already has {self} can't connect more"
22+
)
23+
1824
async def single(self) -> Optional["AsyncStructuredNode"]:
1925
"""
2026
Return the associated node.
@@ -44,10 +50,6 @@ async def connect(
4450
:type: dict
4551
:return: True / rel instance
4652
"""
47-
if await super().get_len():
48-
raise AttemptedCardinalityViolation(
49-
f"Node already has {self} can't connect more"
50-
)
5153
return await super().connect(node, properties)
5254

5355

@@ -96,6 +98,10 @@ class AsyncOne(AsyncRelationshipManager):
9698

9799
description = "one relationship"
98100

101+
async def _check_cardinality(self, node: "AsyncStructuredNode") -> None:
102+
if await self.get_len():
103+
raise AttemptedCardinalityViolation("Node already has one relationship")
104+
99105
async def single(self) -> "AsyncStructuredNode":
100106
"""
101107
Return the associated node.
@@ -139,6 +145,4 @@ async def connect(
139145
"""
140146
if not hasattr(self.source, "element_id") or self.source.element_id is None:
141147
raise ValueError("Node has not been saved cannot connect!")
142-
if await super().get_len():
143-
raise AttemptedCardinalityViolation("Node already has one relationship")
144148
return await super().connect(node, properties)

0 commit comments

Comments
 (0)