Skip to content

Commit cfeffe4

Browse files
committed
Document replace
1 parent bbd6e7c commit cfeffe4

File tree

3 files changed

+123
-1
lines changed

3 files changed

+123
-1
lines changed

arangoasync/collection.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
DocumentGetError,
1414
DocumentInsertError,
1515
DocumentParseError,
16+
DocumentReplaceError,
1617
DocumentRevisionError,
1718
DocumentUpdateError,
1819
IndexCreateError,
@@ -638,3 +639,85 @@ def response_handler(resp: Response) -> bool | Json:
638639
raise DocumentUpdateError(resp, request, msg)
639640

640641
return await self._executor.execute(request, response_handler)
642+
643+
async def replace(
644+
self,
645+
document: T,
646+
ignore_revs: Optional[bool] = None,
647+
wait_for_sync: Optional[bool] = None,
648+
return_new: Optional[bool] = None,
649+
return_old: Optional[bool] = None,
650+
silent: Optional[bool] = None,
651+
refill_index_caches: Optional[bool] = None,
652+
version_attribute: Optional[str] = None,
653+
) -> Result[bool | Json]:
654+
"""Replace a document.
655+
656+
Args:
657+
document (dict): New document. It must contain the "_key" or "_id" field.
658+
Edge document must also have "_from" and "_to" fields.
659+
ignore_revs (bool | None): If set to `True`, the `_rev` attribute in the
660+
document is ignored. If this is set to `False`, then the `_rev`
661+
attribute given in the body document is taken as a precondition.
662+
The document is only replaced if the current revision is the one
663+
specified.
664+
wait_for_sync (bool | None): Wait until document has been synced to disk.
665+
return_new (bool | None): Additionally return the complete new document
666+
under the attribute `new` in the result.
667+
return_old (bool | None): Additionally return the complete old document
668+
under the attribute `old` in the result.
669+
silent (bool | None): If set to `True`, no document metadata is returned.
670+
This can be used to save resources.
671+
refill_index_caches (bool | None): Whether to add new entries to
672+
in-memory index caches if document updates affect the edge index
673+
or cache-enabled persistent indexes.
674+
version_attribute (str | None): Support for simple external versioning to
675+
document operations.
676+
677+
Returns:
678+
bool | dict: Document metadata (e.g. document id, key, revision) or `True`
679+
if **silent** is set to `True`.
680+
681+
Raises:
682+
DocumentRevisionError: If precondition was violated.
683+
DocumentReplaceError: If replace fails.
684+
685+
References:
686+
- `replace-a-document <https://docs.arangodb.com/stable/develop/http-api/documents/#replace-a-document>`__
687+
""" # noqa: E501
688+
params: Params = {}
689+
if ignore_revs is not None:
690+
params["ignoreRevs"] = ignore_revs
691+
if wait_for_sync is not None:
692+
params["waitForSync"] = wait_for_sync
693+
if return_new is not None:
694+
params["returnNew"] = return_new
695+
if return_old is not None:
696+
params["returnOld"] = return_old
697+
if silent is not None:
698+
params["silent"] = silent
699+
if refill_index_caches is not None:
700+
params["refillIndexCaches"] = refill_index_caches
701+
if version_attribute is not None:
702+
params["versionAttribute"] = version_attribute
703+
704+
request = Request(
705+
method=Method.PUT,
706+
endpoint=f"/_api/document/{self._extract_id(cast(Json, document))}",
707+
params=params,
708+
data=self._doc_serializer.dumps(document),
709+
)
710+
711+
def response_handler(resp: Response) -> bool | Json:
712+
if resp.is_success:
713+
if silent is True:
714+
return True
715+
return self._executor.deserialize(resp.raw_body)
716+
msg: Optional[str] = None
717+
if resp.status_code == HTTP_PRECONDITION_FAILED:
718+
raise DocumentRevisionError(resp, request)
719+
elif resp.status_code == HTTP_NOT_FOUND:
720+
msg = "Document, collection or transaction not found."
721+
raise DocumentReplaceError(resp, request, msg)
722+
723+
return await self._executor.execute(request, response_handler)

arangoasync/exceptions.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,10 @@ class DocumentParseError(ArangoClientError):
239239
"""Failed to parse document input."""
240240

241241

242+
class DocumentReplaceError(ArangoServerError):
243+
"""Failed to replace document."""
244+
245+
242246
class DocumentRevisionError(ArangoServerError):
243247
"""The expected and actual document revisions mismatched."""
244248

tests/test_document.py

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from arangoasync.exceptions import (
44
DocumentInsertError,
55
DocumentParseError,
6+
DocumentReplaceError,
67
DocumentUpdateError,
78
)
89
from tests.helpers import generate_col_name
@@ -49,8 +50,10 @@ async def test_document_update(doc_col, bad_col, docs):
4950
assert doc["val"] != 42
5051
await doc_col.insert(doc)
5152
doc["val"] = 42
52-
updated = await doc_col.update(doc)
53+
updated = await doc_col.update(doc, return_old=True, return_new=True)
5354
assert updated["_key"] == doc["_key"]
55+
assert "old" in updated
56+
assert "new" in updated
5457
new_value = await doc_col.get(doc)
5558
assert new_value["val"] == doc["val"]
5659

@@ -60,3 +63,35 @@ async def test_document_update(doc_col, bad_col, docs):
6063
assert updated is True
6164
new_value = await doc_col.get(doc)
6265
assert "val" not in new_value
66+
67+
68+
@pytest.mark.asyncio
69+
async def test_document_replace(doc_col, bad_col, docs):
70+
# Test updating a non-existent document
71+
with pytest.raises(DocumentReplaceError):
72+
await bad_col.replace({"_key": "non-existent", "val": 42})
73+
74+
# Verbose replace
75+
doc = docs[0]
76+
assert doc["val"] != 42
77+
await doc_col.insert(doc)
78+
doc["val"] = 42
79+
doc.pop("loc")
80+
doc.pop("text")
81+
replaced = await doc_col.replace(doc, return_old=True, return_new=True)
82+
assert replaced["_key"] == doc["_key"]
83+
new_value = await doc_col.get(doc)
84+
assert new_value["val"] == doc["val"]
85+
assert "text" not in new_value
86+
assert "loc" not in new_value
87+
assert "new" in replaced
88+
assert "old" in replaced
89+
90+
# Silent replace
91+
doc["text"] = "abcd"
92+
doc["new_entry"] = 3.14
93+
replaced = await doc_col.replace(doc, silent=True)
94+
assert replaced is True
95+
new_value = await doc_col.get(doc)
96+
assert new_value["text"] == doc["text"]
97+
assert new_value["new_entry"] == doc["new_entry"]

0 commit comments

Comments
 (0)