Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

new: use _graphs instead of nxadb_graphs #66

Merged
merged 9 commits into from
Oct 21, 2024
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
2 changes: 1 addition & 1 deletion _nx_arangodb/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.1.1
1.2.0
47 changes: 33 additions & 14 deletions nx_arangodb/classes/dict/graph.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

import os
from collections import UserDict
from typing import Any, Callable

Expand Down Expand Up @@ -39,6 +40,8 @@ def graph_attr_dict_factory(
# Graph #
#########

GRAPH_FIELD = "networkx"


def build_graph_attr_dict_data(
parent: GraphAttrDict, data: dict[str, Any]
Expand Down Expand Up @@ -104,7 +107,11 @@ class GraphDict(UserDict[str, Any]):
Given that ArangoDB does not have a concept of graph attributes, this class
stores the attributes in a collection with the graph name as the document key.

For now, the collection is called 'nxadb_graphs'.
The default collection is called `_graphs`. However, if the
`DATABASE_GRAPH_COLLECTION` environment variable is specified,
then that collection will be used. This variable is useful when the
database user does not have permission to access the `_graphs`
system collection.
Comment on lines -107 to +114
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

highlighting


Parameters
----------
Expand All @@ -123,31 +130,39 @@ class GraphDict(UserDict[str, Any]):
>>> del G.graph['foo']
"""

def __init__(self, db: StandardDatabase, graph: Graph, *args: Any, **kwargs: Any):
def __init__(
self,
db: StandardDatabase,
graph: Graph,
*args: Any,
**kwargs: Any,
):
super().__init__(*args, **kwargs)
self.data: dict[str, Any] = {}

self.db = db
self.adb_graph = graph
self.graph_name = graph.name
self.COLLECTION_NAME = "nxadb_graphs"
self.graph_id = f"{self.COLLECTION_NAME}/{self.graph_name}"
self.collection_name = os.environ.get("DATABASE_GRAPH_COLLECTION", "_graphs")

self.collection = create_collection(db, self.COLLECTION_NAME)
self.graph_id = f"{self.collection_name}/{self.graph_name}"
self.parent_keys = [GRAPH_FIELD]

self.collection = create_collection(db, self.collection_name)
self.graph_attr_dict_factory = graph_attr_dict_factory(
self.db, self.adb_graph, self.graph_id
)

result = doc_get_or_insert(self.db, self.COLLECTION_NAME, self.graph_id)
for k, v in result.items():
result = doc_get_or_insert(self.db, self.collection_name, self.graph_id)
for k, v in result.get(GRAPH_FIELD, {}).items():
self.data[k] = self.__process_graph_dict_value(k, v)

def __process_graph_dict_value(self, key: str, value: Any) -> Any:
if not isinstance(value, dict):
return value

graph_attr_dict = self.graph_attr_dict_factory()
graph_attr_dict.parent_keys = [key]
graph_attr_dict.parent_keys += [key]
graph_attr_dict.data = build_graph_attr_dict_data(graph_attr_dict, value)

return graph_attr_dict
Expand All @@ -158,7 +173,7 @@ def __contains__(self, key: str) -> bool:
if key in self.data:
return True

return aql_doc_has_key(self.db, self.graph_id, key)
return aql_doc_has_key(self.db, self.graph_id, key, self.parent_keys)

@key_is_string
def __getitem__(self, key: str) -> Any:
Expand All @@ -167,7 +182,7 @@ def __getitem__(self, key: str) -> Any:
if value := self.data.get(key):
return value

result = aql_doc_get_key(self.db, self.graph_id, key)
result = aql_doc_get_key(self.db, self.graph_id, key, self.parent_keys)

if result is None:
raise KeyError(key)
Expand All @@ -187,14 +202,17 @@ def __setitem__(self, key: str, value: Any) -> None:

graph_dict_value = self.__process_graph_dict_value(key, value)
self.data[key] = graph_dict_value
doc_update(self.db, self.graph_id, {key: value})

update_dict = get_update_dict(self.parent_keys, {key: value})
doc_update(self.db, self.graph_id, update_dict)

@key_is_string
@key_is_not_reserved
def __delitem__(self, key: str) -> None:
"""del G.graph['foo']"""
self.data.pop(key, None)
doc_update(self.db, self.graph_id, {key: None})
update_dict = get_update_dict(self.parent_keys, {key: None})
doc_update(self.db, self.graph_id, update_dict)

# @values_are_json_serializable # TODO?
def update(self, attrs: Any) -> None: # type: ignore
Expand All @@ -208,7 +226,8 @@ def update(self, attrs: Any) -> None: # type: ignore
graph_attr_dict.data = graph_attr_dict_data

self.data.update(graph_attr_dict_data)
doc_update(self.db, self.graph_id, attrs)
update_dict = get_update_dict(self.parent_keys, attrs)
doc_update(self.db, self.graph_id, update_dict)

def clear(self) -> None:
"""G.graph.clear()"""
Expand Down Expand Up @@ -256,7 +275,7 @@ def __init__(
self.graph = graph
self.graph_id: str = graph_id

self.parent_keys: list[str] = []
self.parent_keys: list[str] = [GRAPH_FIELD]
self.graph_attr_dict_factory = graph_attr_dict_factory(
self.db, self.graph, self.graph_id
)
Expand Down
75 changes: 38 additions & 37 deletions tests/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

import nx_arangodb as nxadb
from nx_arangodb.classes.dict.adj import AdjListOuterDict, EdgeAttrDict, EdgeKeyDict
from nx_arangodb.classes.dict.graph import GRAPH_FIELD
from nx_arangodb.classes.dict.node import NodeAttrDict, NodeDict

from .conftest import create_grid_graph, create_line_graph, db, run_gpu_tests
Expand Down Expand Up @@ -1638,7 +1639,7 @@ def test_multidigraph_edges_crud(load_karate_graph: Any) -> None:
def test_graph_dict_init(load_karate_graph: Any) -> None:
G = nxadb.Graph(name="KarateGraph", default_node_type="person")
assert db.collection("_graphs").has("KarateGraph")
graph_document = db.collection("_graphs").get("KarateGraph")
graph_document = db.document(f"_graphs/{G.name}")
assert graph_document["_key"] == "KarateGraph"
assert graph_document["edgeDefinitions"] == [
{"collection": "knows", "from": ["person"], "to": ["person"]},
Expand All @@ -1655,33 +1656,31 @@ def test_graph_dict_init_extended(load_karate_graph: Any) -> None:
G = nxadb.Graph(name="KarateGraph", foo="bar", bar={"baz": True})
G.graph["foo"] = "!!!"
G.graph["bar"]["baz"] = False
assert db.document(G.graph.graph_id)["foo"] == "!!!"
assert db.document(G.graph.graph_id)["bar"]["baz"] is False
assert "baz" not in db.document(G.graph.graph_id)
assert db.document(G.graph.graph_id)[GRAPH_FIELD]["foo"] == "!!!"
assert db.document(G.graph.graph_id)[GRAPH_FIELD]["bar"]["baz"] is False
assert "baz" not in db.document(G.graph.graph_id)[GRAPH_FIELD]


def test_graph_dict_clear_will_not_remove_remote_data(load_karate_graph: Any) -> None:
G_adb = nxadb.Graph(
G = nxadb.Graph(
name="KarateGraph",
foo="bar",
bar={"a": 4},
)

G_adb.graph["ant"] = {"b": 5}
G_adb.graph["ant"]["b"] = 6
G_adb.clear()
G.graph["ant"] = {"b": 5}
G.graph["ant"]["b"] = 6
G.clear()
try:
G_adb.graph["ant"]
G.graph["ant"]
except KeyError:
raise AssertionError("Not allowed to fail.")

assert db.document(G_adb.graph.graph_id)["ant"] == {"b": 6}
assert db.document(G.graph.graph_id)[GRAPH_FIELD]["ant"] == {"b": 6}


def test_graph_dict_set_item(load_karate_graph: Any) -> None:
name = "KarateGraph"
db.collection("nxadb_graphs").delete(name, ignore_missing=True)
G = nxadb.Graph(name=name, default_node_type="person")
G = nxadb.Graph(name="KarateGraph", default_node_type="person")

json_values = [
"aString",
Expand All @@ -1699,122 +1698,124 @@ def test_graph_dict_set_item(load_karate_graph: Any) -> None:
G.graph["json"] = value

if value is None:
assert "json" not in db.document(G.graph.graph_id)
assert "json" not in db.document(G.graph.graph_id)[GRAPH_FIELD]
else:
assert G.graph["json"] == value
assert db.document(G.graph.graph_id)["json"] == value
assert db.document(G.graph.graph_id)[GRAPH_FIELD]["json"] == value


def test_graph_dict_update(load_karate_graph: Any) -> None:
G = nxadb.Graph(name="KarateGraph", default_node_type="person")
G.clear()

G.graph["a"] = "b"
to_update = {"c": "d"}
G.graph.update(to_update)

# local
assert G.graph["a"] == "b"
assert G.graph["c"] == "d"
assert G.graph.data["a"] == G.graph["a"] == "b"
assert G.graph.data["c"] == G.graph["c"] == "d"

# remote
adb_doc = db.collection("nxadb_graphs").get(G.name)
adb_doc = db.document(f"_graphs/{G.name}")[GRAPH_FIELD]
assert adb_doc["a"] == "b"
assert adb_doc["c"] == "d"


def test_graph_attr_dict_nested_update(load_karate_graph: Any) -> None:
G = nxadb.Graph(name="KarateGraph", default_node_type="person")
G.clear()

G.graph["a"] = {"b": "c"}
G.graph["a"].update({"d": "e"})
assert G.graph["a"]["b"] == "c"
assert G.graph["a"]["d"] == "e"
assert db.document(G.graph.graph_id)["a"]["b"] == "c"
assert db.document(G.graph.graph_id)["a"]["d"] == "e"
assert db.document(G.graph.graph_id)[GRAPH_FIELD]["a"]["b"] == "c"
assert db.document(G.graph.graph_id)[GRAPH_FIELD]["a"]["d"] == "e"


def test_graph_dict_nested_1(load_karate_graph: Any) -> None:
G = nxadb.Graph(name="KarateGraph", default_node_type="person")
G.clear()
icon = {"football_icon": "MJ7"}

G.graph["a"] = {"b": icon}
assert G.graph["a"]["b"] == icon
assert db.document(G.graph.graph_id)["a"]["b"] == icon
assert db.document(G.graph.graph_id)[GRAPH_FIELD]["a"]["b"] == icon


def test_graph_dict_nested_2(load_karate_graph: Any) -> None:
G = nxadb.Graph(name="KarateGraph", default_node_type="person")
G.clear()
icon = {"football_icon": "MJ7"}

G.graph["x"] = {"y": icon}
G.graph["x"]["y"]["amount_of_goals"] = 1337

assert G.graph["x"]["y"]["amount_of_goals"] == 1337
assert db.document(G.graph.graph_id)["x"]["y"]["amount_of_goals"] == 1337
assert (
db.document(G.graph.graph_id)[GRAPH_FIELD]["x"]["y"]["amount_of_goals"] == 1337
)


def test_graph_dict_empty_values(load_karate_graph: Any) -> None:
G = nxadb.Graph(name="KarateGraph", default_node_type="person")
G.clear()

G.graph["empty"] = {}
assert G.graph["empty"] == {}
assert db.document(G.graph.graph_id)["empty"] == {}
assert db.document(G.graph.graph_id)[GRAPH_FIELD]["empty"] == {}

G.graph["none"] = None
assert "none" not in db.document(G.graph.graph_id)
assert "none" not in db.document(G.graph.graph_id)[GRAPH_FIELD]
assert "none" not in G.graph


def test_graph_dict_nested_overwrite(load_karate_graph: Any) -> None:
G = nxadb.Graph(name="KarateGraph", default_node_type="person")
G.clear()
icon1 = {"football_icon": "MJ7"}
icon2 = {"basketball_icon": "MJ23"}

G.graph["a"] = {"b": icon1}
G.graph["a"]["b"]["football_icon"] = "ChangedIcon"
assert G.graph["a"]["b"]["football_icon"] == "ChangedIcon"
assert db.document(G.graph.graph_id)["a"]["b"]["football_icon"] == "ChangedIcon"
assert (
db.document(G.graph.graph_id)[GRAPH_FIELD]["a"]["b"]["football_icon"]
== "ChangedIcon"
)

# Overwrite entire nested dictionary
G.graph["a"] = {"b": icon2}
assert G.graph["a"]["b"]["basketball_icon"] == "MJ23"
assert db.document(G.graph.graph_id)["a"]["b"]["basketball_icon"] == "MJ23"
assert (
db.document(G.graph.graph_id)[GRAPH_FIELD]["a"]["b"]["basketball_icon"]
== "MJ23"
)


def test_graph_dict_complex_nested(load_karate_graph: Any) -> None:
G = nxadb.Graph(name="KarateGraph", default_node_type="person")
G.clear()

complex_structure = {"level1": {"level2": {"level3": {"key": "value"}}}}

G.graph["complex"] = complex_structure
assert G.graph["complex"]["level1"]["level2"]["level3"]["key"] == "value"
assert (
db.document(G.graph.graph_id)["complex"]["level1"]["level2"]["level3"]["key"]
db.document(G.graph.graph_id)[GRAPH_FIELD]["complex"]["level1"]["level2"][
"level3"
]["key"]
== "value"
)


def test_graph_dict_nested_deletion(load_karate_graph: Any) -> None:
G = nxadb.Graph(name="KarateGraph", default_node_type="person")
G.clear()
icon = {"football_icon": "MJ7", "amount_of_goals": 1337}

G.graph["x"] = {"y": icon}
del G.graph["x"]["y"]["amount_of_goals"]
assert "amount_of_goals" not in G.graph["x"]["y"]
assert "amount_of_goals" not in db.document(G.graph.graph_id)["x"]["y"]
assert "amount_of_goals" not in db.document(G.graph.graph_id)[GRAPH_FIELD]["x"]["y"]

# Delete top-level key
del G.graph["x"]
assert "x" not in G.graph
assert "x" not in db.document(G.graph.graph_id)
assert "x" not in db.document(G.graph.graph_id)[GRAPH_FIELD]


def test_readme(load_karate_graph: Any) -> None:
Expand Down
10 changes: 5 additions & 5 deletions tests/test_graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
AdjListOuterDict,
EdgeAttrDict,
)
from nx_arangodb.classes.dict.graph import GraphDict
from nx_arangodb.classes.dict.graph import GRAPH_FIELD, GraphDict
from nx_arangodb.classes.dict.node import NodeAttrDict, NodeDict

from .conftest import db
Expand Down Expand Up @@ -463,11 +463,11 @@ def test_graph_attr(self):
assert isinstance(G.graph, GraphDict)
assert G.graph["foo"] == "bar"
del G.graph["foo"]
graph_doc = get_doc(f"nxadb_graphs/{GRAPH_NAME}")
graph_doc = get_doc(f"_graphs/{GRAPH_NAME}")[GRAPH_FIELD]
assert G.graph == graph_doc
H = self.K3Graph(foo="bar")
assert H.graph["foo"] == "bar"
graph_doc = get_doc(f"nxadb_graphs/{GRAPH_NAME}")
graph_doc = get_doc(f"_graphs/{GRAPH_NAME}")[GRAPH_FIELD]
assert H.graph == graph_doc

def test_node_attr(self):
Expand Down Expand Up @@ -1105,7 +1105,7 @@ def test_update(self):
else:
for src, dst in G.edges():
assert G.adj[dst][src] == G.adj[src][dst]
assert G.graph == get_doc(G.graph.graph_id)
assert G.graph == get_doc(G.graph.graph_id)[GRAPH_FIELD]

# no keywords -- order is edges, nodes
G = self.K3Graph()
Expand All @@ -1126,7 +1126,7 @@ def test_update(self):
else:
for src, dst in G.edges():
assert G.adj[dst][src] == G.adj[src][dst]
assert G.graph == get_doc(G.graph.graph_id)
assert G.graph == get_doc(G.graph.graph_id)[GRAPH_FIELD]

# update using only a graph
G = self.K3Graph()
Expand Down
Loading