Skip to content

Commit 741e3a3

Browse files
authored
Basic graph support (#51)
1 parent ca57e80 commit 741e3a3

File tree

6 files changed

+406
-5
lines changed

6 files changed

+406
-5
lines changed

arangoasync/database.py

+175
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@
2323
DatabaseDeleteError,
2424
DatabaseListError,
2525
DatabasePropertiesError,
26+
GraphCreateError,
27+
GraphDeleteError,
28+
GraphListError,
2629
JWTSecretListError,
2730
JWTSecretReloadError,
2831
PermissionGetError,
@@ -50,6 +53,7 @@
5053
DefaultApiExecutor,
5154
TransactionApiExecutor,
5255
)
56+
from arangoasync.graph import Graph
5357
from arangoasync.request import Method, Request
5458
from arangoasync.response import Response
5559
from arangoasync.result import Result
@@ -58,6 +62,8 @@
5862
CollectionInfo,
5963
CollectionType,
6064
DatabaseProperties,
65+
GraphOptions,
66+
GraphProperties,
6167
Json,
6268
Jsons,
6369
KeyOptions,
@@ -655,6 +661,175 @@ def response_handler(resp: Response) -> bool:
655661

656662
return await self._executor.execute(request, response_handler)
657663

664+
def graph(self, name: str) -> Graph:
665+
"""Return the graph API wrapper.
666+
667+
Args:
668+
name (str): Graph name.
669+
670+
Returns:
671+
Graph: Graph API wrapper.
672+
"""
673+
return Graph(self._executor, name)
674+
675+
async def has_graph(self, name: str) -> Result[bool]:
676+
"""Check if a graph exists in the database.
677+
678+
Args:
679+
name (str): Graph name.
680+
681+
Returns:
682+
bool: True if the graph exists, False otherwise.
683+
684+
Raises:
685+
GraphListError: If the operation fails.
686+
"""
687+
request = Request(method=Method.GET, endpoint=f"/_api/gharial/{name}")
688+
689+
def response_handler(resp: Response) -> bool:
690+
if resp.is_success:
691+
return True
692+
if resp.status_code == HTTP_NOT_FOUND:
693+
return False
694+
raise GraphListError(resp, request)
695+
696+
return await self._executor.execute(request, response_handler)
697+
698+
async def graphs(self) -> Result[List[GraphProperties]]:
699+
"""List all graphs stored in the database.
700+
701+
Returns:
702+
list: Graph properties.
703+
704+
Raises:
705+
GraphListError: If the operation fails.
706+
707+
References:
708+
- `list-all-graphs <https://docs.arangodb.com/stable/develop/http-api/graphs/named-graphs/#list-all-graphs>`__
709+
""" # noqa: E501
710+
request = Request(method=Method.GET, endpoint="/_api/gharial")
711+
712+
def response_handler(resp: Response) -> List[GraphProperties]:
713+
if not resp.is_success:
714+
raise GraphListError(resp, request)
715+
body = self.deserializer.loads(resp.raw_body)
716+
return [GraphProperties(u) for u in body["graphs"]]
717+
718+
return await self._executor.execute(request, response_handler)
719+
720+
async def create_graph(
721+
self,
722+
name: str,
723+
edge_definitions: Optional[Sequence[Json]] = None,
724+
is_disjoint: Optional[bool] = None,
725+
is_smart: Optional[bool] = None,
726+
options: Optional[GraphOptions | Json] = None,
727+
orphan_collections: Optional[Sequence[str]] = None,
728+
wait_for_sync: Optional[bool] = None,
729+
) -> Result[Graph]:
730+
"""Create a new graph.
731+
732+
Args:
733+
name (str): Graph name.
734+
edge_definitions (list | None): List of edge definitions, where each edge
735+
definition entry is a dictionary with fields "collection" (name of the
736+
edge collection), "from" (list of vertex collection names) and "to"
737+
(list of vertex collection names).
738+
is_disjoint (bool | None): Whether to create a Disjoint SmartGraph
739+
instead of a regular SmartGraph (Enterprise Edition only).
740+
is_smart (bool | None): Define if the created graph should be smart
741+
(Enterprise Edition only).
742+
options (GraphOptions | dict | None): Options for creating collections
743+
within this graph.
744+
orphan_collections (list | None): An array of additional vertex
745+
collections. Documents in these collections do not have edges
746+
within this graph.
747+
wait_for_sync (bool | None): If `True`, wait until everything is
748+
synced to disk.
749+
750+
Returns:
751+
Graph: Graph API wrapper.
752+
753+
Raises:
754+
GraphCreateError: If the operation fails.
755+
756+
References:
757+
- `create-a-graph <https://docs.arangodb.com/stable/develop/http-api/graphs/named-graphs/#create-a-graph>`__
758+
""" # noqa: E501
759+
params: Params = {}
760+
if wait_for_sync is not None:
761+
params["waitForSync"] = wait_for_sync
762+
763+
data: Json = {"name": name}
764+
if edge_definitions is not None:
765+
data["edgeDefinitions"] = edge_definitions
766+
if is_disjoint is not None:
767+
data["isDisjoint"] = is_disjoint
768+
if is_smart is not None:
769+
data["isSmart"] = is_smart
770+
if options is not None:
771+
if isinstance(options, GraphOptions):
772+
data["options"] = options.to_dict()
773+
else:
774+
data["options"] = options
775+
if orphan_collections is not None:
776+
data["orphanCollections"] = orphan_collections
777+
778+
request = Request(
779+
method=Method.POST,
780+
endpoint="/_api/gharial",
781+
data=self.serializer.dumps(data),
782+
params=params,
783+
)
784+
785+
def response_handler(resp: Response) -> Graph:
786+
if resp.is_success:
787+
return Graph(self._executor, name)
788+
raise GraphCreateError(resp, request)
789+
790+
return await self._executor.execute(request, response_handler)
791+
792+
async def delete_graph(
793+
self,
794+
name: str,
795+
drop_collections: Optional[bool] = None,
796+
ignore_missing: bool = False,
797+
) -> Result[bool]:
798+
"""Drops an existing graph object by name.
799+
800+
Args:
801+
name (str): Graph name.
802+
drop_collections (bool | None): Optionally all collections not used by
803+
other graphs can be dropped as well.
804+
ignore_missing (bool): Do not raise an exception on missing graph.
805+
806+
Returns:
807+
bool: True if the graph was deleted successfully, `False` if the
808+
graph was not found but **ignore_missing** was set to `True`.
809+
810+
Raises:
811+
GraphDeleteError: If the operation fails.
812+
813+
References:
814+
- `drop-a-graph <https://docs.arangodb.com/stable/develop/http-api/graphs/named-graphs/#drop-a-graph>`__
815+
""" # noqa: E501
816+
params: Params = {}
817+
if drop_collections is not None:
818+
params["dropCollections"] = drop_collections
819+
820+
request = Request(
821+
method=Method.DELETE, endpoint=f"/_api/gharial/{name}", params=params
822+
)
823+
824+
def response_handler(resp: Response) -> bool:
825+
if not resp.is_success:
826+
if resp.status_code == HTTP_NOT_FOUND and ignore_missing:
827+
return False
828+
raise GraphDeleteError(resp, request)
829+
return True
830+
831+
return await self._executor.execute(request, response_handler)
832+
658833
async def has_user(self, username: str) -> Result[bool]:
659834
"""Check if a user exists.
660835

arangoasync/exceptions.py

+12
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,18 @@ class DocumentUpdateError(ArangoServerError):
263263
"""Failed to update document."""
264264

265265

266+
class GraphCreateError(ArangoServerError):
267+
"""Failed to create the graph."""
268+
269+
270+
class GraphDeleteError(ArangoServerError):
271+
"""Failed to delete the graph."""
272+
273+
274+
class GraphListError(ArangoServerError):
275+
"""Failed to retrieve graphs."""
276+
277+
266278
class IndexCreateError(ArangoServerError):
267279
"""Failed to create collection index."""
268280

arangoasync/graph.py

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from arangoasync.executor import ApiExecutor
2+
3+
4+
class Graph:
5+
"""Graph API wrapper, representing a graph in ArangoDB.
6+
7+
Args:
8+
executor: API executor. Required to execute the API requests.
9+
"""
10+
11+
def __init__(self, executor: ApiExecutor, name: str) -> None:
12+
self._executor = executor
13+
self._name = name
14+
15+
def __repr__(self) -> str:
16+
return f"<Graph {self._name}>"
17+
18+
@property
19+
def name(self) -> str:
20+
"""Name of the graph."""
21+
return self._name

0 commit comments

Comments
 (0)