Skip to content

Commit 00eaf49

Browse files
committed
Extra edge methods
1 parent 5e4c0b4 commit 00eaf49

File tree

3 files changed

+269
-21
lines changed

3 files changed

+269
-21
lines changed

arangoasync/collection.py

Lines changed: 118 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
]
77

88

9-
from typing import Any, Generic, List, Optional, Sequence, TypeVar, cast
9+
from typing import Any, Generic, List, Literal, Optional, Sequence, TypeVar, cast
1010

1111
from arangoasync.cursor import Cursor
1212
from arangoasync.errno import (
@@ -26,6 +26,7 @@
2626
DocumentReplaceError,
2727
DocumentRevisionError,
2828
DocumentUpdateError,
29+
EdgeListError,
2930
IndexCreateError,
3031
IndexDeleteError,
3132
IndexGetError,
@@ -111,11 +112,13 @@ def _validate_id(self, doc_id: str) -> str:
111112
raise DocumentParseError(f'Bad collection name in document ID "{doc_id}"')
112113
return doc_id
113114

114-
def _extract_id(self, body: Json) -> str:
115+
def _extract_id(self, body: Json, validate: bool = True) -> str:
115116
"""Extract the document ID from document body.
116117
117118
Args:
118119
body (dict): Document body.
120+
validate (bool): Whether to validate the document ID,
121+
checking if it belongs to the current collection.
119122
120123
Returns:
121124
str: Document ID.
@@ -125,7 +128,10 @@ def _extract_id(self, body: Json) -> str:
125128
"""
126129
try:
127130
if "_id" in body:
128-
return self._validate_id(body["_id"])
131+
if validate:
132+
return self._validate_id(body["_id"])
133+
else:
134+
return cast(str, body["_id"])
129135
else:
130136
key: str = body["_key"]
131137
return self._id_prefix + key
@@ -150,28 +156,30 @@ def _ensure_key_from_id(self, body: Json) -> Json:
150156
body["_key"] = doc_id[len(self._id_prefix) :]
151157
return body
152158

153-
def _prep_from_doc(self, document: str | Json) -> str:
159+
def _get_doc_id(self, document: str | Json, validate: bool = True) -> str:
154160
"""Prepare document ID before a query.
155161
156162
Args:
157163
document (str | dict): Document ID, key or body.
164+
validate (bool): Whether to validate the document ID,
165+
checking if it belongs to the current collection.
158166
159167
Returns:
160168
Document ID and request headers.
161169
162170
Raises:
163171
DocumentParseError: On missing ID and key.
164-
TypeError: On bad document type.
165172
"""
166-
if isinstance(document, dict):
167-
doc_id = self._extract_id(document)
168-
elif isinstance(document, str):
173+
if isinstance(document, str):
169174
if "/" in document:
170-
doc_id = self._validate_id(document)
175+
if validate:
176+
doc_id = self._validate_id(document)
177+
else:
178+
doc_id = document
171179
else:
172180
doc_id = self._id_prefix + document
173181
else:
174-
raise TypeError("Document must be str or a dict")
182+
doc_id = self._extract_id(document, validate)
175183

176184
return doc_id
177185

@@ -585,7 +593,7 @@ async def has(
585593
References:
586594
- `get-a-document-header <https://docs.arangodb.com/stable/develop/http-api/documents/#get-a-document-header>`__
587595
""" # noqa: E501
588-
handle = self._prep_from_doc(document)
596+
handle = self._get_doc_id(document)
589597

590598
headers: RequestHeaders = {}
591599
if allow_dirty_read:
@@ -1314,7 +1322,7 @@ async def get(
13141322
References:
13151323
- `get-a-document <https://docs.arangodb.com/stable/develop/http-api/documents/#get-a-document>`__
13161324
""" # noqa: E501
1317-
handle = self._prep_from_doc(document)
1325+
handle = self._get_doc_id(document)
13181326

13191327
headers: RequestHeaders = {}
13201328
if allow_dirty_read:
@@ -1814,7 +1822,7 @@ async def get(
18141822
References:
18151823
- `get-a-vertex <https://docs.arangodb.com/stable/develop/http-api/graphs/named-graphs/#get-a-vertex>`__
18161824
""" # noqa: E501
1817-
handle = self._prep_from_doc(vertex)
1825+
handle = self._get_doc_id(vertex)
18181826

18191827
headers: RequestHeaders = {}
18201828
if if_match is not None:
@@ -1958,7 +1966,7 @@ async def update(
19581966
request = Request(
19591967
method=Method.PATCH,
19601968
endpoint=f"/_api/gharial/{self._graph}/vertex/"
1961-
f"{self._prep_from_doc(cast(Json, vertex))}",
1969+
f"{self._get_doc_id(cast(Json, vertex))}",
19621970
params=params,
19631971
headers=headers,
19641972
data=self._doc_serializer.dumps(vertex),
@@ -2033,7 +2041,7 @@ async def replace(
20332041
request = Request(
20342042
method=Method.PUT,
20352043
endpoint=f"/_api/gharial/{self._graph}/vertex/"
2036-
f"{self._prep_from_doc(cast(Json, vertex))}",
2044+
f"{self._get_doc_id(cast(Json, vertex))}",
20372045
params=params,
20382046
headers=headers,
20392047
data=self._doc_serializer.dumps(vertex),
@@ -2101,7 +2109,7 @@ async def delete(
21012109
request = Request(
21022110
method=Method.DELETE,
21032111
endpoint=f"/_api/gharial/{self._graph}/vertex/"
2104-
f"{self._prep_from_doc(cast(Json, vertex))}",
2112+
f"{self._get_doc_id(cast(Json, vertex))}",
21052113
params=params,
21062114
headers=headers,
21072115
)
@@ -2213,7 +2221,7 @@ async def get(
22132221
References:
22142222
- `get-an-edge <https://docs.arangodb.com/stable/develop/http-api/graphs/named-graphs/#get-an-edge>`__
22152223
""" # noqa: E501
2216-
handle = self._prep_from_doc(edge)
2224+
handle = self._get_doc_id(edge)
22172225

22182226
headers: RequestHeaders = {}
22192227
if if_match is not None:
@@ -2362,7 +2370,7 @@ async def update(
23622370
request = Request(
23632371
method=Method.PATCH,
23642372
endpoint=f"/_api/gharial/{self._graph}/edge/"
2365-
f"{self._prep_from_doc(cast(Json, edge))}",
2373+
f"{self._get_doc_id(cast(Json, edge))}",
23662374
params=params,
23672375
headers=headers,
23682376
data=self._doc_serializer.dumps(edge),
@@ -2441,7 +2449,7 @@ async def replace(
24412449
request = Request(
24422450
method=Method.PUT,
24432451
endpoint=f"/_api/gharial/{self._graph}/edge/"
2444-
f"{self._prep_from_doc(cast(Json, edge))}",
2452+
f"{self._get_doc_id(cast(Json, edge))}",
24452453
params=params,
24462454
headers=headers,
24472455
data=self._doc_serializer.dumps(edge),
@@ -2512,7 +2520,7 @@ async def delete(
25122520
request = Request(
25132521
method=Method.DELETE,
25142522
endpoint=f"/_api/gharial/{self._graph}/edge/"
2515-
f"{self._prep_from_doc(cast(Json, edge))}",
2523+
f"{self._get_doc_id(cast(Json, edge))}",
25162524
params=params,
25172525
headers=headers,
25182526
)
@@ -2536,3 +2544,93 @@ def response_handler(resp: Response) -> bool | Json:
25362544
raise DocumentDeleteError(resp, request, msg)
25372545

25382546
return await self._executor.execute(request, response_handler)
2547+
2548+
async def edges(
2549+
self,
2550+
vertex: str | Json,
2551+
direction: Optional[Literal["in", "out"]] = None,
2552+
allow_dirty_read: Optional[bool] = None,
2553+
) -> Result[Json]:
2554+
"""Return the edges starting or ending at the specified vertex.
2555+
2556+
Args:
2557+
vertex (str | dict): Document ID, key or body.
2558+
direction (str | None): Direction of the edges to return. Selects `in`
2559+
or `out` direction for edges. If not set, any edges are returned.
2560+
allow_dirty_read (bool | None): Allow reads from followers in a cluster.
2561+
2562+
Returns:
2563+
dict: List of edges and statistics.
2564+
2565+
Raises:
2566+
EdgeListError: If retrieval fails.
2567+
2568+
References:
2569+
- `get-inbound-and-outbound-edges <https://docs.arangodb.com/stable/develop/http-api/graphs/edges/#get-inbound-and-outbound-edges>`__
2570+
""" # noqa: E501
2571+
params: Params = {
2572+
"vertex": self._get_doc_id(vertex, validate=False),
2573+
}
2574+
if direction is not None:
2575+
params["direction"] = direction
2576+
2577+
headers: RequestHeaders = {}
2578+
if allow_dirty_read is not None:
2579+
headers["x-arango-allow-dirty-read"] = "true" if allow_dirty_read else False
2580+
2581+
request = Request(
2582+
method=Method.GET,
2583+
endpoint=f"/_api/edges/{self._name}",
2584+
params=params,
2585+
headers=headers,
2586+
)
2587+
2588+
def response_handler(resp: Response) -> Json:
2589+
if not resp.is_success:
2590+
raise EdgeListError(resp, request)
2591+
body = self.deserializer.loads(resp.raw_body)
2592+
for key in ("error", "code"):
2593+
body.pop(key)
2594+
return body
2595+
2596+
return await self._executor.execute(request, response_handler)
2597+
2598+
async def link(
2599+
self,
2600+
from_vertex: str | Json,
2601+
to_vertex: str | Json,
2602+
data: Optional[Json] = None,
2603+
wait_for_sync: Optional[bool] = None,
2604+
return_new: bool = False,
2605+
) -> Result[Json]:
2606+
"""Insert a new edge document linking the given vertices.
2607+
2608+
Args:
2609+
from_vertex (str | dict): "_from" vertex document ID or body with "_id"
2610+
field.
2611+
to_vertex (str | dict): "_to" vertex document ID or body with "_id" field.
2612+
data (dict | None): Any extra data for the new edge document. If it has
2613+
"_key" or "_id" field, its value is used as key of the new edge document
2614+
(otherwise it is auto-generated).
2615+
wait_for_sync (bool | None): Wait until operation has been synced to disk.
2616+
return_new: Optional[bool]: Additionally return the complete new document
2617+
under the attribute `new` in the result.
2618+
2619+
Returns:
2620+
dict: Document metadata (e.g. document id, key, revision).
2621+
If `return_new` is specified, the result contains the document
2622+
metadata in the "edge" field and the new document in the "new" field.
2623+
2624+
Raises:
2625+
DocumentInsertError: If insertion fails.
2626+
DocumentParseError: If the document is malformed.
2627+
"""
2628+
edge: Json = {
2629+
"_from": self._get_doc_id(from_vertex, validate=False),
2630+
"_to": self._get_doc_id(to_vertex, validate=False),
2631+
}
2632+
if data is not None:
2633+
edge.update(self._ensure_key_from_id(data))
2634+
return await self.insert(
2635+
cast(T, edge), wait_for_sync=wait_for_sync, return_new=return_new
2636+
)

arangoasync/graph.py

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
__all__ = ["Graph"]
22

33

4-
from typing import Generic, List, Optional, Sequence, TypeVar, cast
4+
from typing import Generic, List, Literal, Optional, Sequence, TypeVar, cast
55

66
from arangoasync.collection import Collection, EdgeCollection, VertexCollection
77
from arangoasync.exceptions import (
@@ -966,3 +966,74 @@ async def delete_edge(
966966
return_old=return_old,
967967
if_match=if_match,
968968
)
969+
970+
async def edges(
971+
self,
972+
collection: str,
973+
vertex: str | Json,
974+
direction: Optional[Literal["in", "out"]] = None,
975+
allow_dirty_read: Optional[bool] = None,
976+
) -> Result[Json]:
977+
"""Return the edges starting or ending at the specified vertex.
978+
979+
Args:
980+
collection (str): Name of the edge collection to return edges from.
981+
vertex (str | dict): Document ID, key or body.
982+
direction (str | None): Direction of the edges to return. Selects `in`
983+
or `out` direction for edges. If not set, any edges are returned.
984+
allow_dirty_read (bool | None): Allow reads from followers in a cluster.
985+
986+
Returns:
987+
dict: List of edges and statistics.
988+
989+
Raises:
990+
EdgeListError: If retrieval fails.
991+
992+
References:
993+
- `get-inbound-and-outbound-edges <https://docs.arangodb.com/stable/develop/http-api/graphs/edges/#get-inbound-and-outbound-edges>`__
994+
""" # noqa: E501
995+
return await self.edge_collection(collection).edges(
996+
vertex,
997+
direction=direction,
998+
allow_dirty_read=allow_dirty_read,
999+
)
1000+
1001+
async def link(
1002+
self,
1003+
collection: str,
1004+
from_vertex: str | Json,
1005+
to_vertex: str | Json,
1006+
data: Optional[Json] = None,
1007+
wait_for_sync: Optional[bool] = None,
1008+
return_new: bool = False,
1009+
) -> Result[Json]:
1010+
"""Insert a new edge document linking the given vertices.
1011+
1012+
Args:
1013+
collection (str): Name of the collection to insert the edge into.
1014+
from_vertex (str | dict): "_from" vertex document ID or body with "_id"
1015+
field.
1016+
to_vertex (str | dict): "_to" vertex document ID or body with "_id" field.
1017+
data (dict | None): Any extra data for the new edge document. If it has
1018+
"_key" or "_id" field, its value is used as key of the new edge document
1019+
(otherwise it is auto-generated).
1020+
wait_for_sync (bool | None): Wait until operation has been synced to disk.
1021+
return_new: Optional[bool]: Additionally return the complete new document
1022+
under the attribute `new` in the result.
1023+
1024+
Returns:
1025+
dict: Document metadata (e.g. document id, key, revision).
1026+
If `return_new` is specified, the result contains the document
1027+
metadata in the "edge" field and the new document in the "new" field.
1028+
1029+
Raises:
1030+
DocumentInsertError: If insertion fails.
1031+
DocumentParseError: If the document is malformed.
1032+
"""
1033+
return await self.edge_collection(collection).link(
1034+
from_vertex,
1035+
to_vertex,
1036+
data=data,
1037+
wait_for_sync=wait_for_sync,
1038+
return_new=return_new,
1039+
)

0 commit comments

Comments
 (0)