From 097cc3026b3c08b5927686bb3cd9bbc932f3ca61 Mon Sep 17 00:00:00 2001 From: Hamish Scott Date: Mon, 30 Jun 2025 13:55:01 +0100 Subject: [PATCH 1/2] update to conode --- .gitignore | 1 + README.md | 26 +++++++------- pyproject.toml | 6 ++-- python/drisk_api/graph_client.py | 60 ++++++++++++++++---------------- python/tests/test_api.py | 49 ++++++++++++++++++++++++++ 5 files changed, 96 insertions(+), 46 deletions(-) create mode 100644 python/tests/test_api.py diff --git a/.gitignore b/.gitignore index 8478630..223ef0d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ target Cargo.lock* +__pycache__ diff --git a/README.md b/README.md index a9de391..c57b9a7 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ -# Edge Python API -API to connect to dRISK Edge. +# Conode Python API +API to connect to Conode. -### Useful Edge Links -Some useful links for new edge users: +### Useful Links +Some useful links for new Conode users: -- Log in to edge: [demo.drisk.ai](https://demo.drisk.ai/) -- Documentation: [demo.drisk.ai/docs](https://demo.drisk.ai/docs/) +- [Log in to Conode](https://app.drisk.ai/) +- [Documentation](https://app.drisk.ai/docs/) @@ -22,7 +22,7 @@ The API supports the basic building blocs for Create/Read/Update/Delete operatio ```python from drisk_api import GraphClient -token = "" +token = "" # create or conntect to a graph new_graph = GraphClient.create_graph("a graph", token) @@ -43,7 +43,7 @@ graph.update_node(node_id, label="new label", size=3) # add edges in batch other_id = graph.create_node(label="another node") with graph.batch(): - graph.create_edge(node_id, other, weight=5.) + graph.create_edge(node_id, other_id, weight=5.) ``` @@ -54,14 +54,14 @@ We can use these building blocks to create whatever graphs we are most intereste ### Wikepedia Crawler -In this example we will scrape the main url links for a given wikipedia page and create a graph out of it. +In this example we will scrape the main url links for a given wikipedia page and create a graph. Most of the code will be leveraging the [wikipedia api](https://pypi.org/project/wikipedia/) and is not particularly important. -What is more interesting is how we can use the `api` to convert the corresponding information into a graph to then explore it in edge. +What is more interesting is how we can use the `api` to convert the corresponding information into a graph to then explore it in Conode. -First load the relevant module +First load the relevant modules ```python import wikipedia @@ -165,7 +165,7 @@ def wiki_scraper( Then we can connect to our graph (or make one): ```python -TOKEN = "" +TOKEN = "" graph_id = "graph_id" home_view = "view_id" g = GraphClient(graph_id, TOKEN) @@ -197,7 +197,7 @@ with g.batch(): ``` -We can then head to edge to interact with the graph: +We can then head to Conode to interact with the graph:

diff --git a/pyproject.toml b/pyproject.toml index 184a936..a3c6e40 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ module-name = "drisk_api" [project] name = "drisk_api" -description = "drisk_api - API to connect to dRISK Edge." +description = "drisk_api - API to connect to Conode." version = "0.0.9" requires-python = ">=3.8" classifiers = [ @@ -20,5 +20,5 @@ dependencies = ["requests"] [project.urls] "Homepage" = "https://github.com/driskai/drisk_api/tree/main" -"Edge" = "https://demo.drisk.ai/" -"Edge Docs" = "https://demo.drisk.ai/docs/" +"Conode" = "https://app.drisk.ai/" +"Conode Docs" = "https://app.drisk.ai/docs/" diff --git a/python/drisk_api/graph_client.py b/python/drisk_api/graph_client.py index 62b3cf6..495e78b 100644 --- a/python/drisk_api/graph_client.py +++ b/python/drisk_api/graph_client.py @@ -7,16 +7,16 @@ from .drisk_api import PyGraphDiff -class EdgeException(Exception): +class ConodeException(Exception): """General Edge Expection.""" def __init__(self, status_code: int, message: str): - message = f"Edge Server Error\nStatus Code: {status_code}\n{message}" + message = f"Conode Server Error\nStatus Code: {status_code}\n{message}" super().__init__(message) -def edge_sync(func): - """Sync edges.""" +def sync(func): + """Sync with the remote graph before continuing.""" def wrapper(self: "GraphClient", *args, **kwargs): result = func(self, *args, **kwargs) @@ -28,7 +28,7 @@ def wrapper(self: "GraphClient", *args, **kwargs): class GraphClient: - """A connection to a graph in Edge.""" + """A connection to a graph.""" default_url = "http://localhost:5001/v3/graphs" defaults = { @@ -62,7 +62,7 @@ def create_graph( Raises ------ - EdgeException: If graph creation fails. + ConodeException: If graph creation fails. """ if url is None: @@ -74,7 +74,7 @@ def create_graph( params={"name": graph_name}, ) if not r.ok: - raise EdgeException(r.status_code, r.text) + raise ConodeException(r.status_code, r.text) graph_id = r.json() return cls(graph_id, token, url=url) @@ -104,13 +104,13 @@ def connect(self): Raises ------ - EdgeException: If connecting to the server or loading the graph fails. + ConodeException: If connecting to the server or loading the graph fails. """ url = f"{self.url}/{self.graph_id}/load" r = requests.get(url, headers={"Authorization": self.auth_token}) if r.status_code >= 300: - raise EdgeException(r.status_code, r.text) + raise ConodeException(r.status_code, r.text) def rename_graph(self, name: str): """ @@ -122,7 +122,7 @@ def rename_graph(self, name: str): Raises ------ ValueError: If the provided name is empty. - EdgeException: If renaming the graph fails. + ConodeException: If renaming the graph fails. """ if len(name) == 0: @@ -133,7 +133,7 @@ def rename_graph(self, name: str): params={"name": name, "groups": ""}, ) if r.status_code >= 300: - raise EdgeException(r.status_code, r.text) + raise ConodeException(r.status_code, r.text) def delete_graph(self): """ @@ -141,13 +141,13 @@ def delete_graph(self): Raises ------ - EdgeException: If deleting the graph fails. + ConodeException: If deleting the graph fails. """ url = f"{self.url}/{self.graph_id}/delete" r = requests.delete(url, headers={"Authorization": self.auth_token}) if r.status_code >= 300: - raise EdgeException(r.status_code, r.text) + raise ConodeException(r.status_code, r.text) def get_node(self, node_id: UUID) -> Optional["Node"]: """ @@ -191,7 +191,7 @@ def get_nodes(self, node_ids: List[UUID]) -> Dict[UUID, "Node"]: json=[str(id) for id in node_ids], ) if r.status_code >= 300: - raise EdgeException(r.status_code, r.text) + raise ConodeException(r.status_code, r.text) diff = PyGraphDiff.from_bytes(r.content) nodes = diff.new_or_updated_nodes() return { @@ -263,7 +263,7 @@ def get_edges(self, nodes: Iterable[UUID]) -> Dict: json=[str(node) for node in nodes], ) if r.status_code >= 300: - raise EdgeException(r.status_code, r.text) + raise ConodeException(r.status_code, r.text) return r.json() def create_view( @@ -358,7 +358,7 @@ def _post_diff(self): Raises ------ - EdgeException: If posting the graph differences fails. + ConodeException: If posting the graph differences fails. """ if self._diff_size() == 0: @@ -371,7 +371,7 @@ def _post_diff(self): headers={"Authorization": self.auth_token}, ) if r.status_code >= 300: - raise EdgeException(r.status_code, r.text) + raise ConodeException(r.status_code, r.text) self.diff.clear() def _diff_size(self) -> int: @@ -407,7 +407,7 @@ def _get_node_request( Raises ------ - EdgeException: If retrieving information about the node fails. + ConodeException: If retrieving information about the node fails. """ url = f"{self.url}/{self.graph_id}/atomic/{node_id}" @@ -418,7 +418,7 @@ def _get_node_request( url += query r = requests.get(url, headers={"Authorization": self.auth_token}) if r.status_code >= 300: - raise EdgeException(r.status_code, r.text) + raise ConodeException(r.status_code, r.text) data = r.json() if nbr_type: if weights: @@ -452,7 +452,7 @@ def set_batching_enabled(self, enabled: bool = True): if not self.batching: self._post_diff() - @edge_sync + @sync def create_node(self, label="node", **properties) -> UUID: """ Create a new node with the given properties. @@ -472,7 +472,7 @@ def create_node(self, label="node", **properties) -> UUID: self.diff.add_node(id.bytes, kwargs) return id - @edge_sync + @sync def create_edge(self, from_: UUID, to: UUID, weight: float = 1.0): """ Create a new edge between two nodes. @@ -490,7 +490,7 @@ def create_edge(self, from_: UUID, to: UUID, weight: float = 1.0): to = UUID(to) self.diff.add_edge(from_.bytes, to.bytes, weight) - @edge_sync + @sync def delete_node(self, node_id: UUID): """ Delete a node from the graph. @@ -504,7 +504,7 @@ def delete_node(self, node_id: UUID): node_id = UUID(node_id) self.diff.delete_node(node_id.bytes) - @edge_sync + @sync def delete_edge(self, from_: UUID, to: UUID): """ Delete an edge between two nodes. @@ -521,7 +521,7 @@ def delete_edge(self, from_: UUID, to: UUID): to = UUID(to) self.diff.delete_edge(from_.bytes, to.bytes) - @edge_sync + @sync def update_node(self, node_id: UUID, **new_properties): """ Update properties of a node. @@ -550,7 +550,7 @@ def import_file_as_node(self, filename: str, file: io.BytesIO): Raises ------ - EdgeException: If importing the file fails. + ConodeException: If importing the file fails. """ url = f"{self.url}/{self.graph_id}/data/file" @@ -558,7 +558,7 @@ def import_file_as_node(self, filename: str, file: io.BytesIO): url, headers={"Authorization": self.auth_token}, files={filename: file} ) if not r.ok: - raise EdgeException(r.status_code, r.text) + raise ConodeException(r.status_code, r.text) file_id = r.json() @@ -575,7 +575,7 @@ def update_file_in_node(self, node_id: UUID, filename: str, file: io.BytesIO): Raises ------ - EdgeException: If updating the file fails. + ConodeException: If updating the file fails. """ url = f"{self.url}/{self.graph_id}/data/file/{node_id}" @@ -585,7 +585,7 @@ def update_file_in_node(self, node_id: UUID, filename: str, file: io.BytesIO): files={filename: file}, ) if not r.ok: - raise EdgeException(r.status_code, r.text) + raise ConodeException(r.status_code, r.text) def add_data_from_json(self, filename: str, file: Union[Dict, List]) -> UUID: """ @@ -601,7 +601,7 @@ def add_data_from_json(self, filename: str, file: Union[Dict, List]) -> UUID: Raises ------ - EdgeException: If adding the JSON data fails. + ConodeException: If adding the JSON data fails. """ url = f"{self.url}/{self.graph_id}/data/json" @@ -611,7 +611,7 @@ def add_data_from_json(self, filename: str, file: Union[Dict, List]) -> UUID: json={"filename": filename, "file": file}, ) if not r.ok: - raise EdgeException(r.status_code, r.text) + raise ConodeException(r.status_code, r.text) json_id = r.json() diff --git a/python/tests/test_api.py b/python/tests/test_api.py new file mode 100644 index 0000000..6e831eb --- /dev/null +++ b/python/tests/test_api.py @@ -0,0 +1,49 @@ +import os +from typing import Optional + +import pytest + +from drisk_api import GraphClient + + +@pytest.fixture +def auth_token() -> str: + """Get the auth token from the environment.""" + token = os.getenv("CONODE_AUTH_TOKEN") + if not token: + raise ValueError("CONODE_AUTH_TOKEN environment variable is not set.") + return token + + +@pytest.fixture +def server_url() -> Optional[str]: + """Get the server url from the environment (Optional).""" + return os.getenv("CONODE_SERVER_URL") + + +def test_api(auth_token, server_url): + """Test the basic functionality of the GraphClient API.""" + graph = GraphClient.create_graph("test graph", auth_token, url=server_url) + + # make a new node + node_id = graph.create_node(label="a node") + + # get node properties + node = graph.get_node(node_id) + assert node.properties["label"] == "a node" + + # get the successors of the node + successors = graph.get_successors(node_id) + assert len(successors) == 0 + + # update the node + graph.update_node(node_id, label="new label", size=3) + + # add edges in batch + other_id = graph.create_node(label="another node") + with graph.batch(): + graph.create_edge(node_id, other_id, weight=5.0) + + successors = graph.get_successors(node_id) + assert len(successors) == 1 + assert successors[0] == other_id From 6eb585042644b0d961017f1ef2aa197eec311eaa Mon Sep 17 00:00:00 2001 From: Hamish Scott Date: Wed, 1 Oct 2025 16:49:05 +0100 Subject: [PATCH 2/2] docs + readme update --- README.md | 14 ++++++-------- python/drisk_api/graph_client.py | 12 ++++++------ 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index c57b9a7..a6fa707 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ pip install drisk_api ## Basic Usage -The API supports the basic building blocs for Create/Read/Update/Delete operations on the graph. For example: +The API supports the basic building blocks for Create/Read/Update/Delete operations on the graph. For example: ```python @@ -24,7 +24,7 @@ from drisk_api import GraphClient token = "" -# create or conntect to a graph +# create or connect to a graph new_graph = GraphClient.create_graph("a graph", token) graph = GraphClient("graph_id", token) @@ -52,7 +52,7 @@ with graph.batch(): We can use these building blocks to create whatever graphs we are most interested in. Below are some examples: -### Wikepedia Crawler +### Wikipedia Crawler In this example we will scrape the main url links for a given wikipedia page and create a graph. @@ -125,6 +125,8 @@ def wiki_scraper( if page_name in visited_pages or current_depth >= max_depth: return + visited_pages.add(page_name) + links = top_links(page.links, page.content, first_depth_max_links if current_depth == 0 else max_links) if current_depth == 0: @@ -152,14 +154,12 @@ def wiki_scraper( new_page_node, link, string_cache, - visted_pages, + visited_pages, current_depth=current_depth + 1, max_links=max_links, first_depth_max_links=first_depth_max_links, ) - visited_pages.add(page_name) - ``` Then we can connect to our graph (or make one): @@ -202,5 +202,3 @@ We can then head to Conode to interact with the graph:

- -![](![](![](![]()))) diff --git a/python/drisk_api/graph_client.py b/python/drisk_api/graph_client.py index 495e78b..c3b546d 100644 --- a/python/drisk_api/graph_client.py +++ b/python/drisk_api/graph_client.py @@ -8,7 +8,7 @@ class ConodeException(Exception): - """General Edge Expection.""" + """General Edge Exception.""" def __init__(self, status_code: int, message: str): message = f"Conode Server Error\nStatus Code: {status_code}\n{message}" @@ -54,7 +54,7 @@ def create_graph( Args: graph_name (str): Name of the graph. token (str): Authentication token. - url (Optionalal[str]): API endpoint URL (default URL if not provided). + url (Optional[str]): API endpoint URL (default URL if not provided). Returns ------- @@ -278,11 +278,11 @@ def create_view( Args: label (str): The label for the view node. - x_node (Optionalal[str]): The label for the x-axis node. + x_node (Optional[str]): The label for the x-axis node. If None, a default x-axis node is created. - y_node (Optionalal[str]): The label for the y-axis node. + y_node (Optional[str]): The label for the y-axis node. If None, a default y-axis node is created. - filters (Optionalal[List[str]]): List of labels for filter nodes. + filters (Optional[List[str]]): List of labels for filter nodes. Returns ------- @@ -396,7 +396,7 @@ def _get_node_request( Args: node_id (UUID): The ID of the node to retrieve information for. - nbr_type (Optionalal[str]): The type of neighboring nodes to include + nbr_type (Optional[str]): The type of neighboring nodes to include (default: None). weights (bool): Include weights in the response (default: False).