From 7f73e444faa8ca145bbb6f9d08c6f220461cda60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florentin=20D=C3=B6rre?= Date: Fri, 12 Sep 2025 12:56:08 +0200 Subject: [PATCH 1/6] Fix default behaviour for size and caption in from_neo4j --- changelog.md | 2 + python-wrapper/src/neo4j_viz/neo4j.py | 4 +- python-wrapper/tests/test_neo4j.py | 57 +++++++++++++++++++++++++++ 3 files changed, 61 insertions(+), 2 deletions(-) diff --git a/changelog.md b/changelog.md index 41a53390..98dea18f 100644 --- a/changelog.md +++ b/changelog.md @@ -9,6 +9,8 @@ ## Bug fixes +* fixed a bug in `from_neo4j`, where by default the node size would always be set if a `size` property was set on the node. + ## Improvements diff --git a/python-wrapper/src/neo4j_viz/neo4j.py b/python-wrapper/src/neo4j_viz/neo4j.py index 3d789ad5..5b202b87 100644 --- a/python-wrapper/src/neo4j_viz/neo4j.py +++ b/python-wrapper/src/neo4j_viz/neo4j.py @@ -77,8 +77,8 @@ def from_neo4j( else: raise ValueError(f"Invalid input type `{type(data)}`. Expected `neo4j.Graph`, `neo4j.Result` or `neo4j.Driver`") - all_node_field_aliases = Node.all_validation_aliases() - all_rel_field_aliases = Relationship.all_validation_aliases() + all_node_field_aliases = Node.all_validation_aliases(exempted_fields=["size", "caption"]) + all_rel_field_aliases = Relationship.all_validation_aliases(exempted_fields=["caption"]) try: nodes = [ diff --git a/python-wrapper/tests/test_neo4j.py b/python-wrapper/tests/test_neo4j.py index d46be658..8b288668 100644 --- a/python-wrapper/tests/test_neo4j.py +++ b/python-wrapper/tests/test_neo4j.py @@ -66,6 +66,63 @@ def test_from_neo4j_graph_basic(neo4j_session: Session) -> None: ] +@pytest.mark.requires_neo4j_and_gds +def test_from_neo4j_graph_default_size(neo4j_session: Session) -> None: + # set a non parsable size property, by default it should not be picked up + neo4j_session.run("MATCH (n) SET n.size = 'banana' SET n.real_caption = 'my_caption' SET n.real_size = 4") + + graph = neo4j_session.run("MATCH (a:_CI_A|_CI_B)-[r]->(b) RETURN a, b, r ORDER BY a").graph() + + VG = from_neo4j(graph, size_property="real_size", node_caption="real_caption", node_radius_min_max=None) + + sorted_nodes: list[neo4j.graph.Node] = sorted(graph.nodes, key=lambda x: dict(x.items())["name"]) + node_ids: list[str] = [node.element_id for node in sorted_nodes] + + expected_nodes = [ + Node( + id=node_ids[0], + caption="my_caption", + size=4, + properties=dict( + labels=["_CI_A"], + name="Alice", + size="banana", + real_size=4, + real_caption="my_caption", + height=20, + id=42, + _id=1337, + caption="hello", + ), + ), + Node( + id=node_ids[1], + caption="my_caption", + size=4, + properties=dict( + labels=["_CI_A", "_CI_B"], + name="Bob", + size="banana", + real_size=4, + real_caption="my_caption", + height=10, + id=84, + __labels=[1, 2], + ), + ), + ] + + assert len(VG.nodes) == 2 + assert sorted(VG.nodes, key=lambda x: x.properties["name"]) == expected_nodes + + assert len(VG.relationships) == 2 + vg_rels = sorted([(e.source, e.target, e.caption) for e in VG.relationships], key=lambda x: x[2] if x[2] else "foo") + assert vg_rels == [ + (node_ids[0], node_ids[1], "KNOWS"), + (node_ids[1], node_ids[0], "RELATED"), + ] + + @pytest.mark.requires_neo4j_and_gds def test_from_neo4j_result(neo4j_session: Session) -> None: result = neo4j_session.run("MATCH (a:_CI_A|_CI_B)-[r]->(b) RETURN a, b, r ORDER BY a") From f3952d840d300c1737c576f5c95b6a3d12b39718 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florentin=20D=C3=B6rre?= Date: Fri, 26 Sep 2025 15:14:52 +0200 Subject: [PATCH 2/6] marimo tutorial intro --- changelog.md | 8 ++-- python-wrapper/tests/test_neo4j.py | 77 +++++++++++------------------- 2 files changed, 32 insertions(+), 53 deletions(-) diff --git a/changelog.md b/changelog.md index 98dea18f..ba5bac2a 100644 --- a/changelog.md +++ b/changelog.md @@ -1,18 +1,16 @@ # Changes in 0.5.1 - ## Breaking changes +- Do not automatically derive size and caption for `from_neo4j`. Use the `size_property` and `node_caption` parameters to configure them. ## New features - ## Bug fixes -* fixed a bug in `from_neo4j`, where by default the node size would always be set if a `size` property was set on the node. - +- fixed a bug in `from_neo4j`, where the node size would always be set to the `size` property. +- fixed a bug in `from_neo4j`, where the node caption would always be set to the `caption` property. ## Improvements - ## Other changes diff --git a/python-wrapper/tests/test_neo4j.py b/python-wrapper/tests/test_neo4j.py index 8b288668..313e9a6d 100644 --- a/python-wrapper/tests/test_neo4j.py +++ b/python-wrapper/tests/test_neo4j.py @@ -12,8 +12,11 @@ @pytest.fixture(scope="class", autouse=True) def graph_setup(neo4j_session: Session) -> Generator[None, None, None]: 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}]->" - "(b:_CI_A:_CI_B {name:'Bob', height:10, id: 84, size: 11, labels: [1,2]}), (b)-[:RELATED {year: 2015, _type: 'A', caption:'hej'}]->(a)" + "CREATE " + " (a:_CI_A {name:'Alice', height:20, id:42, _id: 1337, caption: 'hello'})" + " ,(b:_CI_A:_CI_B {name:'Bob', height:10, id: 84, size: 11, labels: [1,2]})" + " ,(a)-[:KNOWS {year: 2025, id: 41, source: 1, target: 2}]->(b)" + " ,(b)-[:RELATED {year: 2015, _type: 'A', caption:'hej'}]->(a)" ) yield neo4j_session.run("MATCH (n:_CI_A|_CI_B) DETACH DELETE n") @@ -67,60 +70,38 @@ def test_from_neo4j_graph_basic(neo4j_session: Session) -> None: @pytest.mark.requires_neo4j_and_gds -def test_from_neo4j_graph_default_size(neo4j_session: Session) -> None: +def test_from_neo4j_graph_size_property(neo4j_session: Session) -> None: # set a non parsable size property, by default it should not be picked up - neo4j_session.run("MATCH (n) SET n.size = 'banana' SET n.real_caption = 'my_caption' SET n.real_size = 4") + neo4j_session.run("MATCH (n) SET n.size = 'banana'") graph = neo4j_session.run("MATCH (a:_CI_A|_CI_B)-[r]->(b) RETURN a, b, r ORDER BY a").graph() - VG = from_neo4j(graph, size_property="real_size", node_caption="real_caption", node_radius_min_max=None) + VG = from_neo4j(graph, size_property="height", node_radius_min_max=None) - sorted_nodes: list[neo4j.graph.Node] = sorted(graph.nodes, key=lambda x: dict(x.items())["name"]) - node_ids: list[str] = [node.element_id for node in sorted_nodes] + assert {n.properties["name"]: n.size for n in VG.nodes} == {"Alice": 20, "Bob": 10} - expected_nodes = [ - Node( - id=node_ids[0], - caption="my_caption", - size=4, - properties=dict( - labels=["_CI_A"], - name="Alice", - size="banana", - real_size=4, - real_caption="my_caption", - height=20, - id=42, - _id=1337, - caption="hello", - ), - ), - Node( - id=node_ids[1], - caption="my_caption", - size=4, - properties=dict( - labels=["_CI_A", "_CI_B"], - name="Bob", - size="banana", - real_size=4, - real_caption="my_caption", - height=10, - id=84, - __labels=[1, 2], - ), - ), - ] + VG = from_neo4j(graph, size_property=None, node_radius_min_max=None) - assert len(VG.nodes) == 2 - assert sorted(VG.nodes, key=lambda x: x.properties["name"]) == expected_nodes + assert {n.properties["name"]: n.size for n in VG.nodes} == {"Alice": None, "Bob": None} - assert len(VG.relationships) == 2 - vg_rels = sorted([(e.source, e.target, e.caption) for e in VG.relationships], key=lambda x: x[2] if x[2] else "foo") - assert vg_rels == [ - (node_ids[0], node_ids[1], "KNOWS"), - (node_ids[1], node_ids[0], "RELATED"), - ] + +@pytest.mark.requires_neo4j_and_gds +def test_from_neo4j_graph_default_caption(neo4j_session: Session) -> None: + neo4j_session.run("MATCH (n) SET n.caption = 'my_caption' SET n.other_caption = 'other_caption'") + + graph = neo4j_session.run("MATCH (a:_CI_A|_CI_B)-[r]->(b) RETURN a, b, r ORDER BY a").graph() + + VG = from_neo4j(graph, node_caption=None, node_radius_min_max=None) + + assert [n.caption for n in VG.nodes] == [None, None] + + VG = from_neo4j(graph, node_caption="other_caption", node_radius_min_max=None) + + assert [n.caption for n in VG.nodes] == ["other_caption", "other_caption"] + + VG = from_neo4j(graph, relationship_caption="year") + + assert {e.properties["type"]: e.caption for e in VG.relationships} == {"KNOWS": "2025", "RELATED": "2015"} @pytest.mark.requires_neo4j_and_gds From a3a2dafb42abde45a622393acbbae672b753bbee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florentin=20D=C3=B6rre?= Date: Fri, 26 Sep 2025 16:09:50 +0200 Subject: [PATCH 3/6] Apply default behaviour also to from_gql_create --- changelog.md | 4 ++- python-wrapper/src/neo4j_viz/gql_create.py | 18 +++++------ python-wrapper/src/neo4j_viz/node.py | 1 + python-wrapper/src/neo4j_viz/relationship.py | 1 + python-wrapper/tests/test_gql_create.py | 16 ++++----- python-wrapper/tests/test_neo4j.py | 7 ++-- python-wrapper/tests/test_sizes.py | 34 +++++++++++++++++++- 7 files changed, 59 insertions(+), 22 deletions(-) diff --git a/changelog.md b/changelog.md index ba5bac2a..daf3399e 100644 --- a/changelog.md +++ b/changelog.md @@ -2,7 +2,7 @@ ## Breaking changes -- Do not automatically derive size and caption for `from_neo4j`. Use the `size_property` and `node_caption` parameters to configure them. +- Do not automatically derive size and caption for `from_neo4j` and `from_gql_create`. Use the `size_property` and `node_caption` parameters to explicitly configure them. ## New features @@ -13,4 +13,6 @@ ## Improvements +- Validate fields of a node and relationship not only at construction but also on assignment. + ## Other changes diff --git a/python-wrapper/src/neo4j_viz/gql_create.py b/python-wrapper/src/neo4j_viz/gql_create.py index e5c52965..4d7d6845 100644 --- a/python-wrapper/src/neo4j_viz/gql_create.py +++ b/python-wrapper/src/neo4j_viz/gql_create.py @@ -251,8 +251,8 @@ def from_gql_create( node_pattern = re.compile(r"^\(([^)]*)\)$") rel_pattern = re.compile(r"^\(([^)]*)\)-\s*\[\s*:(\w+)\s*(\{[^}]*\})?\s*\]->\(([^)]*)\)$") - node_top_level_keys = Node.all_validation_aliases(exempted_fields=["id"]) - rel_top_level_keys = Relationship.all_validation_aliases(exempted_fields=["id", "source", "target"]) + node_top_level_keys = Node.all_validation_aliases(exempted_fields=["id", "size", "caption"]) + rel_top_level_keys = Relationship.all_validation_aliases(exempted_fields=["id", "source", "target", "caption"]) def _parse_validation_error(e: ValidationError, entity_type: type[BaseModel]) -> None: for err in e.errors(): @@ -358,8 +358,11 @@ def _parse_validation_error(e: ValidationError, entity_type: type[BaseModel]) -> raise ValueError(f"Invalid element in CREATE near: `{snippet}`.") if size_property is not None: - for node in nodes: - node.size = node.properties.get(size_property) + try: + for node in nodes: + node.size = node.properties.get(size_property) + except ValidationError as e: + _parse_validation_error(e, Node) if node_caption is not None: for node in nodes: if node_caption == "labels": @@ -376,10 +379,7 @@ def _parse_validation_error(e: ValidationError, entity_type: type[BaseModel]) -> VG = VisualizationGraph(nodes=nodes, relationships=relationships) if (node_radius_min_max is not None) and (size_property is not None): - 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") + VG.resize_nodes(node_radius_min_max=node_radius_min_max) + return VG diff --git a/python-wrapper/src/neo4j_viz/node.py b/python-wrapper/src/neo4j_viz/node.py index 15e73191..03dbc29c 100644 --- a/python-wrapper/src/neo4j_viz/node.py +++ b/python-wrapper/src/neo4j_viz/node.py @@ -30,6 +30,7 @@ class Node( validation_alias=create_aliases, serialization_alias=lambda field_name: to_camel(field_name), ), + validate_assignment=True, ): """ A node in a graph to visualize. diff --git a/python-wrapper/src/neo4j_viz/relationship.py b/python-wrapper/src/neo4j_viz/relationship.py index 5efe845f..f72fb66e 100644 --- a/python-wrapper/src/neo4j_viz/relationship.py +++ b/python-wrapper/src/neo4j_viz/relationship.py @@ -30,6 +30,7 @@ class Relationship( validation_alias=create_aliases, serialization_alias=lambda field_name: to_camel(field_name), ), + validate_assignment=True, ): """ A relationship in a graph to visualize. diff --git a/python-wrapper/tests/test_gql_create.py b/python-wrapper/tests/test_gql_create.py index e18f74d3..67923198 100644 --- a/python-wrapper/tests/test_gql_create.py +++ b/python-wrapper/tests/test_gql_create.py @@ -30,8 +30,8 @@ def test_from_gql_create_syntax() -> None: "properties": {"name": "Alice", "age": 23, "labels": ["User"], "__labels": ["Happy"], "id": 42}, }, { - "top_level": {"caption": "Bridget"}, - "properties": {"name": "Bridget", "age": 34, "labels": ["User", "person"]}, + "top_level": {}, + "properties": {"name": "Bridget", "caption": "Bridget", "age": 34, "labels": ["User", "person"]}, }, { "top_level": {}, @@ -70,8 +70,8 @@ def test_from_gql_create_syntax() -> None: { "source_idx": 4, "target_idx": 7, - "top_level": {"caption": "Balloon"}, - "properties": {"weight": -2, "type": "OTHER_LINK", "__type": 1, "source": 1337}, + "top_level": {}, + "properties": {"weight": -2, "caption": "Balloon", "type": "OTHER_LINK", "__type": 1, "source": 1337}, }, {"source_idx": 9, "target_idx": 10, "top_level": {}, "properties": {"type": "LINK"}}, ] @@ -102,7 +102,7 @@ def test_from_gql_create_captions() -> None: }, { "top_level": {"caption": "User:person"}, - "properties": {"name": "Bridget", "age": 34, "labels": ["User", "person"]}, + "properties": {"name": "Bridget", "caption": "Bridget", "age": 34, "labels": ["User", "person"]}, }, ] @@ -148,8 +148,8 @@ def test_from_gql_create_sizes() -> None: "properties": {"name": "Alice", "age": 23, "labels": ["User"]}, }, { - "top_level": {"caption": "Bridget", "size": 60.0}, - "properties": {"name": "Bridget", "age": 34, "labels": ["User", "person"]}, + "top_level": {"size": 60.0}, + "properties": {"name": "Bridget", "caption": "Bridget", "age": 34, "labels": ["User", "person"]}, }, ] @@ -232,7 +232,7 @@ 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", + match="Error for node property 'hello' with provided input 'tennis'", ): from_gql_create(query, size_property="hello") diff --git a/python-wrapper/tests/test_neo4j.py b/python-wrapper/tests/test_neo4j.py index 313e9a6d..b4de935a 100644 --- a/python-wrapper/tests/test_neo4j.py +++ b/python-wrapper/tests/test_neo4j.py @@ -47,8 +47,9 @@ def test_from_neo4j_graph_basic(neo4j_session: Session) -> None: Node( id=node_ids[1], caption="_CI_A:_CI_B", - size=11, + size=None, properties=dict( + size=11, labels=["_CI_A", "_CI_B"], name="Bob", height=10, @@ -131,8 +132,8 @@ def test_from_neo4j_result(neo4j_session: Session) -> None: Node( id=node_ids[1], caption="_CI_A:_CI_B", - size=11, properties=dict( + size=11, labels=["_CI_A", "_CI_B"], name="Bob", height=10, @@ -268,9 +269,9 @@ def test_from_neo4j_graph_driver(neo4j_session: Session, neo4j_driver: Driver) - Node( id=node_ids[1], caption="_CI_A:_CI_B", - size=11, properties=dict( labels=["_CI_A", "_CI_B"], + size=11, name="Bob", height=10, id=84, diff --git a/python-wrapper/tests/test_sizes.py b/python-wrapper/tests/test_sizes.py index 37a353b3..a22b010e 100644 --- a/python-wrapper/tests/test_sizes.py +++ b/python-wrapper/tests/test_sizes.py @@ -37,6 +37,17 @@ def test_verify_radii() -> None: verify_radii((1, 2)) +def test_resize_nodes_either_sizes_or_property() -> None: + nodes = [ + Node(id=42), + Node(id="1337", size=10), + ] + VG = VisualizationGraph(nodes=nodes, relationships=[]) + + with pytest.raises(ValueError, match="At most one of the arguments `sizes` and `property` can be provided"): + VG.resize_nodes(sizes={"1337": 20}, property="size", node_radius_min_max=(3, 60)) # type: ignore + + def test_resize_nodes_no_scaling() -> None: nodes = [ Node(id=42), @@ -61,6 +72,27 @@ def test_resize_nodes_no_scaling() -> None: VG.resize_nodes(new_sizes, None) +def test_resize_nodes_by_property() -> None: + nodes = [ + Node(id=42, properties={"age": 4}), + Node(id="1337", properties={"age": 2}), + Node(id=55, properties={"age": 8}), + ] + VG = VisualizationGraph(nodes=nodes, relationships=[]) + + VG.resize_nodes(property="age", node_radius_min_max=None) + + assert VG.nodes[0].size == 4 + assert VG.nodes[1].size == 2 + assert VG.nodes[2].size == 8 + + VG.resize_nodes(property="age", node_radius_min_max=(1, 4)) + + assert VG.nodes[0].size == 2 + assert VG.nodes[1].size == 1 + assert VG.nodes[2].size == 4 + + def test_resize_nodes_with_scaling_constant() -> None: nodes = [ Node(id=42), @@ -124,5 +156,5 @@ def test_resize_nodes_with_scaling_only() -> None: def test_resize_nodes_no_args_failure() -> None: VG = VisualizationGraph(nodes=[], relationships=[]) - with pytest.raises(ValueError, match="At least one of `sizes` and `node_radius_min_max` must be given"): + with pytest.raises(ValueError, match="At least one of `sizes`, `property` or `node_radius_min_max` must be given"): VG.resize_nodes(node_radius_min_max=None) From ffc9ca2e6a50a875e4e8566fe66d47abf25ae23c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florentin=20D=C3=B6rre?= Date: Fri, 26 Sep 2025 16:10:48 +0200 Subject: [PATCH 4/6] Allow resize nodes based on a property --- changelog.md | 1 + .../src/neo4j_viz/visualization_graph.py | 48 ++++++++++++------- 2 files changed, 33 insertions(+), 16 deletions(-) diff --git a/changelog.md b/changelog.md index daf3399e..3dc0199c 100644 --- a/changelog.md +++ b/changelog.md @@ -14,5 +14,6 @@ ## Improvements - Validate fields of a node and relationship not only at construction but also on assignment. +- Allow resizing per node property such as `VG.resize_nodes(property="score")`. ## Other changes diff --git a/python-wrapper/src/neo4j_viz/visualization_graph.py b/python-wrapper/src/neo4j_viz/visualization_graph.py index c3387dd2..a9661bc7 100644 --- a/python-wrapper/src/neo4j_viz/visualization_graph.py +++ b/python-wrapper/src/neo4j_viz/visualization_graph.py @@ -154,6 +154,7 @@ def resize_nodes( self, sizes: Optional[dict[NodeIdType, RealNumber]] = None, node_radius_min_max: Optional[tuple[RealNumber, RealNumber]] = (3, 60), + property: Optional[str] = None, ) -> None: """ Resize the nodes in the graph. @@ -163,33 +164,47 @@ def resize_nodes( sizes: A dictionary mapping from node ID to the new size of the node. If a node ID is not in the dictionary, the size of the node is not changed. + Must be None if `property` is provided. node_radius_min_max: Minimum and maximum node size radius as a tuple. To avoid tiny or huge nodes in the visualization, the node sizes are scaled to fit in the given range. If None, the sizes are used as is. + property: + The property of the nodes to use for sizing. Must be None if `sizes` is provided. """ - if sizes is None and node_radius_min_max is None: - raise ValueError("At least one of `sizes` and `node_radius_min_max` must be given") + if sizes is not None and property is not None: + raise ValueError("At most one of the arguments `sizes` and `property` can be provided") - # Gather and verify all node size values we have to work with - all_sizes = {} - for node in self.nodes: - size = None - if sizes is not None: - size = sizes.get(node.id) + if sizes is None and property is None and node_radius_min_max is None: + raise ValueError("At least one of `sizes`, `property` or `node_radius_min_max` must be given") + # Gather node sizes + all_sizes = {} + if sizes is not None: + for node in self.nodes: + size = sizes.get(node.id, node.size) if size is not None: - if not isinstance(size, (int, float)): - raise ValueError(f"Size for node '{node.id}' must be a real number, but was {size}") - - if size < 0: - raise ValueError(f"Size for node '{node.id}' must be non-negative, but was {size}") - all_sizes[node.id] = size - - if size is None: + elif property is not None: + for node in self.nodes: + size = node.properties.get(property, node.size) + if size is not None: + all_sizes[node.id] = size + else: + for node in self.nodes: if node.size is not None: all_sizes[node.id] = node.size + # Validate node sizes + for id, size in all_sizes.items(): + if size is None: + continue + + if not isinstance(size, (int, float)): + raise ValueError(f"Size for node '{id}' must be a real number, but was {size}") + + if size < 0: + raise ValueError(f"Size for node '{id}' must be non-negative, but was {size}") + if node_radius_min_max is not None: verify_radii(node_radius_min_max) @@ -197,6 +212,7 @@ def resize_nodes( else: final_sizes = all_sizes + # Apply the final sizes to the nodes for node in self.nodes: size = final_sizes.get(node.id) From 2f818a41de4941a0df604274039d828d701d936d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florentin=20D=C3=B6rre?= Date: Fri, 26 Sep 2025 16:11:45 +0200 Subject: [PATCH 5/6] Apply format --- python-wrapper/src/neo4j_viz/gql_create.py | 1 - python-wrapper/tests/test_sizes.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/python-wrapper/src/neo4j_viz/gql_create.py b/python-wrapper/src/neo4j_viz/gql_create.py index 4d7d6845..e584d9a4 100644 --- a/python-wrapper/src/neo4j_viz/gql_create.py +++ b/python-wrapper/src/neo4j_viz/gql_create.py @@ -381,5 +381,4 @@ def _parse_validation_error(e: ValidationError, entity_type: type[BaseModel]) -> 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) - return VG diff --git a/python-wrapper/tests/test_sizes.py b/python-wrapper/tests/test_sizes.py index a22b010e..251fd351 100644 --- a/python-wrapper/tests/test_sizes.py +++ b/python-wrapper/tests/test_sizes.py @@ -45,7 +45,7 @@ def test_resize_nodes_either_sizes_or_property() -> None: VG = VisualizationGraph(nodes=nodes, relationships=[]) with pytest.raises(ValueError, match="At most one of the arguments `sizes` and `property` can be provided"): - VG.resize_nodes(sizes={"1337": 20}, property="size", node_radius_min_max=(3, 60)) # type: ignore + VG.resize_nodes(sizes={"1337": 20}, property="size", node_radius_min_max=(3, 60)) def test_resize_nodes_no_scaling() -> None: From 86a706dfd1e35f1c3cdf0e4d5455cda837dfe583 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florentin=20D=C3=B6rre?= Date: Tue, 30 Sep 2025 15:02:26 +0200 Subject: [PATCH 6/6] Filter out 3.9 deprecation warning Tried as a filterwarnings on pyproject.toml but it was not getting picked up :/ --- .github/workflows/snowflake-integration-tests.yml | 5 ++--- .github/workflows/unit-tests.yml | 8 ++++---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/.github/workflows/snowflake-integration-tests.yml b/.github/workflows/snowflake-integration-tests.yml index 07831478..d9d4c8b8 100644 --- a/.github/workflows/snowflake-integration-tests.yml +++ b/.github/workflows/snowflake-integration-tests.yml @@ -8,7 +8,6 @@ on: # branches: [ "main" ] # Skip on this check PR to minimize the load against Snowflake (and keep PR checks fast) - # Allows you to run this workflow manually from the Actions tab workflow_dispatch: @@ -33,7 +32,7 @@ jobs: - uses: actions/setup-python@v5 with: python-version: "3.11" - cache: 'pip' + cache: "pip" cache-dependency-path: pyproject.toml - run: pip install ".[dev]" - run: pip install ".[pandas]" @@ -46,4 +45,4 @@ jobs: SNOWFLAKE_PASSWORD: ${{ secrets.SNOWFLAKE_PASSWORD }} SNOWFLAKE_ROLE: ACCOUNTADMIN SNOWFLAKE_WAREHOUSE: ${{ secrets.SNOWFLAKE_WAREHOUSE }} - run: pytest tests/ --include-snowflake \ No newline at end of file + run: pytest tests/ --include-snowflake -W "ignore:Python Runtime 3.9 reached its End-Of-Life:DeprecationWarning" diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index d5fe6e64..0e44291c 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -4,12 +4,12 @@ name: Run Python unit tests on: # Triggers the workflow on push or pull request events but only for the "main" branch push: - branches: [ "main" ] + branches: ["main"] pull_request: paths: - "python-wrapper/**" # python code + its resources - "python-wrapper/pyproject.toml" # dependencies - branches: [ "main" ] + branches: ["main"] # Allows you to run this workflow manually from the Actions tab workflow_dispatch: @@ -36,7 +36,7 @@ jobs: - uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - cache: 'pip' + cache: "pip" cache-dependency-path: pyproject.toml - run: pip install ".[dev]" - run: pip install ".[pandas]" @@ -45,4 +45,4 @@ jobs: - run: pip install ".[snowflake]" - name: Run tests - run: pytest tests/ + run: pytest tests/ -W "ignore:Python Runtime 3.9 reached its End-Of-Life:DeprecationWarning"