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

docker-based testing #12

Merged
merged 17 commits into from
May 10, 2022
4 changes: 4 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ jobs:
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python }}
- name: Set up ArangoDB Instance via Docker
run: docker create --name adb -p 8529:8529 -e ARANGO_ROOT_PASSWORD=openSesame arangodb/arangodb:3.9.1
- name: Start ArangoDB Instance
run: docker start adb
- name: Setup pip
run: python -m pip install --upgrade pip setuptools wheel
- name: Install packages
Expand Down
49 changes: 33 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,15 @@ The Deep Graph Library (DGL) is an easy-to-use, high performance and scalable Py
* [Documentation](https://docs.dgl.ai/)
* [Highlighted Features](https://github.com/dmlc/dgl#highlighted-features)

## Installation

```
pip install adbdgl-adapter
```

## Quickstart

Get Started on Colab: <a href="https://colab.research.google.com/github/arangoml/dgl-adapter/blob/master/examples/ArangoDB_DGL_Adapter.ipynb" target="_parent"><img src="https://colab.research.google.com/assets/colab-badge.svg" alt="Open In Colab"/></a>
For a more detailed walk-through, access the official notebook on Colab: <a href="https://colab.research.google.com/github/arangoml/dgl-adapter/blob/master/examples/ArangoDB_DGL_Adapter.ipynb" target="_parent"><img src="https://colab.research.google.com/assets/colab-badge.svg" alt="Open In Colab"/></a>


```py
Expand All @@ -39,31 +45,31 @@ from adbdgl_adapter.adapter import ADBDGL_Adapter
# Import a sample graph from DGL
from dgl.data import KarateClubDataset

# This is the connection information for your ArangoDB instance
# (Let's assume that the ArangoDB fraud-detection data dump is imported to this endpoint)
# Store ArangoDB endpoint connection info
# Assumption: the ArangoDB "fraud detection" dataset is imported to this endpoint for example purposes
con = {
"hostname": "localhost",
"protocol": "http",
"hostname": "localhost",
"port": 8529,
"username": "root",
"password": "rootpassword",
"password": "openSesame",
"dbName": "_system",
}

# This instantiates your ADBDGL Adapter with your connection credentials
# Instantiate the ADBDGL Adapter with connection credentials
adbdgl_adapter = ADBDGL_Adapter(con)

# ArangoDB to DGL via Graph
# Convert ArangoDB to DGL via Graph Name
dgl_fraud_graph = adbdgl_adapter.arangodb_graph_to_dgl("fraud-detection")

# ArangoDB to DGL via Collections
# Convert ArangoDB to DGL via Collection Names
dgl_fraud_graph_2 = adbdgl_adapter.arangodb_collections_to_dgl(
"fraud-detection",
{"account", "Class", "customer"}, # Specify vertex collections
{"accountHolder", "Relationship", "transaction"}, # Specify edge collections
)

# ArangoDB to DGL via Metagraph
# Convert ArangoDB to DGL via a Metagraph
metagraph = {
"vertexCollections": {
"account": {"Balance", "account_type", "customer_id", "rank"},
Expand All @@ -76,18 +82,29 @@ metagraph = {
}
dgl_fraud_graph_3 = adbdgl_adapter.arangodb_to_dgl("fraud-detection", metagraph)

# DGL to ArangoDB
# Convert DGL to ArangoDB
dgl_karate_graph = KarateClubDataset()[0]
adb_karate_graph = adbdgl_adapter.dgl_to_arangodb("Karate", karate_dgl_g)
adb_karate_graph = adbdgl_adapter.dgl_to_arangodb("Karate", dgl_karate_graph)
```

## Development & Testing

Prerequisite: `arangorestore` must be installed
Prerequisite: `arangorestore`

1. `git clone https://github.com/arangoml/dgl-adapter.git`
2. `cd dgl-adapter`
3. `python -m venv .venv`
4. `source .venv/bin/activate` (MacOS) or `.venv/scripts/activate` (Windows)
5. `pip install -e . pytest`
6. `pytest`
3. (create virtual environment of choice)
4. `pip install -e . pytest`
5. (create an ArangoDB instance with method of choice)
6. `pytest --protocol <> --host <> --port <> --dbName <> --username <> --password <>`

**Note**: A `pytest` parameter can be omitted if the endpoint is using its default value:
```python
def pytest_addoption(parser):
parser.addoption("--protocol", action="store", default="http")
parser.addoption("--host", action="store", default="localhost")
parser.addoption("--port", action="store", default="8529")
parser.addoption("--dbName", action="store", default="_system")
parser.addoption("--username", action="store", default="root")
parser.addoption("--password", action="store", default="openSesame")
```
6 changes: 5 additions & 1 deletion adbdgl_adapter/adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@

from arango import ArangoClient
from arango.cursor import Cursor
from arango.database import StandardDatabase
from arango.graph import Graph as ArangoDBGraph
from arango.result import Result
from dgl import DGLGraph, heterograph
from dgl.heterograph import DGLHeteroGraph
from dgl.view import HeteroEdgeDataView, HeteroNodeDataView
from torch import tensor # type: ignore
from torch import tensor
from torch.functional import Tensor

from .abc import Abstract_ADBDGL_Adapter
Expand Down Expand Up @@ -55,6 +56,9 @@ def __init__(
self.__db = ArangoClient(hosts=url).db(db_name, username, password, verify=True)
self.__cntrl: ADBDGL_Controller = controller

def db(self) -> StandardDatabase:
return self.__db

def arangodb_to_dgl(
self, name: str, metagraph: ArangoMetagraph, **query_options: Any
) -> DGLHeteroGraph:
Expand Down
3 changes: 2 additions & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,6 @@ extend-ignore = E203, E741, W503
exclude =.git .idea .*_cache dist venv

[mypy]
ignore_missing_imports = True
strict = True
ignore_missing_imports = True
implicit_reexport = True
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@
python_requires=">=3.6",
license="Apache Software License",
install_requires=[
"python-arango==7.3.0",
"torch==1.10.0",
"dgl==0.6.1",
"torch>=1.10.2",
"python-arango>=7.3.1",
"setuptools>=42",
"setuptools_scm[toml]>=3.4",
],
Expand Down
81 changes: 34 additions & 47 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,50 +1,56 @@
import json
import os
import subprocess
import time
from pathlib import Path
from typing import Any

from arango import ArangoClient
from arango.database import StandardDatabase
from dgl import DGLGraph, remove_self_loop
from dgl.data import KarateClubDataset, MiniGCDataset
from requests import post
from torch import ones, rand, tensor, zeros # type: ignore
from torch import ones, rand, tensor, zeros

from adbdgl_adapter.adapter import ADBDGL_Adapter
from adbdgl_adapter.typings import Json

PROJECT_DIR = Path(__file__).parent.parent

con: Json
adbdgl_adapter: ADBDGL_Adapter
db: StandardDatabase
PROJECT_DIR = Path(__file__).parent.parent


def pytest_sessionstart() -> None:
def pytest_addoption(parser: Any) -> None:
parser.addoption("--protocol", action="store", default="http")
parser.addoption("--host", action="store", default="localhost")
parser.addoption("--port", action="store", default="8529")
parser.addoption("--dbName", action="store", default="_system")
parser.addoption("--username", action="store", default="root")
parser.addoption("--password", action="store", default="openSesame")


def pytest_configure(config: Any) -> None:
global con
con = get_oasis_crendetials()
# con = {
# "username": "root",
# "password": "openSesame",
# "hostname": "localhost",
# "port": 8529,
# "protocol": "http",
# "dbName": "_system",
# }
print_connection_details(con)
time.sleep(5) # Enough for the oasis instance to be ready.
con = {
"protocol": config.getoption("protocol"),
"hostname": config.getoption("host"),
"port": config.getoption("port"),
"username": config.getoption("username"),
"password": config.getoption("password"),
"dbName": config.getoption("dbName"),
}

print("----------------------------------------")
print(f"{con['protocol']}://{con['hostname']}:{con['port']}")
print("Username: " + con["username"])
print("Password: " + con["password"])
print("Database: " + con["dbName"])
print("----------------------------------------")

global adbdgl_adapter
adbdgl_adapter = ADBDGL_Adapter(con)

global db
url = "https://" + con["hostname"] + ":" + str(con["port"])
client = ArangoClient(hosts=url)
db = client.db(con["dbName"], con["username"], con["password"], verify=True)

# Restore fraud dataset via arangorestore
arango_restore(con, "examples/data/fraud_dump")
db.create_graph(

# Create Fraud Detection Graph
adbdgl_adapter.db().delete_graph("fraud-detection", ignore_missing=True)
adbdgl_adapter.db().create_graph(
"fraud-detection",
edge_definitions=[
{
Expand All @@ -61,22 +67,12 @@ def pytest_sessionstart() -> None:
)


def get_oasis_crendetials() -> Json:
url = "https://tutorials.arangodb.cloud:8529/_db/_system/tutorialDB/tutorialDB"
request = post(url, data=json.dumps("{}"))
if request.status_code != 200:
raise Exception("Error retrieving login data.")

creds: Json = json.loads(request.text)
return creds


def arango_restore(con: Json, path_to_data: str) -> None:
restore_prefix = "./assets/" if os.getenv("GITHUB_ACTIONS") else ""

subprocess.check_call(
f'chmod -R 755 ./assets/arangorestore && {restore_prefix}arangorestore \
-c none --server.endpoint http+ssl://{con["hostname"]}:{con["port"]} \
-c none --server.endpoint tcp://{con["hostname"]}:{con["port"]} \
--server.username {con["username"]} --server.database {con["dbName"]} \
--server.password {con["password"]} \
--input-directory "{PROJECT_DIR}/{path_to_data}"',
Expand All @@ -85,15 +81,6 @@ def arango_restore(con: Json, path_to_data: str) -> None:
)


def print_connection_details(con: Json) -> None:
print("----------------------------------------")
print("https://{}:{}".format(con["hostname"], con["port"]))
print("Username: " + con["username"])
print("Password: " + con["password"])
print("Database: " + con["dbName"])
print("----------------------------------------")


def get_karate_graph() -> DGLGraph:
return KarateClubDataset()[0]

Expand Down
12 changes: 8 additions & 4 deletions tests/test_adapter.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from typing import Set, Union

import pytest
from arango.database import StandardDatabase
from arango.graph import Graph as ArangoGraph
from dgl import DGLGraph
from dgl.heterograph import DGLHeteroGraph
Expand All @@ -12,7 +13,6 @@
from .conftest import (
adbdgl_adapter,
con,
db,
get_clique_graph,
get_hypercube_graph,
get_karate_graph,
Expand Down Expand Up @@ -71,7 +71,7 @@ def test_adb_to_dgl(
adapter: ADBDGL_Adapter, name: str, metagraph: ArangoMetagraph
) -> None:
dgl_g = adapter.arangodb_to_dgl(name, metagraph)
assert_dgl_data(dgl_g, metagraph)
assert_dgl_data(adapter.db(), dgl_g, metagraph)


@pytest.mark.parametrize(
Expand All @@ -94,6 +94,7 @@ def test_adb_collections_to_dgl(
e_cols,
)
assert_dgl_data(
adapter.db(),
dgl_g,
metagraph={
"vertexCollections": {col: set() for col in v_cols},
Expand All @@ -107,12 +108,13 @@ def test_adb_collections_to_dgl(
[(adbdgl_adapter, "fraud-detection")],
)
def test_adb_graph_to_dgl(adapter: ADBDGL_Adapter, name: str) -> None:
arango_graph = db.graph(name)
arango_graph = adapter.db().graph(name)
v_cols = arango_graph.vertex_collections()
e_cols = {col["edge_collection"] for col in arango_graph.edge_definitions()}

dgl_g: DGLGraph = adapter.arangodb_graph_to_dgl(name)
assert_dgl_data(
adapter.db(),
dgl_g,
metagraph={
"vertexCollections": {col: set() for col in v_cols},
Expand Down Expand Up @@ -141,7 +143,9 @@ def test_dgl_to_adb(
assert_arangodb_data(name, dgl_g, adb_g, is_default_type)


def assert_dgl_data(dgl_g: DGLGraph, metagraph: ArangoMetagraph) -> None:
def assert_dgl_data(
db: StandardDatabase, dgl_g: DGLGraph, metagraph: ArangoMetagraph
) -> None:
has_one_ntype = len(metagraph["vertexCollections"]) == 1
has_one_etype = len(metagraph["edgeCollections"]) == 1

Expand Down