Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
## Improvements

* Allow for `Node` and `Relationship` inputs to be given as `camelCase` and `SCREAMING_SNAKE_CASE` (in addition to `snake_case`).
* Convert non-json serializable properties to strings in the `render` method, instead of raising an error.


## Other changes
37 changes: 25 additions & 12 deletions python-wrapper/src/neo4j_viz/nvl.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import json
import uuid
from importlib.resources import files
from typing import Union

from IPython.display import HTML

Expand Down Expand Up @@ -40,10 +41,28 @@ def __init__(self) -> None:
with screenshot_path.open("r", encoding="utf-8") as file:
self.screenshot_svg = file.read()

def unsupported_field_type_error(self, e: TypeError, entity: str) -> Exception:
if "not JSON serializable" in str(e):
return ValueError(f"A field of a {entity} object is not supported: {str(e)}")
return e
@staticmethod
def _serialize_entity(entity: Union[Node, Relationship]) -> str:
try:
entity_dict = entity.to_dict()
return json.dumps(entity_dict)
except TypeError:
props_as_strings = {}
for k, v in entity_dict["properties"].items():
try:
json.dumps(v)
except TypeError:
props_as_strings[k] = str(v)
entity_dict["properties"].update(props_as_strings)

try:
return json.dumps(entity_dict)
except TypeError as e:
# This should never happen anymore, but just in case
if "not JSON serializable" in str(e):
raise ValueError(f"A field of a {type(entity).__name__} object is not supported: {str(e)}")
else:
raise e

def render(
self,
Expand All @@ -54,14 +73,8 @@ def render(
height: str,
show_hover_tooltip: bool,
) -> HTML:
try:
nodes_json = json.dumps([node.to_dict() for node in nodes])
except TypeError as e:
raise self.unsupported_field_type_error(e, "node")
try:
rels_json = json.dumps([rel.to_dict() for rel in relationships])
except TypeError as e:
raise self.unsupported_field_type_error(e, "relationship")
nodes_json = f"[{','.join([self._serialize_entity(node) for node in nodes])}]"
rels_json = f"[{','.join([self._serialize_entity(rel) for rel in relationships])}]"

render_options_json = json.dumps(render_options.to_dict())
container_id = str(uuid.uuid4())
Expand Down
46 changes: 18 additions & 28 deletions python-wrapper/tests/test_render.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from selenium import webdriver

from neo4j_viz import Node, Relationship, VisualizationGraph
from neo4j_viz.nvl import NVL
from neo4j_viz.options import Layout, Renderer

render_cases = {
Expand Down Expand Up @@ -64,34 +65,6 @@ def test_basic_render(render_option: dict[str, Any], tmp_path: Path) -> None:
assert not severe_logs, f"Severe logs found: {severe_logs}, all logs: {logs}"


def test_unsupported_field_type() -> None:
with pytest.raises(
ValueError, match="A field of a node object is not supported: Object of type set is not JSON serializable"
):
nodes = [
Node(
id="4:d09f48a4-5fca-421d-921d-a30a896c604d:0", caption="Person", properties={"unsupported": {1, 2, 3}}
),
]
VG = VisualizationGraph(nodes=nodes, relationships=[])
VG.render()

with pytest.raises(
ValueError,
match="A field of a relationship object is not supported: Object of type set is not JSON serializable",
):
relationships = [
Relationship(
source="4:d09f48a4-5fca-421d-921d-a30a896c604d:0",
target="4:d09f48a4-5fca-421d-921d-a30a896c604d:6",
caption="BUYS",
properties={"unsupported": {1, 2, 3}},
),
]
VG = VisualizationGraph(nodes=[], relationships=relationships)
VG.render()


def test_max_allowed_nodes_limit() -> None:
nodes = [Node(id=i) for i in range(10_001)]
VG = VisualizationGraph(nodes=nodes, relationships=[])
Expand Down Expand Up @@ -121,3 +94,20 @@ def test_render_warnings() -> None:
"relationships. If you need these features, use the canvas renderer by setting the `renderer` parameter",
):
VG.render(max_allowed_nodes=20_000, renderer=Renderer.WEB_GL)


def test_render_non_json_serializable() -> None:
import datetime

now = datetime.datetime.now()
node = Node(
id=0,
properties={
"non-json-serializable": now,
},
)
assert str(now) in NVL._serialize_entity(node)

VG = VisualizationGraph(nodes=[node], relationships=[])
# Should not raise an error
VG.render()