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

wip: mirror_crud_to_nxcg #65

Draft
wants to merge 10 commits into
base: main
Choose a base branch
from
Draft
Changes from 1 commit
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
Next Next commit
wip: mirror_crud_to_nxcg
aMahanna committed Oct 15, 2024

Verified

This commit was signed with the committer’s verified signature.
aMahanna Anthony Mahanna
commit d9e7e74a97be0bc12a904faab2dfa0d990b9dda1
2 changes: 1 addition & 1 deletion nx_arangodb/classes/dict/adj.py
Original file line number Diff line number Diff line change
@@ -1815,7 +1815,7 @@ def propagate_edge_directed_symmetric(
set_adj_inner_dict_mirror(src_node_id)
set_adj_inner_dict_mirror(dst_node_id)

edge_attr_or_key_dict = set_edge_func( # type: ignore[operator]
edge_attr_or_key_dict = set_edge_func(
src_node_id, dst_node_id, edge_or_edges
)

35 changes: 33 additions & 2 deletions nx_arangodb/classes/digraph.py
Original file line number Diff line number Diff line change
@@ -9,7 +9,7 @@

from .dict.adj import AdjListOuterDict
from .enum import TraversalDirection
from .function import get_node_id
from .function import get_node_id, mirror_to_nxcg

networkx_api = nxadb.utils.decorators.networkx_class(nx.DiGraph) # type: ignore

@@ -161,6 +161,7 @@ def __init__(
symmetrize_edges: bool = False,
use_arango_views: bool = False,
overwrite_graph: bool = False,
mirror_crud_to_nxcg: bool = False,
*args: Any,
**kwargs: Any,
):
@@ -179,16 +180,23 @@ def __init__(
symmetrize_edges,
use_arango_views,
overwrite_graph,
mirror_crud_to_nxcg,
*args,
**kwargs,
)

if self.graph_exists_in_db:
self.clear_edges = self.clear_edges_override
self.reverse = self.reverse_override

self.add_node = self.add_node_override
self.add_nodes_from = self.add_nodes_from_override
self.remove_node = self.remove_node_override
self.reverse = self.reverse_override
self.remove_nodes_from = self.remove_nodes_from_override
self.add_edge = self.add_edge_override
self.add_edges_from = self.add_edges_from_override
self.remove_edge = self.remove_edge_override
self.remove_edges_from = self.remove_edges_from_override

assert isinstance(self._succ, AdjListOuterDict)
assert isinstance(self._pred, AdjListOuterDict)
@@ -234,6 +242,7 @@ def clear_edges_override(self):

super().clear_edges()

@mirror_to_nxcg
def add_node_override(self, node_for_adding, **attr):
if node_for_adding is None:
raise ValueError("None cannot be a node")
@@ -269,6 +278,7 @@ def add_node_override(self, node_for_adding, **attr):

nx._clear_cache(self)

@mirror_to_nxcg
def add_nodes_from_override(self, nodes_for_adding, **attr):
for n in nodes_for_adding:
try:
@@ -312,6 +322,7 @@ def add_nodes_from_override(self, nodes_for_adding, **attr):

nx._clear_cache(self)

@mirror_to_nxcg
def remove_node_override(self, n):
if isinstance(n, (str, int)):
n = get_node_id(str(n), self.default_node_type)
@@ -361,3 +372,23 @@ def remove_node_override(self, n):
del self._succ[u][n] # remove all edges n-u in digraph
del self._pred[n] # remove node from pred
nx._clear_cache(self)

@mirror_to_nxcg
def remove_nodes_from_override(self, nodes):
nx.DiGraph.remove_nodes_from(self, nodes)

@mirror_to_nxcg
def add_edge_override(self, u, v, **attr):
nx.DiGraph.add_edge(self, u, v, **attr)

@mirror_to_nxcg
def add_edges_from_override(self, ebunch_to_add, **attr):
nx.DiGraph.add_edges_from(self, ebunch_to_add, **attr)

@mirror_to_nxcg
def remove_edge_override(self, u, v):
nx.DiGraph.remove_edge(self, u, v)

@mirror_to_nxcg
def remove_edges_from_override(self, ebunch):
nx.DiGraph.remove_edges_from(self, ebunch)
12 changes: 12 additions & 0 deletions nx_arangodb/classes/function.py
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@

from __future__ import annotations

from functools import wraps
from typing import Any, Callable, Generator, Tuple

import networkx as nx
@@ -932,3 +933,14 @@ def upsert_collection_edges(
)

return results


def mirror_to_nxcg(func):
@wraps(func)
def wrapper(self, *args, **kwargs):
result = func(self, *args, **kwargs)
if self.mirror_to_nxcg and self.nxcg_graph is not None:
getattr(self.nxcg_graph, func.__name__)(*args, **kwargs)
return result

return wrapper
200 changes: 122 additions & 78 deletions nx_arangodb/classes/graph.py
Original file line number Diff line number Diff line change
@@ -29,7 +29,7 @@
node_attr_dict_factory,
node_dict_factory,
)
from .function import get_node_id
from .function import get_node_id, mirror_to_nxcg
from .reportviews import ArangoEdgeView, ArangoNodeView

networkx_api = nxadb.utils.decorators.networkx_class(nx.Graph) # type: ignore
@@ -195,12 +195,14 @@ def __init__(
symmetrize_edges: bool = False,
use_arango_views: bool = False,
overwrite_graph: bool = False,
mirror_crud_to_nxcg: bool = False,
*args: Any,
**kwargs: Any,
):
self.__db = None
self.__use_arango_views = use_arango_views
self.__graph_exists_in_db = False
self.__mirror_crud_to_nxcg = mirror_crud_to_nxcg

self.__set_db(db)
if all([self.__db, name]):
@@ -261,11 +263,19 @@ def __init__(
self.subgraph = self.subgraph_override
self.clear = self.clear_override
self.clear_edges = self.clear_edges_override
self.add_node = self.add_node_override
self.add_nodes_from = self.add_nodes_from_override
self.number_of_edges = self.number_of_edges_override
self.nbunch_iter = self.nbunch_iter_override

self.add_node = self.add_node_override
self.add_nodes_from = self.add_nodes_from_override
self.remove_node = self.remove_node_override
self.remove_nodes_from = self.remove_nodes_from_override
self.add_edge = self.add_edge_override
self.add_edges_from = self.add_edges_from_override
self.remove_edge = self.remove_edge_override
self.remove_edges_from = self.remove_edges_from_override
self.update = self.update_override

# If incoming_graph_data wasn't loaded by the NetworkX Adapter,
# then we can rely on the CRUD operations of the modified dictionaries
# to load the data into the graph. However, if the graph is directed
@@ -541,6 +551,10 @@ def is_smart(self) -> bool:
def smart_field(self) -> str | None:
return self.__smart_field

@property
def mirror_crud_to_nxcg(self) -> bool:
return self.__mirror_crud_to_nxcg

###########
# Setters #
###########
@@ -691,81 +705,6 @@ def clear_edges_override(self):
nbr_dict.clear()
nx._clear_cache(self)

def add_node_override(self, node_for_adding, **attr):
if node_for_adding is None:
raise ValueError("None cannot be a node")

if node_for_adding not in self._node:
self._adj[node_for_adding] = self.adjlist_inner_dict_factory()

######################
# NOTE: monkey patch #
######################

# Old:
# attr_dict = self._node[node_for_adding] = self.node_attr_dict_factory()
# attr_dict.update(attr)

# New:
node_attr_dict = self.node_attr_dict_factory()
node_attr_dict.data = attr
self._node[node_for_adding] = node_attr_dict

# Reason:
# We can optimize the process of adding a node by creating avoiding
# the creation of a new dictionary and updating it with the attributes.
# Instead, we can create a new node_attr_dict object and set the attributes
# directly. This only makes 1 network call to the database instead of 2.

###########################

else:
self._node[node_for_adding].update(attr)

nx._clear_cache(self)

def add_nodes_from_override(self, nodes_for_adding, **attr):
for n in nodes_for_adding:
try:
newnode = n not in self._node
newdict = attr
except TypeError:
n, ndict = n
newnode = n not in self._node
newdict = attr.copy()
newdict.update(ndict)
if newnode:
if n is None:
raise ValueError("None cannot be a node")
self._adj[n] = self.adjlist_inner_dict_factory()

######################
# NOTE: monkey patch #
######################

# Old:
# self._node[n] = self.node_attr_dict_factory()
#
# self._node[n].update(newdict)

# New:
node_attr_dict = self.node_attr_dict_factory()
node_attr_dict.data = newdict
self._node[n] = node_attr_dict

else:
self._node[n].update(newdict)

# Reason:
# We can optimize the process of adding a node by creating avoiding
# the creation of a new dictionary and updating it with the attributes.
# Instead, we create a new node_attr_dict object and set the attributes
# directly. This only makes 1 network call to the database instead of 2.

###########################

nx._clear_cache(self)

def number_of_edges_override(self, u=None, v=None):
if u is not None:
return super().number_of_edges(u, v)
@@ -847,3 +786,108 @@ def bunch_iter(nlist, adj):

bunch = bunch_iter(nbunch, self._adj)
return bunch

@mirror_to_nxcg
def add_node_override(self, node_for_adding, **attr):
if node_for_adding is None:
raise ValueError("None cannot be a node")

if node_for_adding not in self._node:
self._adj[node_for_adding] = self.adjlist_inner_dict_factory()

######################
# NOTE: monkey patch #
######################

# Old:
# attr_dict = self._node[node_for_adding] = self.node_attr_dict_factory()
# attr_dict.update(attr)

# New:
node_attr_dict = self.node_attr_dict_factory()
node_attr_dict.data = attr
self._node[node_for_adding] = node_attr_dict

# Reason:
# We can optimize the process of adding a node by creating avoiding
# the creation of a new dictionary and updating it with the attributes.
# Instead, we can create a new node_attr_dict object and set the attributes
# directly. This only makes 1 network call to the database instead of 2.

###########################

else:
self._node[node_for_adding].update(attr)

nx._clear_cache(self)

@mirror_to_nxcg
def add_nodes_from_override(self, nodes_for_adding, **attr):
for n in nodes_for_adding:
try:
newnode = n not in self._node
newdict = attr
except TypeError:
n, ndict = n
newnode = n not in self._node
newdict = attr.copy()
newdict.update(ndict)
if newnode:
if n is None:
raise ValueError("None cannot be a node")
self._adj[n] = self.adjlist_inner_dict_factory()

######################
# NOTE: monkey patch #
######################

# Old:
# self._node[n] = self.node_attr_dict_factory()
#
# self._node[n].update(newdict)

# New:
node_attr_dict = self.node_attr_dict_factory()
node_attr_dict.data = newdict
self._node[n] = node_attr_dict

else:
self._node[n].update(newdict)

# Reason:
# We can optimize the process of adding a node by creating avoiding
# the creation of a new dictionary and updating it with the attributes.
# Instead, we create a new node_attr_dict object and set the attributes
# directly. This only makes 1 network call to the database instead of 2.

###########################

nx._clear_cache(self)

@mirror_to_nxcg
def remove_node_override(self, n):
super().remove_node(n)

@mirror_to_nxcg
def remove_nodes_from_override(self, nodes):
super().remove_nodes_from(nodes)

@mirror_to_nxcg
def add_edge_override(self, u, v, **attr):
super().add_edge(u, v, **attr)

@mirror_to_nxcg
def add_edges_from_override(self, ebunch_to_add, **attr):
super().add_edges_from(ebunch_to_add, **attr)

@mirror_to_nxcg
def remove_edge_override(self, u, v):
super().remove_edge(u, v)

@mirror_to_nxcg
def remove_edges_from_override(self, ebunch):
super().remove_edges_from(ebunch)

@mirror_to_nxcg
def update_override(self, *args, **kwargs):
super().update(*args, **kwargs)
21 changes: 19 additions & 2 deletions nx_arangodb/classes/multidigraph.py
Original file line number Diff line number Diff line change
@@ -8,6 +8,8 @@
from nx_arangodb.classes.digraph import DiGraph
from nx_arangodb.classes.multigraph import MultiGraph

from .function import mirror_to_nxcg

networkx_api = nxadb.utils.decorators.networkx_class(nx.MultiDiGraph) # type: ignore

__all__ = ["MultiDiGraph"]
@@ -172,6 +174,7 @@ def __init__(
symmetrize_edges: bool = False,
use_arango_views: bool = False,
overwrite_graph: bool = False,
mirror_crud_to_nxcg: bool = False,
*args: Any,
**kwargs: Any,
):
@@ -191,6 +194,7 @@ def __init__(
symmetrize_edges,
use_arango_views,
overwrite_graph,
mirror_crud_to_nxcg,
*args,
**kwargs,
)
@@ -199,6 +203,9 @@ def __init__(
self.reverse = self.reverse_override
self.to_undirected = self.to_undirected_override

self.add_edge = self.add_edge_override
self.remove_edge = self.remove_edge_override

#######################
# Init helper methods #
#######################
@@ -211,11 +218,13 @@ def reverse_override(self, copy: bool = True) -> Any:
if copy is False:
raise NotImplementedError("In-place reverse is not supported yet.")

return super().reverse(copy=True)
return nx.MultiDiGraph.reverse(self, copy=True)

def to_undirected_override(self, reciprocal=False, as_view=False):
if reciprocal is False:
return super().to_undirected(reciprocal=False, as_view=as_view)
return nx.MultiDiGraph.to_undirected(
self, reciprocal=False, as_view=as_view
)

graph_class = self.to_undirected_class()
if as_view is True:
@@ -257,3 +266,11 @@ def to_undirected_override(self, reciprocal=False, as_view=False):
###########################

return G

@mirror_to_nxcg
def add_edge_override(self, u, v, key=None, **attr):
nx.MultiDiGraph.add_edge(self, u, v, key, **attr)

@mirror_to_nxcg
def remove_edge_override(self, u, v, key=None):
nx.MultiDiGraph.remove_edge(self, u, v, key)
82 changes: 51 additions & 31 deletions nx_arangodb/classes/multigraph.py
Original file line number Diff line number Diff line change
@@ -8,6 +8,7 @@
from nx_arangodb.logger import logger

from .dict import edge_key_dict_factory
from .function import mirror_to_nxcg

networkx_api = nxadb.utils.decorators.networkx_class(nx.MultiGraph) # type: ignore

@@ -173,6 +174,7 @@ def __init__(
symmetrize_edges: bool = False,
use_arango_views: bool = False,
overwrite_graph: bool = False,
mirror_crud_to_nxcg: bool = False,
*args: Any,
**kwargs: Any,
):
@@ -191,15 +193,20 @@ def __init__(
symmetrize_edges,
use_arango_views,
overwrite_graph,
mirror_crud_to_nxcg,
*args,
**kwargs,
)

if self.graph_exists_in_db:
self.add_edge = self.add_edge_override
self.has_edge = self.has_edge_override
self.copy = self.copy_override

self.add_edge = self.add_edge_override
self.add_edges_from = self.add_edges_from_override
self.remove_edge = self.remove_edge_override
self.remove_edges_from = self.remove_edges_from_override

if incoming_graph_data is not None and not self._loaded_incoming_graph_data:
# Taken from networkx.MultiGraph.__init__
if isinstance(incoming_graph_data, dict) and multigraph_input is not False:
@@ -230,7 +237,7 @@ def __init__(
#######################

def _set_factory_methods(self) -> None:
super()._set_factory_methods()
Graph._set_factory_methods(self)
self.edge_key_dict_factory = edge_key_dict_factory(
self.db,
self.adb_graph,
@@ -243,34 +250,6 @@ def _set_factory_methods(self) -> None:
# nx.MultiGraph Overides #
##########################

def add_edge_override(self, u_for_edge, v_for_edge, key=None, **attr):
if key is not None:
m = "ArangoDB MultiGraph does not support custom edge keys yet."
logger.warning(m)

_ = super().add_edge(u_for_edge, v_for_edge, key="-1", **attr)

######################
# NOTE: monkey patch #
######################

# Old:
# return key

# New:
keys = list(self._adj[u_for_edge][v_for_edge].data.keys())
last_key = keys[-1]
return last_key

# Reason:
# nxadb.MultiGraph does not yet support the ability to work
# with custom edge keys. As a Database, we must rely on the official
# ArangoDB Edge _id to uniquely identify edges. The EdgeKeyDict.__setitem__
# method will be responsible for setting the edge key to the _id of the edge
# document. This will allow us to use the edge key as a unique identifier

###########################

def has_edge_override(self, u, v, key=None):
try:
if key is None:
@@ -299,6 +278,47 @@ def has_edge_override(self, u, v, key=None):

def copy_override(self, *args, **kwargs):
logger.warning("Note that copying a graph loses the connection to the database")
G = super().copy(*args, **kwargs)
G = Graph.copy(self, *args, **kwargs)
G.edge_key_dict_factory = nx.MultiGraph.edge_key_dict_factory
return G

@mirror_to_nxcg
def add_edge_override(self, u_for_edge, v_for_edge, key=None, **attr):
if key is not None:
m = "ArangoDB MultiGraph does not support custom edge keys yet."
logger.warning(m)

_ = nx.MultiGraph.add_edge(self, u_for_edge, v_for_edge, key="-1", **attr)

######################
# NOTE: monkey patch #
######################

# Old:
# return key

# New:
keys = list(self._adj[u_for_edge][v_for_edge].data.keys())
last_key = keys[-1]
return last_key

# Reason:
# nxadb.MultiGraph does not yet support the ability to work
# with custom edge keys. As a Database, we must rely on the official
# ArangoDB Edge _id to uniquely identify edges. The EdgeKeyDict.__setitem__
# method will be responsible for setting the edge key to the _id of the edge
# document. This will allow us to use the edge key as a unique identifier

###########################

@mirror_to_nxcg
def add_edges_from_override(self, ebunch_to_add, **attr):
nx.MultiGraph.add_edges_from(self, ebunch_to_add, **attr)

@mirror_to_nxcg
def remove_edge_override(self, u, v):
nx.MultiGraph.remove_edge(self, u, v)

@mirror_to_nxcg
def remove_edges_from_override(self, ebunch):
nx.MultiGraph.remove_edges_from(self, ebunch)