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

Lines changed: 175 additions & 0 deletions
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

Lines changed: 12 additions & 0 deletions
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

Lines changed: 21 additions & 0 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy