neo4j-viz 0.4.1__tar.gz → 0.5.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. {neo4j_viz-0.4.1 → neo4j_viz-0.5.0}/PKG-INFO +5 -3
  2. {neo4j_viz-0.4.1 → neo4j_viz-0.5.0}/pyproject.toml +4 -3
  3. {neo4j_viz-0.4.1 → neo4j_viz-0.5.0}/src/neo4j_viz/gds.py +34 -27
  4. {neo4j_viz-0.4.1 → neo4j_viz-0.5.0}/src/neo4j_viz/pandas.py +1 -1
  5. neo4j_viz-0.5.0/src/neo4j_viz/resources/nvl_entrypoint/base.js +2 -0
  6. neo4j_viz-0.5.0/src/neo4j_viz/snowflake.py +344 -0
  7. {neo4j_viz-0.4.1 → neo4j_viz-0.5.0}/src/neo4j_viz/visualization_graph.py +2 -1
  8. {neo4j_viz-0.4.1 → neo4j_viz-0.5.0}/src/neo4j_viz.egg-info/PKG-INFO +5 -3
  9. {neo4j_viz-0.4.1 → neo4j_viz-0.5.0}/src/neo4j_viz.egg-info/SOURCES.txt +3 -1
  10. {neo4j_viz-0.4.1 → neo4j_viz-0.5.0}/src/neo4j_viz.egg-info/requires.txt +5 -2
  11. {neo4j_viz-0.4.1 → neo4j_viz-0.5.0}/tests/test_gds.py +45 -22
  12. neo4j_viz-0.5.0/tests/test_snowflake.py +77 -0
  13. neo4j_viz-0.4.1/src/neo4j_viz/resources/nvl_entrypoint/base.js +0 -2
  14. {neo4j_viz-0.4.1 → neo4j_viz-0.5.0}/README.md +0 -0
  15. {neo4j_viz-0.4.1 → neo4j_viz-0.5.0}/setup.cfg +0 -0
  16. {neo4j_viz-0.4.1 → neo4j_viz-0.5.0}/src/neo4j_viz/__init__.py +0 -0
  17. {neo4j_viz-0.4.1 → neo4j_viz-0.5.0}/src/neo4j_viz/colors.py +0 -0
  18. {neo4j_viz-0.4.1 → neo4j_viz-0.5.0}/src/neo4j_viz/gql_create.py +0 -0
  19. {neo4j_viz-0.4.1 → neo4j_viz-0.5.0}/src/neo4j_viz/neo4j.py +0 -0
  20. {neo4j_viz-0.4.1 → neo4j_viz-0.5.0}/src/neo4j_viz/node.py +0 -0
  21. {neo4j_viz-0.4.1 → neo4j_viz-0.5.0}/src/neo4j_viz/node_size.py +0 -0
  22. {neo4j_viz-0.4.1 → neo4j_viz-0.5.0}/src/neo4j_viz/nvl.py +0 -0
  23. {neo4j_viz-0.4.1 → neo4j_viz-0.5.0}/src/neo4j_viz/options.py +0 -0
  24. {neo4j_viz-0.4.1 → neo4j_viz-0.5.0}/src/neo4j_viz/py.typed +0 -0
  25. {neo4j_viz-0.4.1 → neo4j_viz-0.5.0}/src/neo4j_viz/relationship.py +0 -0
  26. {neo4j_viz-0.4.1 → neo4j_viz-0.5.0}/src/neo4j_viz/resources/icons/screenshot.svg +0 -0
  27. {neo4j_viz-0.4.1 → neo4j_viz-0.5.0}/src/neo4j_viz/resources/icons/zoom-in.svg +0 -0
  28. {neo4j_viz-0.4.1 → neo4j_viz-0.5.0}/src/neo4j_viz/resources/icons/zoom-out.svg +0 -0
  29. {neo4j_viz-0.4.1 → neo4j_viz-0.5.0}/src/neo4j_viz/resources/nvl_entrypoint/styles.css +0 -0
  30. {neo4j_viz-0.4.1 → neo4j_viz-0.5.0}/src/neo4j_viz.egg-info/dependency_links.txt +0 -0
  31. {neo4j_viz-0.4.1 → neo4j_viz-0.5.0}/src/neo4j_viz.egg-info/top_level.txt +0 -0
  32. {neo4j_viz-0.4.1 → neo4j_viz-0.5.0}/tests/test_colors.py +0 -0
  33. {neo4j_viz-0.4.1 → neo4j_viz-0.5.0}/tests/test_gql_create.py +0 -0
  34. {neo4j_viz-0.4.1 → neo4j_viz-0.5.0}/tests/test_neo4j.py +0 -0
  35. {neo4j_viz-0.4.1 → neo4j_viz-0.5.0}/tests/test_node.py +0 -0
  36. {neo4j_viz-0.4.1 → neo4j_viz-0.5.0}/tests/test_notebooks.py +0 -0
  37. {neo4j_viz-0.4.1 → neo4j_viz-0.5.0}/tests/test_options.py +0 -0
  38. {neo4j_viz-0.4.1 → neo4j_viz-0.5.0}/tests/test_pandas.py +0 -0
  39. {neo4j_viz-0.4.1 → neo4j_viz-0.5.0}/tests/test_pinned.py +0 -0
  40. {neo4j_viz-0.4.1 → neo4j_viz-0.5.0}/tests/test_relationship.py +0 -0
  41. {neo4j_viz-0.4.1 → neo4j_viz-0.5.0}/tests/test_render.py +0 -0
  42. {neo4j_viz-0.4.1 → neo4j_viz-0.5.0}/tests/test_sizes.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: neo4j-viz
3
- Version: 0.4.1
3
+ Version: 0.5.0
4
4
  Summary: A simple graph visualization tool
5
5
  Author-email: Neo4j <team-gds@neo4j.org>
6
6
  Project-URL: Homepage, https://neo4j.com/
@@ -34,7 +34,7 @@ Requires-Dist: pydantic-extra-types<3,>=2
34
34
  Requires-Dist: enum-tools==0.12.0
35
35
  Provides-Extra: dev
36
36
  Requires-Dist: ruff==0.11.8; extra == "dev"
37
- Requires-Dist: mypy==1.15.0; extra == "dev"
37
+ Requires-Dist: mypy==1.17.1; extra == "dev"
38
38
  Requires-Dist: pytest==8.3.4; extra == "dev"
39
39
  Requires-Dist: selenium==4.32.0; extra == "dev"
40
40
  Requires-Dist: ipykernel==6.29.5; extra == "dev"
@@ -55,6 +55,8 @@ Provides-Extra: gds
55
55
  Requires-Dist: graphdatascience<2,>=1; extra == "gds"
56
56
  Provides-Extra: neo4j
57
57
  Requires-Dist: neo4j; extra == "neo4j"
58
+ Provides-Extra: snowflake
59
+ Requires-Dist: snowflake-snowpark-python<2,>=1; extra == "snowflake"
58
60
  Provides-Extra: notebook
59
61
  Requires-Dist: ipykernel>=6.29.5; extra == "notebook"
60
62
  Requires-Dist: pykernel>=0.1.6; extra == "notebook"
@@ -62,7 +64,7 @@ Requires-Dist: neo4j>=5.26.0; extra == "notebook"
62
64
  Requires-Dist: ipywidgets>=8.0.0; extra == "notebook"
63
65
  Requires-Dist: palettable>=3.3.3; extra == "notebook"
64
66
  Requires-Dist: matplotlib>=3.9.4; extra == "notebook"
65
- Requires-Dist: snowflake-snowpark-python==1.26.0; extra == "notebook"
67
+ Requires-Dist: snowflake-snowpark-python==1.37.0; extra == "notebook"
66
68
 
67
69
  # Graph Visualization for Python by Neo4j
68
70
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "neo4j-viz"
7
- version = "0.4.1"
7
+ version = "0.5.0"
8
8
  description = "A simple graph visualization tool"
9
9
  readme = "README.md"
10
10
  authors = [{ name = "Neo4j", email = "team-gds@neo4j.org" }]
@@ -42,7 +42,7 @@ requires-python = ">=3.9"
42
42
  [project.optional-dependencies]
43
43
  dev = [
44
44
  "ruff==0.11.8",
45
- "mypy==1.15.0",
45
+ "mypy==1.17.1",
46
46
  "pytest==8.3.4",
47
47
  "selenium==4.32.0",
48
48
  "ipykernel==6.29.5",
@@ -61,6 +61,7 @@ docs = [
61
61
  pandas = ["pandas>=2, <3", "pandas-stubs>=2, <3"]
62
62
  gds = ["graphdatascience>=1, <2"]
63
63
  neo4j = ["neo4j"]
64
+ snowflake = ["snowflake-snowpark-python>=1, <2"]
64
65
  notebook = [
65
66
  "ipykernel>=6.29.5",
66
67
  "pykernel>=0.1.6",
@@ -68,7 +69,7 @@ notebook = [
68
69
  "ipywidgets>=8.0.0",
69
70
  "palettable>=3.3.3",
70
71
  "matplotlib>=3.9.4",
71
- "snowflake-snowpark-python==1.26.0",
72
+ "snowflake-snowpark-python==1.37.0",
72
73
  ]
73
74
 
74
75
  [project.urls]
@@ -2,12 +2,11 @@ from __future__ import annotations
2
2
 
3
3
  import warnings
4
4
  from itertools import chain
5
- from typing import Optional
5
+ from typing import Optional, cast
6
6
  from uuid import uuid4
7
7
 
8
8
  import pandas as pd
9
9
  from graphdatascience import Graph, GraphDataScience
10
- from pandas import Series
11
10
 
12
11
  from .pandas import _from_dfs
13
12
  from .visualization_graph import VisualizationGraph
@@ -24,22 +23,25 @@ def _fetch_node_dfs(
24
23
  }
25
24
 
26
25
 
27
- def _fetch_rel_df(gds: GraphDataScience, G: Graph) -> pd.DataFrame:
28
- relationship_properties = G.relationship_properties()
29
- assert isinstance(relationship_properties, Series)
26
+ def _fetch_rel_dfs(gds: GraphDataScience, G: Graph) -> list[pd.DataFrame]:
27
+ rel_types = G.relationship_types()
30
28
 
31
- relationship_properties_per_type = relationship_properties.tolist()
32
- property_set: set[str] = set()
33
- for props in relationship_properties_per_type:
34
- if props:
35
- property_set.update(props)
29
+ rel_props = {rel_type: G.relationship_properties(rel_type) for rel_type in rel_types}
36
30
 
37
- if len(property_set) > 0:
38
- return gds.graph.relationshipProperties.stream(
39
- G, relationship_properties=list(property_set), separate_property_columns=True
40
- )
31
+ rel_dfs: list[pd.DataFrame] = []
32
+ # Have to call per stream per relationship type as there was a bug in GDS < 2.21
33
+ for rel_type, props in rel_props.items():
34
+ assert isinstance(props, list)
35
+ if len(props) > 0:
36
+ rel_df = gds.graph.relationshipProperties.stream(
37
+ G, relationship_types=rel_type, relationship_properties=list(props), separate_property_columns=True
38
+ )
39
+ else:
40
+ rel_df = gds.graph.relationships.stream(G, relationship_types=[rel_type])
41
+
42
+ rel_dfs.append(rel_df)
41
43
 
42
- return gds.graph.relationships.stream(G)
44
+ return rel_dfs
43
45
 
44
46
 
45
47
  def from_gds(
@@ -79,31 +81,32 @@ def from_gds(
79
81
  """
80
82
  node_properties_from_gds = G.node_properties()
81
83
  assert isinstance(node_properties_from_gds, pd.Series)
82
- actual_node_properties = node_properties_from_gds.to_dict()
84
+ actual_node_properties: dict[str, list[str]] = cast(dict[str, list[str]], node_properties_from_gds.to_dict())
83
85
  all_actual_node_properties = list(chain.from_iterable(actual_node_properties.values()))
84
86
 
85
87
  if size_property is not None:
86
88
  if size_property not in all_actual_node_properties:
87
89
  raise ValueError(f"There is no node property '{size_property}' in graph '{G.name()}'")
88
90
 
91
+ node_properties_by_label_sets: dict[str, set[str]] = dict()
89
92
  if additional_node_properties is None:
90
- node_properties_by_label = {k: set(v) for k, v in actual_node_properties.items()}
93
+ node_properties_by_label_sets = {k: set(v) for k, v in actual_node_properties.items()}
91
94
  else:
92
95
  for prop in additional_node_properties:
93
96
  if prop not in all_actual_node_properties:
94
97
  raise ValueError(f"There is no node property '{prop}' in graph '{G.name()}'")
95
98
 
96
- node_properties_by_label = {}
97
99
  for label, props in actual_node_properties.items():
98
- node_properties_by_label[label] = {
100
+ node_properties_by_label_sets[label] = {
99
101
  prop for prop in actual_node_properties[label] if prop in additional_node_properties
100
102
  }
101
103
 
102
104
  if size_property is not None:
103
- for label, props in node_properties_by_label.items():
104
- props.add(size_property)
105
+ # For some reason mypy are unable to understand that this is dict[str, set[str]]
106
+ for label, props in node_properties_by_label_sets.items(): # type: ignore
107
+ props.add(size_property) # type: ignore
105
108
 
106
- node_properties_by_label = {k: list(v) for k, v in node_properties_by_label.items()}
109
+ node_properties_by_label = {k: list(v) for k, v in node_properties_by_label_sets.items()}
107
110
 
108
111
  node_count = G.node_count()
109
112
  if node_count > max_node_count:
@@ -131,7 +134,7 @@ def from_gds(
131
134
  for df in node_dfs.values():
132
135
  df.drop(columns=[property_name], inplace=True)
133
136
 
134
- rel_df = _fetch_rel_df(gds, G_fetched)
137
+ rel_dfs = _fetch_rel_dfs(gds, G_fetched)
135
138
  finally:
136
139
  if G_fetched.name() != G.name():
137
140
  G_fetched.drop()
@@ -146,7 +149,10 @@ def from_gds(
146
149
  if size_property is not None:
147
150
  if "size" in all_actual_node_properties and size_property != "size":
148
151
  node_props_df.rename(columns={"size": "__size"}, inplace=True)
149
- node_props_df.rename(columns={size_property: "size"}, inplace=True)
152
+ if additional_node_properties is not None and size_property not in additional_node_properties:
153
+ node_props_df.rename(columns={size_property: "size"}, inplace=True)
154
+ else:
155
+ node_props_df["size"] = node_props_df[size_property]
150
156
 
151
157
  for lbl, df in node_dfs.items():
152
158
  if "labels" in all_actual_node_properties:
@@ -161,12 +167,13 @@ def from_gds(
161
167
  if "caption" not in all_actual_node_properties:
162
168
  node_df["caption"] = node_df["labels"].astype(str)
163
169
 
164
- if "caption" not in rel_df.columns:
165
- rel_df["caption"] = rel_df["relationshipType"]
170
+ for rel_df in rel_dfs:
171
+ if "caption" not in rel_df.columns:
172
+ rel_df["caption"] = rel_df["relationshipType"]
166
173
 
167
174
  try:
168
175
  return _from_dfs(
169
- node_df, rel_df, node_radius_min_max=node_radius_min_max, rename_properties={"__size": "size"}, dropna=True
176
+ node_df, rel_dfs, node_radius_min_max=node_radius_min_max, rename_properties={"__size": "size"}, dropna=True
170
177
  )
171
178
  except ValueError as e:
172
179
  err_msg = str(e)
@@ -74,7 +74,7 @@ def _parse_nodes(
74
74
  has_size = True
75
75
  nodes = []
76
76
  for node_df in node_dfs_iter:
77
- has_size &= "size" in node_df.columns
77
+ has_size &= "size" in [c.lower() for c in node_df.columns]
78
78
  for _, row in node_df.iterrows():
79
79
  if dropna:
80
80
  row = row.dropna(inplace=False)