From 9114c2662ffff0a5fafe53b12d05bcf3c8d4ac4c Mon Sep 17 00:00:00 2001 From: Marius Conjeaud Date: Wed, 14 Aug 2024 15:39:56 +0200 Subject: [PATCH 1/4] FIx tests and doc for vector index --- doc/source/schema_management.rst | 5 +++- test/async_/test_label_install.py | 36 +++++++++++++++------------- test/sync_/test_label_install.py | 39 +++++++++++++++++-------------- 3 files changed, 45 insertions(+), 35 deletions(-) diff --git a/doc/source/schema_management.rst b/doc/source/schema_management.rst index 61ad94a3..9d0dce0a 100644 --- a/doc/source/schema_management.rst +++ b/doc/source/schema_management.rst @@ -72,6 +72,9 @@ Full example: :: name = StringProperty( index=True, fulltext_index=FulltextIndex(analyzer='english', eventually_consistent=True) + ) + name_embedding = ArrayProperty( + FloatProperty(), vector_index=VectorIndex(dimensions=512, similarity_function='euclidean') ) @@ -83,7 +86,7 @@ The following constraints are supported: - ``unique_index=True``: This will create a uniqueness constraint on the property. Available for both nodes and relationships (Neo4j version 5.7 or higher). .. note:: - The uniquess constraint of Neo4j is not supported as such, but using ``required=True`` on a property serves the same purpose. + The uniqueness constraint of Neo4j is not supported as such, but using ``required=True`` on a property serves the same purpose. Extracting the schema from a database diff --git a/test/async_/test_label_install.py b/test/async_/test_label_install.py index 832578f5..03d6e87b 100644 --- a/test/async_/test_label_install.py +++ b/test/async_/test_label_install.py @@ -6,9 +6,11 @@ from neo4j.exceptions import ClientError from neomodel import ( + ArrayProperty, AsyncRelationshipTo, AsyncStructuredNode, AsyncStructuredRel, + FloatProperty, FulltextIndex, StringProperty, UniqueIdProperty, @@ -317,16 +319,17 @@ async def test_vector_index(): pytest.skip("Not supported before 5.15") class VectorIndexNode(AsyncStructuredNode): - name = StringProperty( - vector_index=VectorIndex(dimensions=256, similarity_function="euclidean") + embedding = ArrayProperty( + FloatProperty(), + vector_index=VectorIndex(dimensions=256, similarity_function="euclidean"), ) await adb.install_labels(VectorIndexNode) indexes = await adb.list_indexes() index_names = [index["name"] for index in indexes] - assert "vector_index_VectorIndexNode_name" in index_names + assert "vector_index_VectorIndexNode_embedding" in index_names - await adb.cypher_query("DROP INDEX vector_index_VectorIndexNode_name") + await adb.cypher_query("DROP INDEX vector_index_VectorIndexNode_embedding") @mark_async_test @@ -338,11 +341,11 @@ async def test_vector_index_conflict(): with patch("sys.stdout", new=stream): await adb.cypher_query( - "CREATE VECTOR INDEX FOR (n:VectorIndexNodeConflict) ON n.name OPTIONS{indexConfig:{`vector.similarity_function`:'cosine', `vector.dimensions`:1536}}" + "CREATE VECTOR INDEX FOR (n:VectorIndexNodeConflict) ON n.embedding OPTIONS{indexConfig:{`vector.similarity_function`:'cosine', `vector.dimensions`:1536}}" ) class VectorIndexNodeConflict(AsyncStructuredNode): - name = StringProperty(vector_index=VectorIndex()) + embedding = ArrayProperty(FloatProperty(), vector_index=VectorIndex()) await adb.install_labels(VectorIndexNodeConflict, quiet=False) @@ -361,7 +364,7 @@ async def test_vector_index_not_supported(): ): class VectorIndexNodeOld(AsyncStructuredNode): - name = StringProperty(vector_index=VectorIndex()) + embedding = ArrayProperty(FloatProperty(), vector_index=VectorIndex()) await adb.install_labels(VectorIndexNodeOld) @@ -372,8 +375,9 @@ async def test_rel_vector_index(): pytest.skip("Not supported before 5.18") class VectorIndexRel(AsyncStructuredRel): - name = StringProperty( - vector_index=VectorIndex(dimensions=256, similarity_function="euclidean") + embedding = ArrayProperty( + FloatProperty(), + vector_index=VectorIndex(dimensions=256, similarity_function="euclidean"), ) class VectorIndexRelNode(AsyncStructuredNode): @@ -384,9 +388,9 @@ class VectorIndexRelNode(AsyncStructuredNode): await adb.install_labels(VectorIndexRelNode) indexes = await adb.list_indexes() index_names = [index["name"] for index in indexes] - assert "vector_index_VECTOR_INDEX_REL_name" in index_names + assert "vector_index_VECTOR_INDEX_REL_embedding" in index_names - await adb.cypher_query("DROP INDEX vector_index_VECTOR_INDEX_REL_name") + await adb.cypher_query("DROP INDEX vector_index_VECTOR_INDEX_REL_embedding") @mark_async_test @@ -398,11 +402,11 @@ async def test_rel_vector_index_conflict(): with patch("sys.stdout", new=stream): await adb.cypher_query( - "CREATE VECTOR INDEX FOR ()-[r:VECTOR_INDEX_REL_CONFLICT]-() ON r.name OPTIONS{indexConfig:{`vector.similarity_function`:'cosine', `vector.dimensions`:1536}}" + "CREATE VECTOR INDEX FOR ()-[r:VECTOR_INDEX_REL_CONFLICT]-() ON r.embedding OPTIONS{indexConfig:{`vector.similarity_function`:'cosine', `vector.dimensions`:1536}}" ) class VectorIndexRelConflict(AsyncStructuredRel): - name = StringProperty(vector_index=VectorIndex()) + embedding = ArrayProperty(FloatProperty(), vector_index=VectorIndex()) class VectorIndexRelConflictNode(AsyncStructuredNode): has_rel = AsyncRelationshipTo( @@ -428,7 +432,7 @@ async def test_rel_vector_index_not_supported(): ): class VectorIndexRelOld(AsyncStructuredRel): - name = StringProperty(vector_index=VectorIndex()) + embedding = ArrayProperty(FloatProperty(), vector_index=VectorIndex()) class VectorIndexRelOldNode(AsyncStructuredNode): has_rel = AsyncRelationshipTo( @@ -522,7 +526,7 @@ class UnauthorizedFulltextNode(AsyncStructuredNode): with await adb.impersonate(unauthorized_user): class UnauthorizedVectorNode(AsyncStructuredNode): - name = StringProperty(vector_index=VectorIndex()) + embedding = ArrayProperty(FloatProperty(), vector_index=VectorIndex()) await adb.install_labels(UnauthorizedVectorNode) @@ -572,7 +576,7 @@ class UnauthorizedFulltextRelNode(AsyncStructuredNode): with await adb.impersonate(unauthorized_user): class UnauthorizedVectorRel(AsyncStructuredRel): - name = StringProperty(vector_index=VectorIndex()) + embedding = ArrayProperty(FloatProperty(), vector_index=VectorIndex()) class UnauthorizedVectorRelNode(AsyncStructuredNode): has_rel = AsyncRelationshipTo( diff --git a/test/sync_/test_label_install.py b/test/sync_/test_label_install.py index 8b3c5b3a..ef18324b 100644 --- a/test/sync_/test_label_install.py +++ b/test/sync_/test_label_install.py @@ -6,6 +6,8 @@ from neo4j.exceptions import ClientError from neomodel import ( + ArrayProperty, + FloatProperty, FulltextIndex, RelationshipTo, StringProperty, @@ -26,8 +28,7 @@ class NodeWithConstraint(StructuredNode): name = StringProperty(unique_index=True) -class NodeWithRelationship(StructuredNode): - ... +class NodeWithRelationship(StructuredNode): ... class IndexedRelationship(StructuredRel): @@ -317,16 +318,17 @@ def test_vector_index(): pytest.skip("Not supported before 5.15") class VectorIndexNode(StructuredNode): - name = StringProperty( - vector_index=VectorIndex(dimensions=256, similarity_function="euclidean") + embedding = ArrayProperty( + FloatProperty(), + vector_index=VectorIndex(dimensions=256, similarity_function="euclidean"), ) db.install_labels(VectorIndexNode) indexes = db.list_indexes() index_names = [index["name"] for index in indexes] - assert "vector_index_VectorIndexNode_name" in index_names + assert "vector_index_VectorIndexNode_embedding" in index_names - db.cypher_query("DROP INDEX vector_index_VectorIndexNode_name") + db.cypher_query("DROP INDEX vector_index_VectorIndexNode_embedding") @mark_sync_test @@ -338,11 +340,11 @@ def test_vector_index_conflict(): with patch("sys.stdout", new=stream): db.cypher_query( - "CREATE VECTOR INDEX FOR (n:VectorIndexNodeConflict) ON n.name OPTIONS{indexConfig:{`vector.similarity_function`:'cosine', `vector.dimensions`:1536}}" + "CREATE VECTOR INDEX FOR (n:VectorIndexNodeConflict) ON n.embedding OPTIONS{indexConfig:{`vector.similarity_function`:'cosine', `vector.dimensions`:1536}}" ) class VectorIndexNodeConflict(StructuredNode): - name = StringProperty(vector_index=VectorIndex()) + embedding = ArrayProperty(FloatProperty(), vector_index=VectorIndex()) db.install_labels(VectorIndexNodeConflict, quiet=False) @@ -361,7 +363,7 @@ def test_vector_index_not_supported(): ): class VectorIndexNodeOld(StructuredNode): - name = StringProperty(vector_index=VectorIndex()) + embedding = ArrayProperty(FloatProperty(), vector_index=VectorIndex()) db.install_labels(VectorIndexNodeOld) @@ -372,8 +374,9 @@ def test_rel_vector_index(): pytest.skip("Not supported before 5.18") class VectorIndexRel(StructuredRel): - name = StringProperty( - vector_index=VectorIndex(dimensions=256, similarity_function="euclidean") + embedding = ArrayProperty( + FloatProperty(), + vector_index=VectorIndex(dimensions=256, similarity_function="euclidean"), ) class VectorIndexRelNode(StructuredNode): @@ -384,9 +387,9 @@ class VectorIndexRelNode(StructuredNode): db.install_labels(VectorIndexRelNode) indexes = db.list_indexes() index_names = [index["name"] for index in indexes] - assert "vector_index_VECTOR_INDEX_REL_name" in index_names + assert "vector_index_VECTOR_INDEX_REL_embedding" in index_names - db.cypher_query("DROP INDEX vector_index_VECTOR_INDEX_REL_name") + db.cypher_query("DROP INDEX vector_index_VECTOR_INDEX_REL_embedding") @mark_sync_test @@ -398,11 +401,11 @@ def test_rel_vector_index_conflict(): with patch("sys.stdout", new=stream): db.cypher_query( - "CREATE VECTOR INDEX FOR ()-[r:VECTOR_INDEX_REL_CONFLICT]-() ON r.name OPTIONS{indexConfig:{`vector.similarity_function`:'cosine', `vector.dimensions`:1536}}" + "CREATE VECTOR INDEX FOR ()-[r:VECTOR_INDEX_REL_CONFLICT]-() ON r.embedding OPTIONS{indexConfig:{`vector.similarity_function`:'cosine', `vector.dimensions`:1536}}" ) class VectorIndexRelConflict(StructuredRel): - name = StringProperty(vector_index=VectorIndex()) + embedding = ArrayProperty(FloatProperty(), vector_index=VectorIndex()) class VectorIndexRelConflictNode(StructuredNode): has_rel = RelationshipTo( @@ -428,7 +431,7 @@ def test_rel_vector_index_not_supported(): ): class VectorIndexRelOld(StructuredRel): - name = StringProperty(vector_index=VectorIndex()) + embedding = ArrayProperty(FloatProperty(), vector_index=VectorIndex()) class VectorIndexRelOldNode(StructuredNode): has_rel = RelationshipTo( @@ -520,7 +523,7 @@ class UnauthorizedFulltextNode(StructuredNode): with db.impersonate(unauthorized_user): class UnauthorizedVectorNode(StructuredNode): - name = StringProperty(vector_index=VectorIndex()) + embedding = ArrayProperty(FloatProperty(), vector_index=VectorIndex()) db.install_labels(UnauthorizedVectorNode) @@ -570,7 +573,7 @@ class UnauthorizedFulltextRelNode(StructuredNode): with db.impersonate(unauthorized_user): class UnauthorizedVectorRel(StructuredRel): - name = StringProperty(vector_index=VectorIndex()) + embedding = ArrayProperty(FloatProperty(), vector_index=VectorIndex()) class UnauthorizedVectorRelNode(StructuredNode): has_rel = RelationshipTo( From d78da5e79044438d231f3e69b626019a5eba42d7 Mon Sep 17 00:00:00 2001 From: Marius Conjeaud Date: Wed, 14 Aug 2024 16:24:30 +0200 Subject: [PATCH 2/4] Fix test --- test/async_/test_match_api.py | 5 +++-- test/sync_/test_match_api.py | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/test/async_/test_match_api.py b/test/async_/test_match_api.py index e3195448..4f549dd5 100644 --- a/test/async_/test_match_api.py +++ b/test/async_/test_match_api.py @@ -173,8 +173,9 @@ async def test_double_traverse(): results = [node async for node in qb._execute()] assert len(results) == 2 - assert results[0].name == "Decafe" - assert results[1].name == "Nescafe plus" + names = [n.name for n in results] + assert "Decafe" in names + assert "Nescafe plus" in names @mark_async_test diff --git a/test/sync_/test_match_api.py b/test/sync_/test_match_api.py index 170a7363..d9c90bb9 100644 --- a/test/sync_/test_match_api.py +++ b/test/sync_/test_match_api.py @@ -164,8 +164,9 @@ def test_double_traverse(): results = [node for node in qb._execute()] assert len(results) == 2 - assert results[0].name == "Decafe" - assert results[1].name == "Nescafe plus" + names = [n.name for n in results] + assert "Decafe" in names + assert "Nescafe plus" in names @mark_sync_test From 02e96190568aa0207534485d8cd7d9bfa0fc5ebe Mon Sep 17 00:00:00 2001 From: Marius Conjeaud Date: Wed, 14 Aug 2024 16:38:58 +0200 Subject: [PATCH 3/4] Prepare rc branch --- Changelog | 3 +++ doc/source/configuration.rst | 2 +- neomodel/_version.py | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Changelog b/Changelog index fe655005..b65b90a7 100644 --- a/Changelog +++ b/Changelog @@ -1,3 +1,6 @@ +Version 5.3.3 2024-09 +* Fixes vector index doc and test + Version 5.3.2 2024-06 * Add support for Vector and Fulltext indexes creation * Add DateTimeNeo4jFormatProperty for Neo4j native datetime format diff --git a/doc/source/configuration.rst b/doc/source/configuration.rst index e8c0a38b..e5d5d5d8 100644 --- a/doc/source/configuration.rst +++ b/doc/source/configuration.rst @@ -32,7 +32,7 @@ Adjust driver configuration - these options are only available for this connecti config.MAX_TRANSACTION_RETRY_TIME = 30.0 # default config.RESOLVER = None # default config.TRUST = neo4j.TRUST_SYSTEM_CA_SIGNED_CERTIFICATES # default - config.USER_AGENT = neomodel/v5.3.2 # default + config.USER_AGENT = neomodel/v5.3.3 # default Setting the database name, if different from the default one:: diff --git a/neomodel/_version.py b/neomodel/_version.py index 07f0e9e2..d2f4a6f4 100644 --- a/neomodel/_version.py +++ b/neomodel/_version.py @@ -1 +1 @@ -__version__ = "5.3.2" +__version__ = "5.3.3" From fcfd45db480f4f2a1204a5df805dd3e9f906b468 Mon Sep 17 00:00:00 2001 From: Christoph Brosch Date: Tue, 3 Sep 2024 09:24:19 +0200 Subject: [PATCH 4/4] Update getting_started.rst Fixed wrongly typed out argument in example command --- doc/source/getting_started.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/getting_started.rst b/doc/source/getting_started.rst index 2203b8a3..6e8a5aa0 100644 --- a/doc/source/getting_started.rst +++ b/doc/source/getting_started.rst @@ -79,7 +79,7 @@ Database Inspection - Requires APOC =================================== You can inspect an existing Neo4j database to generate a neomodel definition file using the ``inspect`` command:: - $ neomodel_inspect_database -db bolt://neo4j_username:neo4j_password@localhost:7687 --write-to yourapp/models.py + $ neomodel_inspect_database --db bolt://neo4j_username:neo4j_password@localhost:7687 --write-to yourapp/models.py This will generate a file called ``models.py`` in the ``yourapp`` directory. This file can be used as a starting point, and will contain the necessary module imports, as well as class definition for nodes and, if relevant, relationships.