Skip to content

[GA-153-2] AdjListInnerDict Update implementation (new) #34

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

Merged
merged 67 commits into from
Aug 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
f85d4bf
remove not needed imports, fix typos
hkernbach Aug 15, 2024
20b989b
moved over code from pr
hkernbach Aug 15, 2024
1fcbb10
fmt and lint
hkernbach Aug 15, 2024
9d8d1f6
Merge remote-tracking branch 'origin/main' into feature/node-dict
hkernbach Aug 15, 2024
22d0cd9
fix code, added test for graphs, added todo
hkernbach Aug 15, 2024
1173880
adapt MultiGraph to old code
hkernbach Aug 15, 2024
492a6c9
flake8
hkernbach Aug 15, 2024
0fa5db4
removed auto added import
hkernbach Aug 15, 2024
dce4e85
add update method to CustomNodeView
hkernbach Aug 16, 2024
73c194e
update_local_nodes as private method
hkernbach Aug 16, 2024
1c6b158
user logger instead of warnings
hkernbach Aug 16, 2024
a0de6e8
remove assertion, raise in case wrong key is given
hkernbach Aug 16, 2024
685802d
move test only func into tst, removed unused func
hkernbach Aug 16, 2024
bcc6ddf
remove import
hkernbach Aug 16, 2024
6cd34e5
TODO WIP
hkernbach Aug 16, 2024
17f4323
Merge branch 'feature/node-dict' into feature/edge-attr-dict
hkernbach Aug 16, 2024
abc65fb
fix typo
hkernbach Aug 16, 2024
e38dfac
Merge branch 'feature/node-dict' into feature/edge-attr-dict
hkernbach Aug 16, 2024
fd9dc4b
move over code, will be broken as it is now
hkernbach Aug 16, 2024
1ff708a
disabled this for now
hkernbach Aug 16, 2024
472ea76
Merge branch 'feature/edge-attr-dict' into feature/adj-list-inner-dict
hkernbach Aug 16, 2024
debf161
fmt
hkernbach Aug 16, 2024
e9b14e1
Merge remote-tracking branch 'origin/main' into feature/node-dict
hkernbach Aug 16, 2024
fcd4b14
fix mypy
hkernbach Aug 16, 2024
54048aa
Merge remote-tracking branch 'origin/main' into feature/node-dict
hkernbach Aug 16, 2024
770b7a6
Merge remote-tracking branch 'origin/main' into feature/node-dict
hkernbach Aug 16, 2024
00e5fe8
py to 3.12
hkernbach Aug 16, 2024
73fa537
py to 3.12.3
hkernbach Aug 16, 2024
4c65ac4
py to 3.12.5
hkernbach Aug 16, 2024
938b30a
py to 3.12.5 ..............
hkernbach Aug 16, 2024
8cccca4
back to 3.12.2
hkernbach Aug 16, 2024
049d637
back to 3.10
hkernbach Aug 16, 2024
c98aa93
Merge branch 'feature/node-dict' into feature/edge-attr-dict
hkernbach Aug 16, 2024
e56ce4f
attempt to resolve merge conflicts
hkernbach Aug 16, 2024
f43a6d5
fixes after merge
hkernbach Aug 16, 2024
ffe843b
fix use of method
hkernbach Aug 16, 2024
776ae86
Merge branch 'feature/edge-attr-dict' into feature/adj-list-inner-dict
hkernbach Aug 16, 2024
6628066
linting
hkernbach Aug 16, 2024
b8db0d0
make awesome linter happy
hkernbach Aug 16, 2024
fb65319
seriously.....
hkernbach Aug 16, 2024
c46c35c
so wow
hkernbach Aug 16, 2024
e1d078c
added core view
hkernbach Aug 16, 2024
6217e3d
use proper class
hkernbach Aug 16, 2024
36686da
Merge branch 'feature/edge-attr-dict' into feature/adj-list-inner-dict
hkernbach Aug 19, 2024
b8ccc75
Update nx_arangodb/classes/function.py
hkernbach Aug 20, 2024
3a87bfa
Update nx_arangodb/classes/function.py
hkernbach Aug 20, 2024
a4c69f1
Update tests/test.py
hkernbach Aug 20, 2024
52e7dfd
Update tests/test.py
hkernbach Aug 20, 2024
da332e0
optimize separate_edges_by_collections
hkernbach Aug 20, 2024
cc18d04
fmt
hkernbach Aug 20, 2024
2b40b26
Merge branch 'feature/edge-attr-dict' into feature/adj-list-inner-dict
hkernbach Aug 20, 2024
7ddfe21
merge
hkernbach Aug 20, 2024
1411bd5
Merge remote-tracking branch 'origin/main' into feature/adj-list-inne…
hkernbach Aug 20, 2024
6eb3348
attempt to fix update in inner adj dict
hkernbach Aug 20, 2024
5f78fe6
also test multigraphs
hkernbach Aug 20, 2024
9459b81
fmt and mypy
hkernbach Aug 20, 2024
543afbf
sort
hkernbach Aug 20, 2024
e275fb4
remove obsolete comment
hkernbach Aug 20, 2024
063b1f9
use default node id
hkernbach Aug 20, 2024
546f0b2
fmt
hkernbach Aug 20, 2024
dab9408
remove obsolete comment
hkernbach Aug 20, 2024
af99113
fix set adj elements by providing update
hkernbach Aug 21, 2024
d72dd26
Update tests/test.py
hkernbach Aug 21, 2024
e1879bd
Update tests/test.py
hkernbach Aug 21, 2024
89faf1a
Merge branch 'main' into feature/adj-list-inner-dict
hkernbach Aug 21, 2024
0226ef2
Merge branch 'main' into feature/adj-list-inner-dict
aMahanna Aug 21, 2024
f11be58
fix test to new api
hkernbach Aug 22, 2024
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
9 changes: 9 additions & 0 deletions nx_arangodb/classes/coreviews.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,12 @@ class CustomAdjacencyView(nx.classes.coreviews.AdjacencyView):

def update(self, data):
return self._atlas.update(data)

def __getitem__(self, name):
return CustomAtlasView(self._atlas[name])


class CustomAtlasView(nx.classes.coreviews.AtlasView):

def update(self, data):
return self._atlas.update(data)
64 changes: 60 additions & 4 deletions nx_arangodb/classes/dict/adj.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from collections import UserDict
from collections.abc import Iterator
from itertools import islice
from typing import Any, Callable
from typing import Any, Callable, Dict, List

from arango.database import StandardDatabase
from arango.exceptions import DocumentDeleteError
Expand Down Expand Up @@ -40,13 +40,15 @@
get_node_id,
get_node_type_and_id,
get_update_dict,
is_arangodb_id,
json_serializable,
key_is_adb_id_or_int,
key_is_not_reserved,
key_is_string,
keys_are_not_reserved,
keys_are_strings,
logger_debug,
read_collection_name_from_local_id,
separate_edges_by_collections,
upsert_collection_edges,
)
Expand Down Expand Up @@ -1196,7 +1198,50 @@ def clear(self) -> None:
@logger_debug
def update(self, edges: Any) -> None:
"""g._adj['node/1'].update({'node/2': {'foo': 'bar'}})"""
raise NotImplementedError("AdjListInnerDict.update()")
from_col_name = read_collection_name_from_local_id(
self.src_node_id, self.default_node_type
)

to_upsert: Dict[str, List[Dict[str, Any]]] = {from_col_name: []}

for edge_id, edge_data in edges.items():
edge_doc = edge_data
edge_doc["_from"] = self.src_node_id
edge_doc["_to"] = edge_id

edge_doc_id = edge_data.get("_id")
assert is_arangodb_id(edge_doc_id)
edge_col_name = read_collection_name_from_local_id(
edge_doc_id, self.default_node_type
)

if to_upsert.get(edge_col_name) is None:
to_upsert[edge_col_name] = [edge_doc]
else:
to_upsert[edge_col_name].append(edge_doc)

# perform write to ArangoDB
result = upsert_collection_edges(self.db, to_upsert)

all_good = check_list_for_errors(result)
if all_good:
# Means no single operation failed, in this case we update the local cache
self.__set_adj_elements(edges)
else:
# In this case some or all documents failed. Right now we will not
# update the local cache, but raise an error instead.
# Reason: We cannot set silent to True, because we need as it does
# not report errors then. We need to update the driver to also pass
# the errors back to the user, then we can adjust the behavior here.
# This will also save network traffic and local computation time.
errors = []
for collections_results in result:
for collection_result in collections_results:
errors.append(collection_result)
logger.warning(
"Failed to insert at least one node. Will not update local cache."
)
raise ArangoDBBatchError(errors)

@logger_debug
def values(self) -> Any:
Expand Down Expand Up @@ -1242,14 +1287,25 @@ def _fetch_all(self) -> None:
self.FETCHED_ALL_DATA = True
self.FETCHED_ALL_IDS = True

def __set_adj_elements(self, edges):
for dst_node_id, edge in edges.items():
edge_attr_dict: EdgeAttrDict = self._create_edge_attr_dict(edge)

self.__fetch_all_helper(edge_attr_dict, dst_node_id, is_update=True)

@logger_debug
def __fetch_all_graph(self, edge_attr_dict: EdgeAttrDict, dst_node_id: str) -> None:
def __fetch_all_graph(
self, edge_attr_dict: EdgeAttrDict, dst_node_id: str, is_update: bool = False
) -> None:
"""Helper function for _fetch_all() in Graphs."""
if dst_node_id in self.data:
# Don't raise an error if it's a self-loop
if self.data[dst_node_id] == edge_attr_dict:
return

if is_update:
return

m = "Multiple edges between the same nodes are not supported in Graphs."
m += f" Found 2 edges between {self.src_node_id} & {dst_node_id}."
m += " Consider using a MultiGraph."
Expand All @@ -1259,7 +1315,7 @@ def __fetch_all_graph(self, edge_attr_dict: EdgeAttrDict, dst_node_id: str) -> N

@logger_debug
def __fetch_all_multigraph(
self, edge_attr_dict: EdgeAttrDict, dst_node_id: str
self, edge_attr_dict: EdgeAttrDict, dst_node_id: str, is_update: bool = False
) -> None:
"""Helper function for _fetch_all() in MultiGraphs."""
edge_key_dict = self.data.get(dst_node_id)
Expand Down
23 changes: 22 additions & 1 deletion nx_arangodb/classes/function.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

from __future__ import annotations

from typing import Any, Callable, Tuple
from typing import Any, Callable, Optional, Tuple

import networkx as nx
from arango import ArangoError, DocumentInsertError
Expand Down Expand Up @@ -710,6 +710,27 @@ def get_arangodb_collection_key_tuple(key):
return key.split("/", 1)


def extract_arangodb_collection_name(arangodb_id: str) -> str:
if not is_arangodb_id(arangodb_id):
raise ValueError(f"Invalid ArangoDB key: {arangodb_id}")
return arangodb_id.split("/")[0]


def read_collection_name_from_local_id(
local_id: Optional[str], default_collection: str
) -> str:
if local_id is None:
print("local_id is None, cannot read collection name.")
return ""

if is_arangodb_id(local_id):
return extract_arangodb_collection_name(local_id)

assert default_collection is not None
assert default_collection != ""
return default_collection


def separate_nodes_by_collections(nodes: Any, default_collection: str) -> Any:
"""
Separate the dictionary into collections based on whether keys contain '/'.
Expand Down
95 changes: 94 additions & 1 deletion tests/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
)

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

from .conftest import create_line_graph, db
Expand Down Expand Up @@ -626,6 +626,99 @@ def test_edge_dict_update_multiple_collections(load_two_relation_graph: Any) ->
assert f"{v_1_name}/{3}" in local_edge_cache[f"{v_2_name}/{1}"]


@pytest.mark.parametrize(
"graph_cls",
[
(nxadb.Graph),
(nxadb.DiGraph),
],
)
def test_edge_adj_inner_dict_update_existing_single_collection(
load_karate_graph: Any, graph_cls: type[nxadb.Graph]
) -> None:
G_1 = graph_cls(name="KarateGraph", foo="bar", use_experimental_views=True)

local_adj = G_1.adj
local_inner_edges_dict: GraphAdjDict = {}
from_doc_id_to_use: str = "person/9"

target_dict = local_adj[from_doc_id_to_use]
for to_doc_id, edge_doc in target_dict.items():
# will contain three items/documents
edge_doc_id = edge_doc["_id"]
local_inner_edges_dict[to_doc_id] = {
"_id": edge_doc_id,
"extraValue": edge_doc["_key"],
}

G_1.adj[from_doc_id_to_use].update(local_inner_edges_dict)

edge_col = db.collection("knows")
edge_col_docs = edge_col.all()

# Check if the extraValue attribute was added to requested docs in ADB
for doc in edge_col_docs:
if doc["_from"] == from_doc_id_to_use:
assert "extraValue" in doc
assert doc["extraValue"] == doc["_key"]

# Check if the extraValue attribute was added to each document in the local cache
for to_doc_id in local_inner_edges_dict.keys():
assert "extraValue" in G_1._adj[from_doc_id_to_use][to_doc_id]
assert G_1.adj[from_doc_id_to_use][to_doc_id][
"extraValue"
] == extract_arangodb_key(local_inner_edges_dict[to_doc_id]["_id"])
return


@pytest.mark.parametrize(
"graph_cls",
[
(nxadb.MultiGraph),
(nxadb.MultiDiGraph),
],
)
def test_edge_adj_inner_dict_update_existing_single_collection_multi_graphs(
load_karate_graph: Any, graph_cls: type[nxadb.Graph]
) -> None:
G_1 = graph_cls(name="KarateGraph", foo="bar", use_experimental_views=True)

local_adj = G_1.adj
local_inner_edges_dict: GraphAdjDict = {}
from_doc_id_to_use: str = "person/9"

target_dict = local_adj[from_doc_id_to_use]
for outer_to_doc_id, edge_key_dict in target_dict.items():
assert isinstance(edge_key_dict, EdgeKeyDict)

for to_doc_id, edge_doc in edge_key_dict.items():
edge_doc_id = edge_doc["_id"]
local_inner_edges_dict[to_doc_id] = {
"_id": edge_doc_id,
"extraValue": edge_doc["_key"],
}

G_1.adj[from_doc_id_to_use].update(local_inner_edges_dict)

edge_col = db.collection("knows")
edge_col_docs = edge_col.all()

# Check if the extraValue attribute was added to requested docs in ADB
for doc in edge_col_docs:
if doc["_from"] == from_doc_id_to_use:
assert "extraValue" in doc
assert doc["extraValue"] == doc["_key"]

# Check if the extraValue attribute was added to each document in the local cache
for to_doc_id in local_inner_edges_dict.keys():
assert to_doc_id in G_1.adj[from_doc_id_to_use][to_doc_id]

assert "extraValue" in G_1.adj[from_doc_id_to_use][to_doc_id][to_doc_id]
assert G_1.adj[from_doc_id_to_use][to_doc_id][to_doc_id][
"extraValue"
] == extract_arangodb_key(local_inner_edges_dict[to_doc_id]["_id"])


@pytest.mark.parametrize(
"graph_cls",
[
Expand Down