From d020ed4db1983745ecc8d56e03ea44c4967a2bc5 Mon Sep 17 00:00:00 2001 From: Adam Schill Collberg Date: Fri, 23 May 2025 15:13:01 +0200 Subject: [PATCH 1/6] Improve `from_dfs` error messages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Florentin Dörre --- python-wrapper/src/neo4j_viz/pandas.py | 26 ++++++++++- python-wrapper/tests/test_pandas.py | 60 ++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 2 deletions(-) diff --git a/python-wrapper/src/neo4j_viz/pandas.py b/python-wrapper/src/neo4j_viz/pandas.py index 694ebf6e..15e29c0e 100644 --- a/python-wrapper/src/neo4j_viz/pandas.py +++ b/python-wrapper/src/neo4j_viz/pandas.py @@ -4,6 +4,7 @@ from typing import Optional, Union from pandas import DataFrame +from pydantic import BaseModel, ValidationError from .node import Node from .relationship import Relationship @@ -12,6 +13,19 @@ DFS_TYPE = Union[DataFrame, Iterable[DataFrame]] +def _parse_validation_error(e: ValidationError, entity_type: type[BaseModel]) -> None: + for err in e.errors(): + loc = err["loc"][0] + if err["type"] == "missing": + raise ValueError( + f"Mandatory {entity_type.__name__.lower()} column '{loc}' is missing. Expected one of {entity_type.model_fields[loc].validation_alias.choices} to be present" # type: ignore + ) + else: + raise ValueError( + f"Error for {entity_type.__name__.lower()} column '{loc}' with provided input '{err['input']}'. Reason: {err['msg']}" + ) + + def _from_dfs( node_dfs: Optional[DFS_TYPE], rel_dfs: DFS_TYPE, @@ -63,7 +77,11 @@ def _parse_nodes(node_dfs: DFS_TYPE, rename_properties: Optional[dict[str, str]] key = rename_properties[key] properties[key] = value - nodes.append(Node(**top_level, properties=properties)) + try: + nodes.append(Node(**top_level, properties=properties)) + except ValidationError as e: + _parse_validation_error(e, Node) + return nodes, has_size @@ -88,7 +106,11 @@ def _parse_relationships(rel_dfs: DFS_TYPE, rename_properties: Optional[dict[str key = rename_properties[key] properties[key] = value - relationships.append(Relationship(**top_level, properties=properties)) + try: + relationships.append(Relationship(**top_level, properties=properties)) + except ValidationError as e: + _parse_validation_error(e, Relationship) + return relationships diff --git a/python-wrapper/tests/test_pandas.py b/python-wrapper/tests/test_pandas.py index 76fb77f6..7da45bf1 100644 --- a/python-wrapper/tests/test_pandas.py +++ b/python-wrapper/tests/test_pandas.py @@ -1,3 +1,4 @@ +import pytest from pandas import DataFrame from pydantic_extra_types.color import Color @@ -130,3 +131,62 @@ def test_from_dfs() -> None: assert VG.relationships[1].source == 1 assert VG.relationships[1].target == 0 assert VG.relationships[1].caption == "REL2" + + +def test_node_errors() -> None: + nodes = DataFrame( + {"caption": ["A", "B"], "size": [1337, 42], "color": "#FF0000", "instrument": ["piano", "guitar"]} + ) + with pytest.raises( + ValueError, + match=r"Mandatory node column 'id' is missing. Expected one of \['id', 'ID', 'id', 'nodeid', 'NODEID', 'nodeid', 'node_id', 'NODE_ID', 'nodeId'\] to be present", + ): + from_dfs(nodes, []) + + nodes = DataFrame( + { + "id": [0, 1], + "caption": ["A", "B"], + "size": ["aaa", 42], + "color": "#FF0000", + "instrument": ["piano", "guitar"], + } + ) + with pytest.raises( + ValueError, + match=r"Error for node column 'size' with provided input 'aaa'. Reason: Input should be a valid integer, unable to parse string as an integer", + ): + from_dfs(nodes, []) + + +def test_rel_errors() -> None: + nodes = DataFrame( + {"id": [0, 1], "caption": ["A", "B"], "size": [1337, 42], "color": "#FF0000", "instrument": ["piano", "guitar"]} + ) + relationships = DataFrame( + { + "target": [1, 0], + "caption": ["REL", "REL2"], + "weight": [1.0, 2.0], + } + ) + with pytest.raises( + ValueError, + match=r"Mandatory relationship column 'source' is missing. Expected one of \['source', 'SOURCE', 'source', 'sourcenodeid', 'SOURCENODEID', 'sourcenodeid', 'source_node_id', 'SOURCE_NODE_ID', 'sourceNodeId', 'from', 'FROM', 'from'\] to be present", + ): + from_dfs(nodes, relationships) + + relationships = DataFrame( + { + "source": [0, 1], + "target": [1, 0], + "caption": ["REL", "REL2"], + "caption_size": [1.0, -300], + "weight": [1.0, 2.0], + } + ) + with pytest.raises( + ValueError, + match=r"Error for relationship column 'caption_size' with provided input '-300.0'. Reason: Input should be greater than 0", + ): + from_dfs(nodes, relationships) From 0c0b05ebc73366e6d781ba6bb64749398a065526 Mon Sep 17 00:00:00 2001 From: Adam Schill Collberg Date: Fri, 23 May 2025 15:48:11 +0200 Subject: [PATCH 2/6] Improve `from_gds` error messages --- python-wrapper/src/neo4j_viz/gds.py | 9 +++++++- python-wrapper/tests/test_gds.py | 33 +++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/python-wrapper/src/neo4j_viz/gds.py b/python-wrapper/src/neo4j_viz/gds.py index 7be4151d..f4b54545 100644 --- a/python-wrapper/src/neo4j_viz/gds.py +++ b/python-wrapper/src/neo4j_viz/gds.py @@ -97,4 +97,11 @@ def from_gds( rel_df = _rel_df(gds, G) rel_df.rename(columns={"sourceNodeId": "source", "targetNodeId": "target"}, inplace=True) - return _from_dfs(node_df, rel_df, node_radius_min_max=node_radius_min_max, rename_properties={"__size": "size"}) + try: + VG = _from_dfs(node_df, rel_df, node_radius_min_max=node_radius_min_max, rename_properties={"__size": "size"}) + except ValueError as e: + if "column" in str(e): + raise ValueError(str(e).replace("column", "property")) + raise e + + return VG diff --git a/python-wrapper/tests/test_gds.py b/python-wrapper/tests/test_gds.py index bad5e4e1..baf170d5 100644 --- a/python-wrapper/tests/test_gds.py +++ b/python-wrapper/tests/test_gds.py @@ -132,3 +132,36 @@ def test_from_gds_mocked(mocker: MockerFixture) -> None: (1, 2, "REL2"), (2, 0, "REL"), ] + + +@pytest.mark.requires_neo4j_and_gds +def test_from_gds_node_errors(gds: Any) -> None: + from neo4j_viz.gds import from_gds + + nodes = pd.DataFrame( + { + "nodeId": [0, 1, 2], + "labels": [["A"], ["C"], ["A", "B"]], + "component": [1, 4, 2], + "size": [-0.1, 0.2, 0.3], + } + ) + rels = pd.DataFrame( + { + "sourceNodeId": [0, 1, 2], + "targetNodeId": [1, 2, 0], + "relationshipType": ["REL", "REL2", "REL"], + } + ) + + with gds.graph.construct("flo", nodes, rels) as G: + with pytest.raises( + ValueError, + match=r"Error for node property 'size' with provided input '-0.1'. Reason: Input should be greater than or equal to 0", + ): + from_gds( + gds, + G, + additional_node_properties=["component", "size"], + node_radius_min_max=None, + ) From 34e8b88c5cf4a90f6deb0ff708b838ab09dd3a82 Mon Sep 17 00:00:00 2001 From: Adam Schill Collberg Date: Fri, 23 May 2025 16:14:33 +0200 Subject: [PATCH 3/6] Improve `from_neo4j` error messages --- python-wrapper/src/neo4j_viz/gds.py | 4 +--- python-wrapper/src/neo4j_viz/neo4j.py | 23 ++++++++++++++++++-- python-wrapper/tests/test_neo4j.py | 31 +++++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 5 deletions(-) diff --git a/python-wrapper/src/neo4j_viz/gds.py b/python-wrapper/src/neo4j_viz/gds.py index f4b54545..f431ec33 100644 --- a/python-wrapper/src/neo4j_viz/gds.py +++ b/python-wrapper/src/neo4j_viz/gds.py @@ -98,10 +98,8 @@ def from_gds( rel_df.rename(columns={"sourceNodeId": "source", "targetNodeId": "target"}, inplace=True) try: - VG = _from_dfs(node_df, rel_df, node_radius_min_max=node_radius_min_max, rename_properties={"__size": "size"}) + return _from_dfs(node_df, rel_df, node_radius_min_max=node_radius_min_max, rename_properties={"__size": "size"}) except ValueError as e: if "column" in str(e): raise ValueError(str(e).replace("column", "property")) raise e - - return VG diff --git a/python-wrapper/src/neo4j_viz/neo4j.py b/python-wrapper/src/neo4j_viz/neo4j.py index 8973acaf..7528f1c6 100644 --- a/python-wrapper/src/neo4j_viz/neo4j.py +++ b/python-wrapper/src/neo4j_viz/neo4j.py @@ -4,12 +4,21 @@ import neo4j.graph from neo4j import Result +from pydantic import BaseModel, ValidationError from neo4j_viz.node import Node from neo4j_viz.relationship import Relationship from neo4j_viz.visualization_graph import VisualizationGraph +def _parse_validation_error(e: ValidationError, entity_type: type[BaseModel]) -> None: + for err in e.errors(): + loc = err["loc"][0] + raise ValueError( + f"Error for {entity_type.__name__.lower()} property '{loc}' with provided input '{err['input']}'. Reason: {err['msg']}" + ) + + def from_neo4j( result: Union[neo4j.graph.Graph, Result], size_property: Optional[str] = None, @@ -102,7 +111,12 @@ def _map_node( properties["__labels"] = properties["labels"] properties["labels"] = labels - return Node(**top_level_fields, properties=properties) + try: + viz_node = Node(**top_level_fields, properties=properties) + except ValidationError as e: + _parse_validation_error(e, Node) + + return viz_node def _map_relationship( @@ -135,4 +149,9 @@ def _map_relationship( properties["__type"] = properties["type"] properties["type"] = rel.type - return Relationship(**top_level_fields, properties=properties) + try: + viz_rel = Relationship(**top_level_fields, properties=properties) + except ValidationError as e: + _parse_validation_error(e, Relationship) + + return viz_rel diff --git a/python-wrapper/tests/test_neo4j.py b/python-wrapper/tests/test_neo4j.py index 7182087c..cf0bbb31 100644 --- a/python-wrapper/tests/test_neo4j.py +++ b/python-wrapper/tests/test_neo4j.py @@ -161,3 +161,34 @@ def test_from_neo4j_graph_full(neo4j_session: Session) -> None: (node_ids[1], node_ids[0], "2015"), (node_ids[0], node_ids[1], "2025"), ] + + +@pytest.mark.requires_neo4j_and_gds +def test_from_neo4j_node_error(neo4j_session: Session) -> None: + neo4j_session.run("MATCH (n:_CI_A|_CI_B) DETACH DELETE n") + neo4j_session.run( + "CREATE (a:_CI_A {name:'Alice', height:20, id:42, _id: 1337, caption: 'hello', caption_size: -5})" + ) + graph = neo4j_session.run("MATCH (a:_CI_A) RETURN a").graph() + + with pytest.raises( + ValueError, + match="Error for node property 'caption_size' with provided input '-5'. Reason: Input should be greater than or equal to 1", + ): + from_neo4j(graph) + + +@pytest.mark.requires_neo4j_and_gds +def test_from_neo4j_rel_error(neo4j_session: Session) -> None: + neo4j_session.run("MATCH (n:_CI_A|_CI_B) DETACH DELETE n") + neo4j_session.run( + "CREATE (a:_CI_A {name:'Alice', height:20, id:42, _id: 1337, caption: 'hello'})-[:KNOWS {year: 2025, id: 41, source: 1, target: 2, caption_align: 'banana'}]->" + "(b:_CI_A:_CI_B {name:'Bob', height:10, id: 84, size: 11, labels: [1,2]})" + ) + graph = neo4j_session.run("MATCH (a:_CI_A|_CI_B)-[r]->(b) RETURN a, b, r ORDER BY a").graph() + + with pytest.raises( + ValueError, + match="Error for relationship property 'caption_align' with provided input 'banana'. Reason: Input should be 'top', 'center' or 'bottom'", + ): + from_neo4j(graph) From 5a73828f2c605cde140863c2f4a97759b3342f40 Mon Sep 17 00:00:00 2001 From: Adam Schill Collberg Date: Fri, 23 May 2025 16:24:12 +0200 Subject: [PATCH 4/6] Improve `from_gql_create` error messages --- python-wrapper/src/neo4j_viz/gql_create.py | 39 ++++++++++++++++------ python-wrapper/tests/test_gql_create.py | 18 ++++++++++ 2 files changed, 46 insertions(+), 11 deletions(-) diff --git a/python-wrapper/src/neo4j_viz/gql_create.py b/python-wrapper/src/neo4j_viz/gql_create.py index e0cca63a..0974ca93 100644 --- a/python-wrapper/src/neo4j_viz/gql_create.py +++ b/python-wrapper/src/neo4j_viz/gql_create.py @@ -2,7 +2,10 @@ import uuid from typing import Any, Optional +from pydantic import ValidationError + from neo4j_viz import Node, Relationship, VisualizationGraph +from neo4j_viz.neo4j import _parse_validation_error def _parse_value(value_str: str) -> Any: @@ -267,7 +270,10 @@ def from_gql_create( anonymous_count += 1 if alias not in alias_to_id: alias_to_id[alias] = str(uuid.uuid4()) - nodes.append(Node(id=alias_to_id[alias], **top_level, properties=props)) + try: + nodes.append(Node(id=alias_to_id[alias], **top_level, properties=props)) + except ValidationError as e: + _parse_validation_error(e, Node) continue @@ -283,7 +289,10 @@ def from_gql_create( anonymous_count += 1 if left_alias not in alias_to_id: alias_to_id[left_alias] = str(uuid.uuid4()) - nodes.append(Node(id=alias_to_id[left_alias], **left_top_level, properties=left_props)) + try: + nodes.append(Node(id=alias_to_id[left_alias], **left_top_level, properties=left_props)) + except ValidationError as e: + _parse_validation_error(e, Node) elif left_alias not in alias_to_id: snippet = _get_snippet(query, query.index(left_node)) raise ValueError(f"Relationship references unknown node alias: '{left_alias}' near: `{snippet}`.") @@ -295,7 +304,10 @@ def from_gql_create( anonymous_count += 1 if right_alias not in alias_to_id: alias_to_id[right_alias] = str(uuid.uuid4()) - nodes.append(Node(id=alias_to_id[right_alias], **right_top_level, properties=right_props)) + try: + nodes.append(Node(id=alias_to_id[right_alias], **right_top_level, properties=right_props)) + except ValidationError as e: + _parse_validation_error(e, Node) elif right_alias not in alias_to_id: snippet = _get_snippet(query, query.index(right_node)) raise ValueError(f"Relationship references unknown node alias: '{right_alias}' near: `{snippet}`.") @@ -313,15 +325,20 @@ def from_gql_create( if "type" in props: props["__type"] = props["type"] props["type"] = rel_type - relationships.append( - Relationship( - id=rel_id, - source=alias_to_id[left_alias], - target=alias_to_id[right_alias], - **top_level, - properties=props, + + try: + relationships.append( + Relationship( + id=rel_id, + source=alias_to_id[left_alias], + target=alias_to_id[right_alias], + **top_level, + properties=props, + ) ) - ) + except ValidationError as e: + _parse_validation_error(e, Relationship) + continue snippet = part[:30] diff --git a/python-wrapper/tests/test_gql_create.py b/python-wrapper/tests/test_gql_create.py index 75b58c04..c959a79e 100644 --- a/python-wrapper/tests/test_gql_create.py +++ b/python-wrapper/tests/test_gql_create.py @@ -217,3 +217,21 @@ def test_no_create_keyword() -> None: query = "(a:User {y:4})" with pytest.raises(ValueError, match=r"Query must begin with 'CREATE' \(case insensitive\)."): from_gql_create(query) + + +def test_illegal_node_x() -> None: + query = "CREATE (a:User {x:'tennis'})" + with pytest.raises( + ValueError, + match="Error for node property 'x' with provided input 'tennis'. Reason: Input should be a valid integer, unable to parse string as an integer", + ): + from_gql_create(query) + + +def test_illegal_rel_color() -> None: + query = "CREATE ()-[:LINK {caption_size: -42}]->()" + with pytest.raises( + ValueError, + match="Error for relationship property 'caption_size' with provided input '-42'. Reason: Input should be greater than 0", + ): + from_gql_create(query) From 89df7371a2bcd219badf6ea0f7b5623e2b027788 Mon Sep 17 00:00:00 2001 From: Adam Schill Collberg Date: Tue, 27 May 2025 10:37:43 +0200 Subject: [PATCH 5/6] Handle errors for renamed properties --- python-wrapper/src/neo4j_viz/gds.py | 8 ++++-- python-wrapper/src/neo4j_viz/gql_create.py | 23 ++++++++++++++--- python-wrapper/src/neo4j_viz/neo4j.py | 30 +++++++++++++++++----- python-wrapper/tests/test_gds.py | 14 ++++++++++ python-wrapper/tests/test_gql_create.py | 11 +++++++- python-wrapper/tests/test_neo4j.py | 9 +++++++ 6 files changed, 82 insertions(+), 13 deletions(-) diff --git a/python-wrapper/src/neo4j_viz/gds.py b/python-wrapper/src/neo4j_viz/gds.py index f431ec33..911d648f 100644 --- a/python-wrapper/src/neo4j_viz/gds.py +++ b/python-wrapper/src/neo4j_viz/gds.py @@ -100,6 +100,10 @@ def from_gds( try: return _from_dfs(node_df, rel_df, node_radius_min_max=node_radius_min_max, rename_properties={"__size": "size"}) except ValueError as e: - if "column" in str(e): - raise ValueError(str(e).replace("column", "property")) + err_msg = str(e) + if "column" in err_msg: + err_msg = err_msg.replace("column", "property") + if ("'size'" in err_msg) and (size_property is not None): + err_msg = err_msg.replace("'size'", f"'{size_property}'") + raise ValueError(err_msg) raise e diff --git a/python-wrapper/src/neo4j_viz/gql_create.py b/python-wrapper/src/neo4j_viz/gql_create.py index 0974ca93..e5c52965 100644 --- a/python-wrapper/src/neo4j_viz/gql_create.py +++ b/python-wrapper/src/neo4j_viz/gql_create.py @@ -2,10 +2,9 @@ import uuid from typing import Any, Optional -from pydantic import ValidationError +from pydantic import BaseModel, ValidationError from neo4j_viz import Node, Relationship, VisualizationGraph -from neo4j_viz.neo4j import _parse_validation_error def _parse_value(value_str: str) -> Any: @@ -255,6 +254,20 @@ def from_gql_create( node_top_level_keys = Node.all_validation_aliases(exempted_fields=["id"]) rel_top_level_keys = Relationship.all_validation_aliases(exempted_fields=["id", "source", "target"]) + def _parse_validation_error(e: ValidationError, entity_type: type[BaseModel]) -> None: + for err in e.errors(): + loc = err["loc"][0] + if (loc == "size") and size_property is not None: + loc = size_property + if loc == "caption": + if (entity_type == Node) and (node_caption is not None): + loc = node_caption + elif (entity_type == Relationship) and (relationship_caption is not None): + loc = relationship_caption + raise ValueError( + f"Error for {entity_type.__name__.lower()} property '{loc}' with provided input '{err['input']}'. Reason: {err['msg']}" + ) + nodes = [] relationships = [] alias_to_id = {} @@ -363,6 +376,10 @@ def from_gql_create( VG = VisualizationGraph(nodes=nodes, relationships=relationships) if (node_radius_min_max is not None) and (size_property is not None): - VG.resize_nodes(node_radius_min_max=node_radius_min_max) + try: + VG.resize_nodes(node_radius_min_max=node_radius_min_max) + except TypeError: + loc = "size" if size_property is None else size_property + raise ValueError(f"Error for node property '{loc}'. Reason: must be a numerical value") return VG diff --git a/python-wrapper/src/neo4j_viz/neo4j.py b/python-wrapper/src/neo4j_viz/neo4j.py index 7528f1c6..d5eeb408 100644 --- a/python-wrapper/src/neo4j_viz/neo4j.py +++ b/python-wrapper/src/neo4j_viz/neo4j.py @@ -59,14 +59,30 @@ def from_neo4j( all_node_field_aliases = Node.all_validation_aliases() all_rel_field_aliases = Relationship.all_validation_aliases() - nodes = [ - _map_node(node, all_node_field_aliases, size_property, caption_property=node_caption) for node in graph.nodes - ] + try: + nodes = [ + _map_node(node, all_node_field_aliases, size_property, caption_property=node_caption) + for node in graph.nodes + ] + except ValueError as e: + err_msg = str(e) + if ("'size'" in err_msg) and (size_property is not None): + err_msg = err_msg.replace("'size'", f"'{size_property}'") + elif ("'caption'" in err_msg) and (node_caption is not None): + err_msg = err_msg.replace("'caption'", f"'{node_caption}'") + raise ValueError(err_msg) + relationships = [] - for rel in graph.relationships: - mapped_rel = _map_relationship(rel, all_rel_field_aliases, caption_property=relationship_caption) - if mapped_rel: - relationships.append(mapped_rel) + try: + for rel in graph.relationships: + mapped_rel = _map_relationship(rel, all_rel_field_aliases, caption_property=relationship_caption) + if mapped_rel: + relationships.append(mapped_rel) + except ValueError as e: + err_msg = str(e) + if ("'caption'" in err_msg) and (relationship_caption is not None): + err_msg = err_msg.replace("'caption'", f"'{relationship_caption}'") + raise ValueError(err_msg) VG = VisualizationGraph(nodes, relationships) diff --git a/python-wrapper/tests/test_gds.py b/python-wrapper/tests/test_gds.py index baf170d5..a5243459 100644 --- a/python-wrapper/tests/test_gds.py +++ b/python-wrapper/tests/test_gds.py @@ -143,6 +143,7 @@ def test_from_gds_node_errors(gds: Any) -> None: "nodeId": [0, 1, 2], "labels": [["A"], ["C"], ["A", "B"]], "component": [1, 4, 2], + "score": [1337, -42, 3.14], "size": [-0.1, 0.2, 0.3], } ) @@ -165,3 +166,16 @@ def test_from_gds_node_errors(gds: Any) -> None: additional_node_properties=["component", "size"], node_radius_min_max=None, ) + + with gds.graph.construct("flo", nodes, rels) as G: + with pytest.raises( + ValueError, + match=r"Error for node property 'score' with provided input '-42.0'. Reason: Input should be greater than or equal to 0", + ): + from_gds( + gds, + G, + size_property="score", + additional_node_properties=["component", "size"], + node_radius_min_max=None, + ) diff --git a/python-wrapper/tests/test_gql_create.py b/python-wrapper/tests/test_gql_create.py index c959a79e..e18f74d3 100644 --- a/python-wrapper/tests/test_gql_create.py +++ b/python-wrapper/tests/test_gql_create.py @@ -228,7 +228,16 @@ def test_illegal_node_x() -> None: from_gql_create(query) -def test_illegal_rel_color() -> None: +def test_illegal_node_size() -> None: + query = "CREATE (a:User {hello: 'tennis'})" + with pytest.raises( + ValueError, + match="Error for node property 'hello'. Reason: must be a numerical value", + ): + from_gql_create(query, size_property="hello") + + +def test_illegal_rel_caption_size() -> None: query = "CREATE ()-[:LINK {caption_size: -42}]->()" with pytest.raises( ValueError, diff --git a/python-wrapper/tests/test_neo4j.py b/python-wrapper/tests/test_neo4j.py index cf0bbb31..779e36c9 100644 --- a/python-wrapper/tests/test_neo4j.py +++ b/python-wrapper/tests/test_neo4j.py @@ -177,6 +177,15 @@ def test_from_neo4j_node_error(neo4j_session: Session) -> None: ): from_neo4j(graph) + neo4j_session.run("MATCH (n:_CI_A|_CI_B) DETACH DELETE n") + neo4j_session.run("CREATE (a:_CI_A {name:'Alice', height:20, id:42, _id: 1337, hello: -5})") + graph = neo4j_session.run("MATCH (a:_CI_A) RETURN a").graph() + with pytest.raises( + ValueError, + match="Error for node property 'hello' with provided input '-5'. Reason: Input should be greater than or equal to 0", + ): + from_neo4j(graph, size_property="hello") + @pytest.mark.requires_neo4j_and_gds def test_from_neo4j_rel_error(neo4j_session: Session) -> None: From f5506dc652bc277057e75f0a89aba04d2f291a24 Mon Sep 17 00:00:00 2001 From: Adam Schill Collberg Date: Tue, 27 May 2025 10:41:21 +0200 Subject: [PATCH 6/6] Add changelog entry --- changelog.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/changelog.md b/changelog.md index e35e198c..46cd6c91 100644 --- a/changelog.md +++ b/changelog.md @@ -13,5 +13,7 @@ ## Improvements +* Improved error messages when constructing `VisualizationGraph`s using `from_dfs`, `from_neo4j`, `from_gds` and `from_gql_create` methods + ## Other changes