Skip to content

Commit 5e4c0b4

Browse files
committed
Edges CRUD
1 parent a5c6278 commit 5e4c0b4

File tree

3 files changed

+277
-11
lines changed

3 files changed

+277
-11
lines changed

arangoasync/collection.py

Lines changed: 153 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2317,7 +2317,7 @@ async def update(
23172317
return_old: Optional[bool] = None,
23182318
if_match: Optional[str] = None,
23192319
) -> Result[Json]:
2320-
"""Update a vertex in the graph.
2320+
"""Update an edge in the graph.
23212321
23222322
Args:
23232323
edge (dict): Partial or full document with the updated values.
@@ -2336,7 +2336,7 @@ async def update(
23362336
Returns:
23372337
dict: Document metadata (e.g. document id, key, revision).
23382338
If `return_new` or "return_old" are specified, the result contains
2339-
the document metadata in the "vertex" field and two additional fields
2339+
the document metadata in the "edge" field and two additional fields
23402340
("new" and "old").
23412341
23422342
Raises:
@@ -2385,3 +2385,154 @@ def response_handler(resp: Response) -> Json:
23852385
raise DocumentUpdateError(resp, request, msg)
23862386

23872387
return await self._executor.execute(request, response_handler)
2388+
2389+
async def replace(
2390+
self,
2391+
edge: T,
2392+
wait_for_sync: Optional[bool] = None,
2393+
keep_null: Optional[bool] = None,
2394+
return_new: Optional[bool] = None,
2395+
return_old: Optional[bool] = None,
2396+
if_match: Optional[str] = None,
2397+
) -> Result[Json]:
2398+
"""Replace an edge in the graph.
2399+
2400+
Args:
2401+
edge (dict): Partial or full document with the updated values.
2402+
It must contain the "_key" or "_id" field, along with "_from" and
2403+
"_to" fields.
2404+
wait_for_sync (bool | None): Wait until document has been synced to disk.
2405+
keep_null (bool | None): If the intention is to delete existing attributes
2406+
with the patch command, set this parameter to `False`.
2407+
return_new (bool | None): Additionally return the complete new document
2408+
under the attribute `new` in the result.
2409+
return_old (bool | None): Additionally return the complete old document
2410+
under the attribute `old` in the result.
2411+
if_match (str | None): You can conditionally replace a document based on a
2412+
target revision id by using the "if-match" HTTP header.
2413+
2414+
Returns:
2415+
dict: Document metadata (e.g. document id, key, revision).
2416+
If `return_new` or "return_old" are specified, the result contains
2417+
the document metadata in the "edge" field and two additional fields
2418+
("new" and "old").
2419+
2420+
Raises:
2421+
DocumentRevisionError: If precondition was violated.
2422+
DocumentReplaceError: If replace fails.
2423+
2424+
References:
2425+
- `replace-an-edge <https://docs.arangodb.com/stable/develop/http-api/graphs/named-graphs/#replace-an-edge>`__
2426+
""" # noqa: E501
2427+
params: Params = {}
2428+
if wait_for_sync is not None:
2429+
params["waitForSync"] = wait_for_sync
2430+
if keep_null is not None:
2431+
params["keepNull"] = keep_null
2432+
if return_new is not None:
2433+
params["returnNew"] = return_new
2434+
if return_old is not None:
2435+
params["returnOld"] = return_old
2436+
2437+
headers: RequestHeaders = {}
2438+
if if_match is not None:
2439+
headers["If-Match"] = if_match
2440+
2441+
request = Request(
2442+
method=Method.PUT,
2443+
endpoint=f"/_api/gharial/{self._graph}/edge/"
2444+
f"{self._prep_from_doc(cast(Json, edge))}",
2445+
params=params,
2446+
headers=headers,
2447+
data=self._doc_serializer.dumps(edge),
2448+
)
2449+
2450+
def response_handler(resp: Response) -> Json:
2451+
if resp.is_success:
2452+
return self._parse_result(self.deserializer.loads(resp.raw_body))
2453+
msg: Optional[str] = None
2454+
if resp.status_code == HTTP_PRECONDITION_FAILED:
2455+
raise DocumentRevisionError(resp, request)
2456+
elif resp.status_code == HTTP_NOT_FOUND:
2457+
msg = (
2458+
"The graph cannot be found or the edge collection is not "
2459+
"part of the graph. It is also possible that the vertex "
2460+
"collection referenced in the _from or _to attribute is not part "
2461+
"of the graph or the vertex collection is part of the graph, but "
2462+
"does not exist. Finally check that _from or _to vertex do exist."
2463+
)
2464+
raise DocumentReplaceError(resp, request, msg)
2465+
2466+
return await self._executor.execute(request, response_handler)
2467+
2468+
async def delete(
2469+
self,
2470+
edge: T,
2471+
ignore_missing: bool = False,
2472+
wait_for_sync: Optional[bool] = None,
2473+
return_old: Optional[bool] = None,
2474+
if_match: Optional[str] = None,
2475+
) -> Result[bool | Json]:
2476+
"""Delete an edge from the graph.
2477+
2478+
Args:
2479+
edge (dict): Partial or full document with the updated values.
2480+
It must contain the "_key" or "_id" field, along with "_from" and
2481+
"_to" fields.
2482+
ignore_missing (bool): Do not raise an exception on missing document.
2483+
wait_for_sync (bool | None): Wait until operation has been synced to disk.
2484+
return_old (bool | None): Additionally return the complete old document
2485+
under the attribute `old` in the result.
2486+
if_match (str | None): You can conditionally replace a document based on a
2487+
target revision id by using the "if-match" HTTP header.
2488+
2489+
Returns:
2490+
bool | dict: `True` if vertex was deleted successfully, `False` if vertex
2491+
was not found and **ignore_missing** was set to `True` (does not apply
2492+
in transactions). Old document is returned if **return_old** is set
2493+
to `True`.
2494+
2495+
Raises:
2496+
DocumentRevisionError: If precondition was violated.
2497+
DocumentDeleteError: If deletion fails.
2498+
2499+
References:
2500+
- `remove-an-edge <https://docs.arangodb.com/stable/develop/http-api/graphs/named-graphs/#remove-an-edge>`__
2501+
""" # noqa: E501
2502+
params: Params = {}
2503+
if wait_for_sync is not None:
2504+
params["waitForSync"] = wait_for_sync
2505+
if return_old is not None:
2506+
params["returnOld"] = return_old
2507+
2508+
headers: RequestHeaders = {}
2509+
if if_match is not None:
2510+
headers["If-Match"] = if_match
2511+
2512+
request = Request(
2513+
method=Method.DELETE,
2514+
endpoint=f"/_api/gharial/{self._graph}/edge/"
2515+
f"{self._prep_from_doc(cast(Json, edge))}",
2516+
params=params,
2517+
headers=headers,
2518+
)
2519+
2520+
def response_handler(resp: Response) -> bool | Json:
2521+
if resp.is_success:
2522+
data: Json = self.deserializer.loads(resp.raw_body)
2523+
if "old" in data:
2524+
return cast(Json, data["old"])
2525+
return True
2526+
msg: Optional[str] = None
2527+
if resp.status_code == HTTP_PRECONDITION_FAILED:
2528+
raise DocumentRevisionError(resp, request)
2529+
elif resp.status_code == HTTP_NOT_FOUND:
2530+
if resp.error_code == DOCUMENT_NOT_FOUND and ignore_missing:
2531+
return False
2532+
msg = (
2533+
"Either the graph cannot be found, the edge collection is not "
2534+
"part of the graph, or the edge does not exist"
2535+
)
2536+
raise DocumentDeleteError(resp, request, msg)
2537+
2538+
return await self._executor.execute(request, response_handler)

arangoasync/graph.py

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -857,7 +857,7 @@ async def update_edge(
857857
Returns:
858858
dict: Document metadata (e.g. document id, key, revision).
859859
If `return_new` or "return_old" are specified, the result contains
860-
the document metadata in the "vertex" field and two additional fields
860+
the document metadata in the "edge" field and two additional fields
861861
("new" and "old").
862862
863863
Raises:
@@ -875,3 +875,94 @@ async def update_edge(
875875
return_old=return_old,
876876
if_match=if_match,
877877
)
878+
879+
async def replace_edge(
880+
self,
881+
edge: T,
882+
wait_for_sync: Optional[bool] = None,
883+
keep_null: Optional[bool] = None,
884+
return_new: Optional[bool] = None,
885+
return_old: Optional[bool] = None,
886+
if_match: Optional[str] = None,
887+
) -> Result[Json]:
888+
"""Replace an edge in the graph.
889+
890+
Args:
891+
edge (dict): Partial or full document with the updated values.
892+
It must contain the "_key" or "_id" field, along with "_from" and
893+
"_to" fields.
894+
wait_for_sync (bool | None): Wait until document has been synced to disk.
895+
keep_null (bool | None): If the intention is to delete existing attributes
896+
with the patch command, set this parameter to `False`.
897+
return_new (bool | None): Additionally return the complete new document
898+
under the attribute `new` in the result.
899+
return_old (bool | None): Additionally return the complete old document
900+
under the attribute `old` in the result.
901+
if_match (str | None): You can conditionally replace a document based on a
902+
target revision id by using the "if-match" HTTP header.
903+
904+
Returns:
905+
dict: Document metadata (e.g. document id, key, revision).
906+
If `return_new` or "return_old" are specified, the result contains
907+
the document metadata in the "edge" field and two additional fields
908+
("new" and "old").
909+
910+
Raises:
911+
DocumentRevisionError: If precondition was violated.
912+
DocumentReplaceError: If replace fails.
913+
914+
References:
915+
- `replace-an-edge <https://docs.arangodb.com/stable/develop/http-api/graphs/named-graphs/#replace-an-edge>`__
916+
""" # noqa: E501
917+
col = Collection.get_col_name(cast(Json | str, edge))
918+
return await self.edge_collection(col).replace(
919+
edge,
920+
wait_for_sync=wait_for_sync,
921+
keep_null=keep_null,
922+
return_new=return_new,
923+
return_old=return_old,
924+
if_match=if_match,
925+
)
926+
927+
async def delete_edge(
928+
self,
929+
edge: T,
930+
ignore_missing: bool = False,
931+
wait_for_sync: Optional[bool] = None,
932+
return_old: Optional[bool] = None,
933+
if_match: Optional[str] = None,
934+
) -> Result[bool | Json]:
935+
"""Delete an edge from the graph.
936+
937+
Args:
938+
edge (dict): Partial or full document with the updated values.
939+
It must contain the "_key" or "_id" field, along with "_from" and
940+
"_to" fields.
941+
ignore_missing (bool): Do not raise an exception on missing document.
942+
wait_for_sync (bool | None): Wait until operation has been synced to disk.
943+
return_old (bool | None): Additionally return the complete old document
944+
under the attribute `old` in the result.
945+
if_match (str | None): You can conditionally replace a document based on a
946+
target revision id by using the "if-match" HTTP header.
947+
948+
Returns:
949+
bool | dict: `True` if vertex was deleted successfully, `False` if vertex
950+
was not found and **ignore_missing** was set to `True` (does not apply
951+
in transactions). Old document is returned if **return_old** is set
952+
to `True`.
953+
954+
Raises:
955+
DocumentRevisionError: If precondition was violated.
956+
DocumentDeleteError: If deletion fails.
957+
958+
References:
959+
- `remove-an-edge <https://docs.arangodb.com/stable/develop/http-api/graphs/named-graphs/#remove-an-edge>`__
960+
""" # noqa: E501
961+
col = Collection.get_col_name(cast(Json | str, edge))
962+
return await self.edge_collection(col).delete(
963+
edge,
964+
ignore_missing=ignore_missing,
965+
wait_for_sync=wait_for_sync,
966+
return_old=return_old,
967+
if_match=if_match,
968+
)

tests/test_graph.py

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ async def test_vertex_collections(db, docs, bad_graph):
154154
v1["text"] = "updated_text"
155155
v1_meta = await graph.update_vertex(v1, return_new=True)
156156
assert "new" in v1_meta
157+
assert "vertex" in v1_meta
157158
v1 = await graph.vertex(v1_meta["vertex"])
158159
assert v1["text"] == "updated_text"
159160

@@ -164,6 +165,7 @@ async def test_vertex_collections(db, docs, bad_graph):
164165
v1_meta = await graph.replace_vertex(v1, return_old=True, return_new=True)
165166
assert "old" in v1_meta
166167
assert "new" in v1_meta
168+
assert "vertex" in v1_meta
167169
v1 = await graph.vertex(v1_meta["vertex"])
168170
assert v1["text"] == "replaced_text"
169171
assert "additional" in v1
@@ -255,16 +257,20 @@ async def test_edge_collections(db, bad_graph):
255257
]
256258

257259
# Create an edge
258-
await graph.insert_vertex(teachers_col_name, teachers[0])
259-
await graph.insert_vertex(students_col_name, students[0])
260-
edge_meta = await graph.insert_edge(
261-
edge_col_name,
262-
edges[0],
263-
return_new=True,
264-
)
265-
assert "new" in edge_meta
260+
edge_metas = []
261+
for idx in range(len(edges)):
262+
await graph.insert_vertex(teachers_col_name, teachers[idx])
263+
await graph.insert_vertex(students_col_name, students[idx])
264+
edge_meta = await graph.insert_edge(
265+
edge_col_name,
266+
edges[0],
267+
return_new=True,
268+
)
269+
assert "new" in edge_meta
270+
edge_metas.append(edge_meta)
266271

267272
# Check for edge existence
273+
edge_meta = edge_metas[0]
268274
edge_id = edge_meta["new"]["_id"]
269275
assert await graph.has_edge(edge_id) is True
270276
assert await graph.has_edge(f"{edge_col_name}/bad_id") is False
@@ -276,9 +282,27 @@ async def test_edge_collections(db, bad_graph):
276282
updated_edge_meta = await graph.update_edge(edge, return_new=True, return_old=True)
277283
assert "new" in updated_edge_meta
278284
assert "old" in updated_edge_meta
285+
assert "edge" in updated_edge_meta
279286
edge = await graph.edge(edge_id)
280287
assert edge["subject"] == "Advanced Math"
281288

289+
# Replace an edge
290+
edge["subject"] = "Replaced Subject"
291+
edge["extra_info"] = "Some additional data"
292+
replaced_edge_meta = await graph.replace_edge(
293+
edge, return_old=True, return_new=True
294+
)
295+
assert "old" in replaced_edge_meta
296+
assert "new" in replaced_edge_meta
297+
assert "edge" in replaced_edge_meta
298+
edge = await graph.edge(edge_id)
299+
assert edge["subject"] == "Replaced Subject"
300+
301+
# Delete the edge
302+
deleted_edge = await graph.delete_edge(edge_id, return_old=True)
303+
assert "_id" in deleted_edge
304+
assert await graph.has_edge(edge_id) is False
305+
282306
# Replace the edge definition
283307
new_from_collections = [students_col_name]
284308
new_to_collections = [teachers_col_name]

0 commit comments

Comments
 (0)