Skip to content

more housekeeping #7

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 11 commits into from
Jul 4, 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
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -122,4 +122,7 @@ node_modules/
# test results
*_results.txt

*.egg-info
*.egg-info

# VSCode
.vscode/
2 changes: 1 addition & 1 deletion nx_arangodb/classes/digraph.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def to_networkx_class(cls) -> type[nx.DiGraph]:
def __init__(
self,
graph_name: str | None = None,
# default_node_type: str = "nxadb_nodes",
# default_node_type: str = "node",
# edge_type_func: Callable[[str, str], str] = lambda u, v: f"{u}_to_{v}",
*args: Any,
**kwargs: Any,
Expand Down
50 changes: 39 additions & 11 deletions nx_arangodb/classes/function.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,12 @@ def get_arangodb_graph(
def key_is_string(func: Callable[..., Any]) -> Any:
"""Decorator to check if the key is a string."""

def wrapper(self: Any, key: str, *args: Any, **kwargs: Any) -> Any:
def wrapper(self: Any, key: Any, *args: Any, **kwargs: Any) -> Any:
if not isinstance(key, str):
raise TypeError(f"'{key}' is not a string.")
if not isinstance(key, (int, float)):
raise TypeError(f"{key} cannot be casted to string.")

key = str(key)

return func(self, key, *args, **kwargs)

Expand All @@ -98,12 +101,29 @@ def wrapper(self: Any, key: str, *args: Any, **kwargs: Any) -> Any:
def keys_are_strings(func: Callable[..., Any]) -> Any:
"""Decorator to check if the keys are strings."""

def wrapper(self: Any, dict: dict[Any, Any], *args: Any, **kwargs: Any) -> Any:
for key in dict:
def wrapper(
self: Any, data: dict[Any, Any] | zip[Any], *args: Any, **kwargs: Any
) -> Any:
data_dict = {}

items: Any
if isinstance(data, dict):
items = data.items()
elif isinstance(data, zip):
items = list(data)
else:
raise TypeError(f"Decorator found unsupported type: {type(data)}.")

for key, value in items:
if not isinstance(key, str):
raise TypeError(f"'{key}' is not a string.")
if not isinstance(key, (int, float)):
raise TypeError(f"{key} cannot be casted to string.")

key = str(key)

data_dict[key] = value

return func(self, dict, *args, **kwargs)
return func(self, data_dict, *args, **kwargs)

return wrapper

Expand All @@ -126,12 +146,22 @@ def wrapper(self: Any, key: str, *args: Any, **kwargs: Any) -> Any:
def keys_are_not_reserved(func: Any) -> Any:
"""Decorator to check if the keys are not reserved."""

def wrapper(self: Any, dict: dict[Any, Any], *args: Any, **kwargs: Any) -> Any:
for key in dict:
def wrapper(
self: Any, data: dict[Any, Any] | zip[Any], *args: Any, **kwargs: Any
) -> Any:
keys: Any
if isinstance(data, dict):
keys = data.keys()
elif isinstance(data, zip):
keys = (key for key, _ in list(data))
else:
raise TypeError(f"Decorator found unsupported type: {type(data)}.")

for key in keys:
if key in RESERVED_KEYS:
raise KeyError(f"'{key}' is a reserved key.")

return func(self, dict, *args, **kwargs)
return func(self, data, *args, **kwargs)

return wrapper

Expand Down Expand Up @@ -229,7 +259,6 @@ def aql_edge_get(
graph_name: str,
direction: str,
) -> Any | None:
# TODO: need the use of DISTINCT
return_clause = "DISTINCT e" if direction == "ANY" else "e"
return aql_edge(
db,
Expand All @@ -248,7 +277,6 @@ def aql_edge_id(
graph_name: str,
direction: str,
) -> str | None:
# TODO: need the use of DISTINCT
return_clause = "DISTINCT e._id" if direction == "ANY" else "e._id"
result = aql_edge(
db,
Expand Down
69 changes: 38 additions & 31 deletions nx_arangodb/classes/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,16 +41,17 @@ def to_networkx_class(cls) -> type[nx.Graph]:
def __init__(
self,
graph_name: str | None = None,
default_node_type: str = "nxadb_node",
default_node_type: str = "node",
edge_type_func: Callable[[str, str], str] = lambda u, v: f"{u}_to_{v}",
db: StandardDatabase | None = None,
*args: Any,
**kwargs: Any,
):
self.__db = None
self.__graph_name = None
self.__graph_exists_in_db = False

self.__set_db()
self.__set_db(db)
if self.__db is not None:
self.__set_graph_name(graph_name)

Expand All @@ -73,41 +74,47 @@ def __init__(
self.edge_type_func = edge_type_func
self.default_edge_type = edge_type_func(default_node_type, default_node_type)

# self.__qa_chain = None

incoming_graph_data = kwargs.get("incoming_graph_data")
if self.__graph_exists_in_db:
self.adb_graph = self.db.graph(graph_name)
self.__create_default_collections()
self.__set_factory_methods()

if incoming_graph_data:
if incoming_graph_data is not None:
m = "Cannot pass both **incoming_graph_data** and **graph_name** yet if the already graph exists" # noqa: E501
raise NotImplementedError(m)

elif self.__graph_name and incoming_graph_data:
if not isinstance(incoming_graph_data, nx.Graph):
m = f"Type of **incoming_graph_data** not supported yet ({type(incoming_graph_data)})" # noqa: E501
raise NotImplementedError(m)
self.adb_graph = self.db.graph(self.__graph_name)
self.__create_default_collections()
self.__set_factory_methods()

adapter = ADBNX_Adapter(self.db)
self.adb_graph = adapter.networkx_to_arangodb(
graph_name,
incoming_graph_data,
# TODO: Parameterize the edge definitions
# How can we work with a heterogenous **incoming_graph_data**?
edge_definitions=[
{
"edge_collection": self.default_edge_type,
"from_vertex_collections": [self.default_node_type],
"to_vertex_collections": [self.default_node_type],
}
],
)
elif self.__graph_name and incoming_graph_data is not None:
# TODO: Parameterize the edge definitions
# How can we work with a heterogenous **incoming_graph_data**?
edge_definitions = [
{
"edge_collection": self.default_edge_type,
"from_vertex_collections": [self.default_node_type],
"to_vertex_collections": [self.default_node_type],
}
]

if isinstance(incoming_graph_data, nx.Graph):
self.adb_graph = ADBNX_Adapter(self.db).networkx_to_arangodb(
self.__graph_name,
incoming_graph_data,
edge_definitions=edge_definitions,
)

# No longer need this (we've already populated the graph)
del kwargs["incoming_graph_data"]

else:
self.adb_graph = self.db.create_graph(
self.__graph_name,
edge_definitions=edge_definitions,
)

self.__set_factory_methods()
self.__graph_exists_in_db = True
del kwargs["incoming_graph_data"]

# self.__qa_chain = None

super().__init__(*args, **kwargs)

Expand Down Expand Up @@ -187,6 +194,7 @@ def __set_db(self, db: StandardDatabase | None = None) -> None:
m = "arango.database.StandardDatabase"
raise TypeError(m)

db.version()
self.__db = db
return

Expand Down Expand Up @@ -232,7 +240,6 @@ def __set_graph_name(self, graph_name: str | None = None) -> None:
# ArangoDB Methods #
####################

# TODO: proper subgraphing!
def aql(self, query: str, bind_vars: dict[str, Any] = {}, **kwargs: Any) -> Cursor:
return nxadb.classes.function.aql(self.db, query, bind_vars, **kwargs)

Expand Down Expand Up @@ -267,12 +274,12 @@ def pull(self, load_node_dict=True, load_adj_dict=True, load_coo=True):
:param load_node_dict: Load the node dictionary.
Enabling this option will clear the existing node dictionary,
and replace it with the node data from the database. Comes with
a remote reference to the database. <--- TODO: Should we paramaterize this?
a remote reference to the database.
:type load_node_dict: bool
:param load_adj_dict: Load the adjacency dictionary.
Enabling this option will clear the existing adjacency dictionary,
and replace it with the edge data from the database. Comes with
a remote reference to the database. <--- TODO: Should we paramaterize this?
a remote reference to the database.
:type load_adj_dict: bool
:param load_coo: Load the COO representation. If False, the src & dst
indices will be empty, along with the node-ID-to-index mapping.
Expand Down
9 changes: 1 addition & 8 deletions nx_arangodb/convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,6 @@ def from_networkx(
) -> nxadb.Graph | nxadb.DiGraph:
"""Convert a networkx graph to nx_arangodb graph.

TEMPORARY ASSUMPTION: The nx_arangodb Graph is a subclass of networkx Graph.
Therefore, I'm going to assume that we _should_ be able instantiate an
nx_arangodb Graph using the **incoming_graph_data** parameter.

TODO: The actual implementation should store the graph in ArangoDB.

Parameters
----------
G : networkx.Graph
Expand Down Expand Up @@ -187,8 +181,7 @@ def _to_nx_graph(
if isinstance(G, nx.Graph):
return G

# TODO: handle cugraph.Graph
raise TypeError
raise TypeError(f"Expected nx_arangodb.Graph or nx.Graph; got {type(G)}")


if GPU_ENABLED:
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ dev = [
"Flake8-pyproject",
"isort",
"mypy",
"pandas",
]
gpu = [
"nx-cugraph-cu12 @ https://pypi.nvidia.com"
Expand Down
2 changes: 1 addition & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def pytest_addoption(parser: Any) -> None:
parser.addoption("--url", action="store", default="http://localhost:8529")
parser.addoption("--dbName", action="store", default="_system")
parser.addoption("--username", action="store", default="root")
parser.addoption("--password", action="store", default="passwd")
parser.addoption("--password", action="store", default="test")


def pytest_configure(config: Any) -> None:
Expand Down
2 changes: 1 addition & 1 deletion tests/static/cluster.conf
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ port = 8528
jwt-secret = /tests/static/keyfile

[args]
all.database.password = passwd
all.database.password = test
all.database.extended-names = true
all.log.api-enabled = true
all.javascript.allow-admin-execute = true
Expand Down
Loading