python-arango-async 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- arangoasync/__init__.py +5 -0
- arangoasync/aql.py +760 -0
- arangoasync/auth.py +121 -0
- arangoasync/client.py +239 -0
- arangoasync/collection.py +1688 -0
- arangoasync/compression.py +139 -0
- arangoasync/connection.py +515 -0
- arangoasync/cursor.py +262 -0
- arangoasync/database.py +1533 -0
- arangoasync/errno.py +1168 -0
- arangoasync/exceptions.py +379 -0
- arangoasync/executor.py +168 -0
- arangoasync/http.py +182 -0
- arangoasync/job.py +214 -0
- arangoasync/logger.py +3 -0
- arangoasync/request.py +107 -0
- arangoasync/resolver.py +119 -0
- arangoasync/response.py +65 -0
- arangoasync/result.py +9 -0
- arangoasync/serialization.py +111 -0
- arangoasync/typings.py +1646 -0
- arangoasync/version.py +1 -0
- python_arango_async-0.0.1.dist-info/METADATA +142 -0
- python_arango_async-0.0.1.dist-info/RECORD +27 -0
- python_arango_async-0.0.1.dist-info/WHEEL +5 -0
- python_arango_async-0.0.1.dist-info/licenses/LICENSE +21 -0
- python_arango_async-0.0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1688 @@
|
|
|
1
|
+
__all__ = ["Collection", "StandardCollection"]
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
from typing import Any, Generic, List, Optional, Sequence, Tuple, TypeVar, cast
|
|
5
|
+
|
|
6
|
+
from arangoasync.cursor import Cursor
|
|
7
|
+
from arangoasync.errno import (
|
|
8
|
+
DOCUMENT_NOT_FOUND,
|
|
9
|
+
HTTP_BAD_PARAMETER,
|
|
10
|
+
HTTP_NOT_FOUND,
|
|
11
|
+
HTTP_PRECONDITION_FAILED,
|
|
12
|
+
)
|
|
13
|
+
from arangoasync.exceptions import (
|
|
14
|
+
CollectionPropertiesError,
|
|
15
|
+
CollectionTruncateError,
|
|
16
|
+
DocumentCountError,
|
|
17
|
+
DocumentDeleteError,
|
|
18
|
+
DocumentGetError,
|
|
19
|
+
DocumentInsertError,
|
|
20
|
+
DocumentParseError,
|
|
21
|
+
DocumentReplaceError,
|
|
22
|
+
DocumentRevisionError,
|
|
23
|
+
DocumentUpdateError,
|
|
24
|
+
IndexCreateError,
|
|
25
|
+
IndexDeleteError,
|
|
26
|
+
IndexGetError,
|
|
27
|
+
IndexListError,
|
|
28
|
+
IndexLoadError,
|
|
29
|
+
SortValidationError,
|
|
30
|
+
)
|
|
31
|
+
from arangoasync.executor import ApiExecutor, DefaultApiExecutor, NonAsyncExecutor
|
|
32
|
+
from arangoasync.request import Method, Request
|
|
33
|
+
from arangoasync.response import Response
|
|
34
|
+
from arangoasync.result import Result
|
|
35
|
+
from arangoasync.serialization import Deserializer, Serializer
|
|
36
|
+
from arangoasync.typings import (
|
|
37
|
+
CollectionProperties,
|
|
38
|
+
IndexProperties,
|
|
39
|
+
Json,
|
|
40
|
+
Jsons,
|
|
41
|
+
Params,
|
|
42
|
+
RequestHeaders,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
T = TypeVar("T") # Serializer type
|
|
46
|
+
U = TypeVar("U") # Deserializer loads
|
|
47
|
+
V = TypeVar("V") # Deserializer loads_many
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class Collection(Generic[T, U, V]):
|
|
51
|
+
"""Base class for collection API wrappers.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
executor (ApiExecutor): API executor.
|
|
55
|
+
name (str): Collection name
|
|
56
|
+
doc_serializer (Serializer): Document serializer.
|
|
57
|
+
doc_deserializer (Deserializer): Document deserializer.
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
def __init__(
|
|
61
|
+
self,
|
|
62
|
+
executor: ApiExecutor,
|
|
63
|
+
name: str,
|
|
64
|
+
doc_serializer: Serializer[T],
|
|
65
|
+
doc_deserializer: Deserializer[U, V],
|
|
66
|
+
) -> None:
|
|
67
|
+
self._executor = executor
|
|
68
|
+
self._name = name
|
|
69
|
+
self._doc_serializer = doc_serializer
|
|
70
|
+
self._doc_deserializer = doc_deserializer
|
|
71
|
+
self._id_prefix = f"{self._name}/"
|
|
72
|
+
|
|
73
|
+
def _validate_id(self, doc_id: str) -> str:
|
|
74
|
+
"""Check the collection name in the document ID.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
doc_id (str): Document ID.
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
str: Verified document ID.
|
|
81
|
+
|
|
82
|
+
Raises:
|
|
83
|
+
DocumentParseError: On bad collection name.
|
|
84
|
+
"""
|
|
85
|
+
if not doc_id.startswith(self._id_prefix):
|
|
86
|
+
raise DocumentParseError(f'Bad collection name in document ID "{doc_id}"')
|
|
87
|
+
return doc_id
|
|
88
|
+
|
|
89
|
+
def _extract_id(self, body: Json) -> str:
|
|
90
|
+
"""Extract the document ID from document body.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
body (dict): Document body.
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
str: Document ID.
|
|
97
|
+
|
|
98
|
+
Raises:
|
|
99
|
+
DocumentParseError: On missing ID and key.
|
|
100
|
+
"""
|
|
101
|
+
try:
|
|
102
|
+
if "_id" in body:
|
|
103
|
+
return self._validate_id(body["_id"])
|
|
104
|
+
else:
|
|
105
|
+
key: str = body["_key"]
|
|
106
|
+
return self._id_prefix + key
|
|
107
|
+
except KeyError:
|
|
108
|
+
raise DocumentParseError('Field "_key" or "_id" required')
|
|
109
|
+
|
|
110
|
+
def _ensure_key_from_id(self, body: Json) -> Json:
|
|
111
|
+
"""Return the body with "_key" field if it has "_id" field.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
body (dict): Document body.
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
dict: Document body with "_key" field if it has "_id" field.
|
|
118
|
+
"""
|
|
119
|
+
if "_id" in body and "_key" not in body:
|
|
120
|
+
doc_id = self._validate_id(body["_id"])
|
|
121
|
+
body = body.copy()
|
|
122
|
+
body["_key"] = doc_id[len(self._id_prefix) :]
|
|
123
|
+
return body
|
|
124
|
+
|
|
125
|
+
def _prep_from_doc(
|
|
126
|
+
self,
|
|
127
|
+
document: str | Json,
|
|
128
|
+
rev: Optional[str] = None,
|
|
129
|
+
check_rev: bool = False,
|
|
130
|
+
) -> Tuple[str, Json]:
|
|
131
|
+
"""Prepare document ID, body and request headers before a query.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
document (str | dict): Document ID, key or body.
|
|
135
|
+
rev (str | None): Document revision.
|
|
136
|
+
check_rev (bool): Whether to check the revision.
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
Document ID and request headers.
|
|
140
|
+
|
|
141
|
+
Raises:
|
|
142
|
+
DocumentParseError: On missing ID and key.
|
|
143
|
+
TypeError: On bad document type.
|
|
144
|
+
"""
|
|
145
|
+
if isinstance(document, dict):
|
|
146
|
+
doc_id = self._extract_id(document)
|
|
147
|
+
rev = rev or document.get("_rev")
|
|
148
|
+
elif isinstance(document, str):
|
|
149
|
+
if "/" in document:
|
|
150
|
+
doc_id = self._validate_id(document)
|
|
151
|
+
else:
|
|
152
|
+
doc_id = self._id_prefix + document
|
|
153
|
+
else:
|
|
154
|
+
raise TypeError("Document must be str or a dict")
|
|
155
|
+
|
|
156
|
+
if not check_rev or rev is None:
|
|
157
|
+
return doc_id, {}
|
|
158
|
+
else:
|
|
159
|
+
return doc_id, {"If-Match": rev}
|
|
160
|
+
|
|
161
|
+
def _build_filter_conditions(self, filters: Optional[Json]) -> str:
|
|
162
|
+
"""Build filter conditions for an AQL query.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
filters (dict | None): Document filters.
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
str: The complete AQL filter condition.
|
|
169
|
+
"""
|
|
170
|
+
if not filters:
|
|
171
|
+
return ""
|
|
172
|
+
|
|
173
|
+
conditions = []
|
|
174
|
+
for k, v in filters.items():
|
|
175
|
+
field = k if "." in k else f"`{k}`"
|
|
176
|
+
conditions.append(f"doc.{field} == {self.serializer.dumps(v)}")
|
|
177
|
+
|
|
178
|
+
return "FILTER " + " AND ".join(conditions)
|
|
179
|
+
|
|
180
|
+
@staticmethod
|
|
181
|
+
def _is_none_or_int(obj: Any) -> bool:
|
|
182
|
+
"""Check if obj is `None` or a positive integer.
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
obj: Object to check.
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
bool: `True` if object is `None` or a positive integer.
|
|
189
|
+
"""
|
|
190
|
+
return obj is None or isinstance(obj, int) and obj >= 0
|
|
191
|
+
|
|
192
|
+
@staticmethod
|
|
193
|
+
def _is_none_or_dict(obj: Any) -> bool:
|
|
194
|
+
"""Check if obj is `None` or a dict.
|
|
195
|
+
|
|
196
|
+
Args:
|
|
197
|
+
obj: Object to check.
|
|
198
|
+
|
|
199
|
+
Returns:
|
|
200
|
+
bool: `True` if object is `None` or a dict.
|
|
201
|
+
"""
|
|
202
|
+
return obj is None or isinstance(obj, dict)
|
|
203
|
+
|
|
204
|
+
@staticmethod
|
|
205
|
+
def _validate_sort_parameters(sort: Optional[Jsons]) -> None:
|
|
206
|
+
"""Validate sort parameters for an AQL query.
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
sort (list | None): Document sort parameters.
|
|
210
|
+
|
|
211
|
+
Raises:
|
|
212
|
+
SortValidationError: If sort parameters are invalid.
|
|
213
|
+
"""
|
|
214
|
+
if not sort:
|
|
215
|
+
return
|
|
216
|
+
|
|
217
|
+
for param in sort:
|
|
218
|
+
if "sort_by" not in param or "sort_order" not in param:
|
|
219
|
+
raise SortValidationError(
|
|
220
|
+
"Each sort parameter must have 'sort_by' and 'sort_order'."
|
|
221
|
+
)
|
|
222
|
+
if param["sort_order"].upper() not in ["ASC", "DESC"]:
|
|
223
|
+
raise SortValidationError("'sort_order' must be either 'ASC' or 'DESC'")
|
|
224
|
+
|
|
225
|
+
@staticmethod
|
|
226
|
+
def _build_sort_expression(sort: Optional[Jsons]) -> str:
|
|
227
|
+
"""Build a sort condition for an AQL query.
|
|
228
|
+
|
|
229
|
+
Args:
|
|
230
|
+
sort (list | None): Document sort parameters.
|
|
231
|
+
|
|
232
|
+
Returns:
|
|
233
|
+
str: The complete AQL sort condition.
|
|
234
|
+
"""
|
|
235
|
+
if not sort:
|
|
236
|
+
return ""
|
|
237
|
+
|
|
238
|
+
sort_chunks = []
|
|
239
|
+
for sort_param in sort:
|
|
240
|
+
chunk = f"doc.{sort_param['sort_by']} {sort_param['sort_order']}"
|
|
241
|
+
sort_chunks.append(chunk)
|
|
242
|
+
|
|
243
|
+
return "SORT " + ", ".join(sort_chunks)
|
|
244
|
+
|
|
245
|
+
@property
|
|
246
|
+
def name(self) -> str:
|
|
247
|
+
"""Return the name of the collection.
|
|
248
|
+
|
|
249
|
+
Returns:
|
|
250
|
+
str: Collection name.
|
|
251
|
+
"""
|
|
252
|
+
return self._name
|
|
253
|
+
|
|
254
|
+
@property
|
|
255
|
+
def db_name(self) -> str:
|
|
256
|
+
"""Return the name of the current database.
|
|
257
|
+
|
|
258
|
+
Returns:
|
|
259
|
+
str: Database name.
|
|
260
|
+
"""
|
|
261
|
+
return self._executor.db_name
|
|
262
|
+
|
|
263
|
+
@property
|
|
264
|
+
def serializer(self) -> Serializer[Json]:
|
|
265
|
+
"""Return the serializer."""
|
|
266
|
+
return self._executor.serializer
|
|
267
|
+
|
|
268
|
+
@property
|
|
269
|
+
def deserializer(self) -> Deserializer[Json, Jsons]:
|
|
270
|
+
"""Return the deserializer."""
|
|
271
|
+
return self._executor.deserializer
|
|
272
|
+
|
|
273
|
+
async def indexes(self) -> Result[List[IndexProperties]]:
|
|
274
|
+
"""Fetch all index descriptions for the given collection.
|
|
275
|
+
|
|
276
|
+
Returns:
|
|
277
|
+
list: List of index properties.
|
|
278
|
+
|
|
279
|
+
Raises:
|
|
280
|
+
IndexListError: If retrieval fails.
|
|
281
|
+
|
|
282
|
+
References:
|
|
283
|
+
- `list-all-indexes-of-a-collection <https://docs.arangodb.com/stable/develop/http-api/indexes/#list-all-indexes-of-a-collection>`__
|
|
284
|
+
""" # noqa: E501
|
|
285
|
+
request = Request(
|
|
286
|
+
method=Method.GET,
|
|
287
|
+
endpoint="/_api/index",
|
|
288
|
+
params=dict(collection=self._name),
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
def response_handler(resp: Response) -> List[IndexProperties]:
|
|
292
|
+
if not resp.is_success:
|
|
293
|
+
raise IndexListError(resp, request)
|
|
294
|
+
data = self.deserializer.loads(resp.raw_body)
|
|
295
|
+
return [IndexProperties(item) for item in data["indexes"]]
|
|
296
|
+
|
|
297
|
+
return await self._executor.execute(request, response_handler)
|
|
298
|
+
|
|
299
|
+
async def get_index(self, id: str | int) -> Result[IndexProperties]:
|
|
300
|
+
"""Return the properties of an index.
|
|
301
|
+
|
|
302
|
+
Args:
|
|
303
|
+
id (str): Index ID. Could be either the full ID or just the index number.
|
|
304
|
+
|
|
305
|
+
Returns:
|
|
306
|
+
IndexProperties: Index properties.
|
|
307
|
+
|
|
308
|
+
Raises:
|
|
309
|
+
IndexGetError: If retrieval fails.
|
|
310
|
+
|
|
311
|
+
References:
|
|
312
|
+
`get-an-index <https://docs.arangodb.com/stable/develop/http-api/indexes/#get-an-index>`__
|
|
313
|
+
""" # noqa: E501
|
|
314
|
+
if isinstance(id, int):
|
|
315
|
+
full_id = f"{self._name}/{id}"
|
|
316
|
+
else:
|
|
317
|
+
full_id = id if "/" in id else f"{self._name}/{id}"
|
|
318
|
+
|
|
319
|
+
request = Request(
|
|
320
|
+
method=Method.GET,
|
|
321
|
+
endpoint=f"/_api/index/{full_id}",
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
def response_handler(resp: Response) -> IndexProperties:
|
|
325
|
+
if not resp.is_success:
|
|
326
|
+
raise IndexGetError(resp, request)
|
|
327
|
+
return IndexProperties(self.deserializer.loads(resp.raw_body))
|
|
328
|
+
|
|
329
|
+
return await self._executor.execute(request, response_handler)
|
|
330
|
+
|
|
331
|
+
async def add_index(
|
|
332
|
+
self,
|
|
333
|
+
type: str,
|
|
334
|
+
fields: Json | List[str],
|
|
335
|
+
options: Optional[Json] = None,
|
|
336
|
+
) -> Result[IndexProperties]:
|
|
337
|
+
"""Create an index.
|
|
338
|
+
|
|
339
|
+
Args:
|
|
340
|
+
type (str): Type attribute (ex. "persistent", "inverted", "ttl", "mdi",
|
|
341
|
+
"geo").
|
|
342
|
+
fields (dict | list): Fields to index.
|
|
343
|
+
options (dict | None): Additional index options.
|
|
344
|
+
|
|
345
|
+
Returns:
|
|
346
|
+
IndexProperties: New index properties.
|
|
347
|
+
|
|
348
|
+
Raises:
|
|
349
|
+
IndexCreateError: If index creation fails.
|
|
350
|
+
|
|
351
|
+
References:
|
|
352
|
+
- `create-an-index <https://docs.arangodb.com/stable/develop/http-api/indexes/#create-an-index>`__
|
|
353
|
+
- `create-a-persistent-index <https://docs.arangodb.com/stable/develop/http-api/indexes/persistent/#create-a-persistent-index>`__
|
|
354
|
+
- `create-an-inverted-index <https://docs.arangodb.com/stable/develop/http-api/indexes/inverted/#create-an-inverted-index>`__
|
|
355
|
+
- `create-a-ttl-index <https://docs.arangodb.com/stable/develop/http-api/indexes/ttl/#create-a-ttl-index>`__
|
|
356
|
+
- `create-a-multi-dimensional-index <https://docs.arangodb.com/stable/develop/http-api/indexes/multi-dimensional/#create-a-multi-dimensional-index>`__
|
|
357
|
+
- `create-a-geo-spatial-index <https://docs.arangodb.com/stable/develop/http-api/indexes/geo-spatial/#create-a-geo-spatial-index>`__
|
|
358
|
+
""" # noqa: E501
|
|
359
|
+
options = options or {}
|
|
360
|
+
request = Request(
|
|
361
|
+
method=Method.POST,
|
|
362
|
+
endpoint="/_api/index",
|
|
363
|
+
data=self.serializer.dumps(dict(type=type, fields=fields) | options),
|
|
364
|
+
params=dict(collection=self._name),
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
def response_handler(resp: Response) -> IndexProperties:
|
|
368
|
+
if not resp.is_success:
|
|
369
|
+
raise IndexCreateError(resp, request)
|
|
370
|
+
return IndexProperties(self.deserializer.loads(resp.raw_body))
|
|
371
|
+
|
|
372
|
+
return await self._executor.execute(request, response_handler)
|
|
373
|
+
|
|
374
|
+
async def delete_index(
|
|
375
|
+
self, id: str | int, ignore_missing: bool = False
|
|
376
|
+
) -> Result[bool]:
|
|
377
|
+
"""Delete an index.
|
|
378
|
+
|
|
379
|
+
Args:
|
|
380
|
+
id (str): Index ID. Could be either the full ID or just the index number.
|
|
381
|
+
ignore_missing (bool): Do not raise an exception on missing index.
|
|
382
|
+
|
|
383
|
+
Returns:
|
|
384
|
+
bool: `True` if the operation was successful. `False` if the index was not
|
|
385
|
+
found and **ignore_missing** was set to `True`.
|
|
386
|
+
|
|
387
|
+
Raises:
|
|
388
|
+
IndexDeleteError: If deletion fails.
|
|
389
|
+
|
|
390
|
+
References:
|
|
391
|
+
- `delete-an-index <https://docs.arangodb.com/stable/develop/http-api/indexes/#delete-an-index>`__
|
|
392
|
+
""" # noqa: E501
|
|
393
|
+
if isinstance(id, int):
|
|
394
|
+
full_id = f"{self._name}/{id}"
|
|
395
|
+
else:
|
|
396
|
+
full_id = id if "/" in id else f"{self._name}/{id}"
|
|
397
|
+
|
|
398
|
+
request = Request(
|
|
399
|
+
method=Method.DELETE,
|
|
400
|
+
endpoint=f"/_api/index/{full_id}",
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
def response_handler(resp: Response) -> bool:
|
|
404
|
+
if resp.is_success:
|
|
405
|
+
return True
|
|
406
|
+
elif ignore_missing and resp.status_code == HTTP_NOT_FOUND:
|
|
407
|
+
return False
|
|
408
|
+
raise IndexDeleteError(resp, request)
|
|
409
|
+
|
|
410
|
+
return await self._executor.execute(request, response_handler)
|
|
411
|
+
|
|
412
|
+
async def load_indexes(self) -> Result[bool]:
|
|
413
|
+
"""Cache this collection’s index entries in the main memory.
|
|
414
|
+
|
|
415
|
+
Returns:
|
|
416
|
+
bool: `True` if the operation was successful.
|
|
417
|
+
|
|
418
|
+
Raises:
|
|
419
|
+
IndexLoadError: If loading fails.
|
|
420
|
+
|
|
421
|
+
References:
|
|
422
|
+
- `load-collection-indexes-into-memory <https://docs.arangodb.com/stable/develop/http-api/collections/#load-collection-indexes-into-memory>`__
|
|
423
|
+
""" # noqa: E501
|
|
424
|
+
request = Request(
|
|
425
|
+
method=Method.PUT,
|
|
426
|
+
endpoint=f"/_api/collection/{self._name}/loadIndexesIntoMemory",
|
|
427
|
+
)
|
|
428
|
+
|
|
429
|
+
def response_handler(resp: Response) -> bool:
|
|
430
|
+
if resp.is_success:
|
|
431
|
+
return True
|
|
432
|
+
raise IndexLoadError(resp, request)
|
|
433
|
+
|
|
434
|
+
return await self._executor.execute(request, response_handler)
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
class StandardCollection(Collection[T, U, V]):
|
|
438
|
+
"""Standard collection API wrapper.
|
|
439
|
+
|
|
440
|
+
Args:
|
|
441
|
+
executor (ApiExecutor): API executor.
|
|
442
|
+
name (str): Collection name
|
|
443
|
+
doc_serializer (Serializer): Document serializer.
|
|
444
|
+
doc_deserializer (Deserializer): Document deserializer.
|
|
445
|
+
"""
|
|
446
|
+
|
|
447
|
+
def __init__(
|
|
448
|
+
self,
|
|
449
|
+
executor: ApiExecutor,
|
|
450
|
+
name: str,
|
|
451
|
+
doc_serializer: Serializer[T],
|
|
452
|
+
doc_deserializer: Deserializer[U, V],
|
|
453
|
+
) -> None:
|
|
454
|
+
super().__init__(executor, name, doc_serializer, doc_deserializer)
|
|
455
|
+
|
|
456
|
+
def __repr__(self) -> str:
|
|
457
|
+
return f"<StandardCollection {self.name}>"
|
|
458
|
+
|
|
459
|
+
async def properties(self) -> Result[CollectionProperties]:
|
|
460
|
+
"""Return the full properties of the current collection.
|
|
461
|
+
|
|
462
|
+
Returns:
|
|
463
|
+
CollectionProperties: Properties.
|
|
464
|
+
|
|
465
|
+
Raises:
|
|
466
|
+
CollectionPropertiesError: If retrieval fails.
|
|
467
|
+
|
|
468
|
+
References:
|
|
469
|
+
- `get-the-properties-of-a-collection <https://docs.arangodb.com/stable/develop/http-api/collections/#get-the-properties-of-a-collection>`__
|
|
470
|
+
""" # noqa: E501
|
|
471
|
+
request = Request(
|
|
472
|
+
method=Method.GET,
|
|
473
|
+
endpoint=f"/_api/collection/{self.name}/properties",
|
|
474
|
+
)
|
|
475
|
+
|
|
476
|
+
def response_handler(resp: Response) -> CollectionProperties:
|
|
477
|
+
if not resp.is_success:
|
|
478
|
+
raise CollectionPropertiesError(resp, request)
|
|
479
|
+
return CollectionProperties(self._executor.deserialize(resp.raw_body))
|
|
480
|
+
|
|
481
|
+
return await self._executor.execute(request, response_handler)
|
|
482
|
+
|
|
483
|
+
async def truncate(
|
|
484
|
+
self,
|
|
485
|
+
wait_for_sync: Optional[bool] = None,
|
|
486
|
+
compact: Optional[bool] = None,
|
|
487
|
+
) -> None:
|
|
488
|
+
"""Removes all documents, but leaves indexes intact.
|
|
489
|
+
|
|
490
|
+
Args:
|
|
491
|
+
wait_for_sync (bool | None): If set to `True`, the data is synchronized
|
|
492
|
+
to disk before returning from the truncate operation.
|
|
493
|
+
compact (bool | None): If set to `True`, the storage engine is told to
|
|
494
|
+
start a compaction in order to free up disk space. This can be
|
|
495
|
+
resource intensive. If the only intention is to start over with an
|
|
496
|
+
empty collection, specify `False`.
|
|
497
|
+
|
|
498
|
+
Raises:
|
|
499
|
+
CollectionTruncateError: If truncation fails.
|
|
500
|
+
|
|
501
|
+
References:
|
|
502
|
+
- `truncate-a-collection <https://docs.arangodb.com/stable/develop/http-api/collections/#truncate-a-collection>`__
|
|
503
|
+
""" # noqa: E501
|
|
504
|
+
params: Params = {}
|
|
505
|
+
if wait_for_sync is not None:
|
|
506
|
+
params["waitForSync"] = wait_for_sync
|
|
507
|
+
if compact is not None:
|
|
508
|
+
params["compact"] = compact
|
|
509
|
+
|
|
510
|
+
request = Request(
|
|
511
|
+
method=Method.PUT,
|
|
512
|
+
endpoint=f"/_api/collection/{self.name}/truncate",
|
|
513
|
+
params=params,
|
|
514
|
+
)
|
|
515
|
+
|
|
516
|
+
def response_handler(resp: Response) -> None:
|
|
517
|
+
if not resp.is_success:
|
|
518
|
+
raise CollectionTruncateError(resp, request)
|
|
519
|
+
|
|
520
|
+
await self._executor.execute(request, response_handler)
|
|
521
|
+
|
|
522
|
+
async def count(self) -> Result[int]:
|
|
523
|
+
"""Return the total document count.
|
|
524
|
+
|
|
525
|
+
Returns:
|
|
526
|
+
int: Total document count.
|
|
527
|
+
|
|
528
|
+
Raises:
|
|
529
|
+
DocumentCountError: If retrieval fails.
|
|
530
|
+
"""
|
|
531
|
+
request = Request(
|
|
532
|
+
method=Method.GET, endpoint=f"/_api/collection/{self.name}/count"
|
|
533
|
+
)
|
|
534
|
+
|
|
535
|
+
def response_handler(resp: Response) -> int:
|
|
536
|
+
if resp.is_success:
|
|
537
|
+
result: int = self.deserializer.loads(resp.raw_body)["count"]
|
|
538
|
+
return result
|
|
539
|
+
raise DocumentCountError(resp, request)
|
|
540
|
+
|
|
541
|
+
return await self._executor.execute(request, response_handler)
|
|
542
|
+
|
|
543
|
+
async def get(
|
|
544
|
+
self,
|
|
545
|
+
document: str | Json,
|
|
546
|
+
allow_dirty_read: bool = False,
|
|
547
|
+
if_match: Optional[str] = None,
|
|
548
|
+
if_none_match: Optional[str] = None,
|
|
549
|
+
) -> Result[Optional[U]]:
|
|
550
|
+
"""Return a document.
|
|
551
|
+
|
|
552
|
+
Args:
|
|
553
|
+
document (str | dict): Document ID, key or body.
|
|
554
|
+
Document body must contain the "_id" or "_key" field.
|
|
555
|
+
allow_dirty_read (bool): Allow reads from followers in a cluster.
|
|
556
|
+
if_match (str | None): The document is returned, if it has the same
|
|
557
|
+
revision as the given ETag.
|
|
558
|
+
if_none_match (str | None): The document is returned, if it has a
|
|
559
|
+
different revision than the given ETag.
|
|
560
|
+
|
|
561
|
+
Returns:
|
|
562
|
+
Document or `None` if not found.
|
|
563
|
+
|
|
564
|
+
Raises:
|
|
565
|
+
DocumentRevisionError: If the revision is incorrect.
|
|
566
|
+
DocumentGetError: If retrieval fails.
|
|
567
|
+
|
|
568
|
+
References:
|
|
569
|
+
- `get-a-document <https://docs.arangodb.com/stable/develop/http-api/documents/#get-a-document>`__
|
|
570
|
+
""" # noqa: E501
|
|
571
|
+
handle, _ = self._prep_from_doc(document)
|
|
572
|
+
|
|
573
|
+
headers: RequestHeaders = {}
|
|
574
|
+
if allow_dirty_read:
|
|
575
|
+
headers["x-arango-allow-dirty-read"] = "true"
|
|
576
|
+
if if_match is not None:
|
|
577
|
+
headers["If-Match"] = if_match
|
|
578
|
+
if if_none_match is not None:
|
|
579
|
+
headers["If-None-Match"] = if_none_match
|
|
580
|
+
|
|
581
|
+
request = Request(
|
|
582
|
+
method=Method.GET,
|
|
583
|
+
endpoint=f"/_api/document/{handle}",
|
|
584
|
+
headers=headers,
|
|
585
|
+
)
|
|
586
|
+
|
|
587
|
+
def response_handler(resp: Response) -> Optional[U]:
|
|
588
|
+
if resp.is_success:
|
|
589
|
+
return self._doc_deserializer.loads(resp.raw_body)
|
|
590
|
+
elif resp.status_code == HTTP_NOT_FOUND:
|
|
591
|
+
if resp.error_code == DOCUMENT_NOT_FOUND:
|
|
592
|
+
return None
|
|
593
|
+
else:
|
|
594
|
+
raise DocumentGetError(resp, request)
|
|
595
|
+
elif resp.status_code == HTTP_PRECONDITION_FAILED:
|
|
596
|
+
raise DocumentRevisionError(resp, request)
|
|
597
|
+
else:
|
|
598
|
+
raise DocumentGetError(resp, request)
|
|
599
|
+
|
|
600
|
+
return await self._executor.execute(request, response_handler)
|
|
601
|
+
|
|
602
|
+
async def has(
|
|
603
|
+
self,
|
|
604
|
+
document: str | Json,
|
|
605
|
+
allow_dirty_read: bool = False,
|
|
606
|
+
if_match: Optional[str] = None,
|
|
607
|
+
if_none_match: Optional[str] = None,
|
|
608
|
+
) -> Result[bool]:
|
|
609
|
+
"""Check if a document exists in the collection.
|
|
610
|
+
|
|
611
|
+
Args:
|
|
612
|
+
document (str | dict): Document ID, key or body.
|
|
613
|
+
Document body must contain the "_id" or "_key" field.
|
|
614
|
+
allow_dirty_read (bool): Allow reads from followers in a cluster.
|
|
615
|
+
if_match (str | None): The document is returned, if it has the same
|
|
616
|
+
revision as the given ETag.
|
|
617
|
+
if_none_match (str | None): The document is returned, if it has a
|
|
618
|
+
different revision than the given ETag.
|
|
619
|
+
|
|
620
|
+
Returns:
|
|
621
|
+
`True` if the document exists, `False` otherwise.
|
|
622
|
+
|
|
623
|
+
Raises:
|
|
624
|
+
DocumentRevisionError: If the revision is incorrect.
|
|
625
|
+
DocumentGetError: If retrieval fails.
|
|
626
|
+
|
|
627
|
+
References:
|
|
628
|
+
- `get-a-document-header <https://docs.arangodb.com/stable/develop/http-api/documents/#get-a-document-header>`__
|
|
629
|
+
""" # noqa: E501
|
|
630
|
+
handle, _ = self._prep_from_doc(document)
|
|
631
|
+
|
|
632
|
+
headers: RequestHeaders = {}
|
|
633
|
+
if allow_dirty_read:
|
|
634
|
+
headers["x-arango-allow-dirty-read"] = "true"
|
|
635
|
+
if if_match is not None:
|
|
636
|
+
headers["If-Match"] = if_match
|
|
637
|
+
if if_none_match is not None:
|
|
638
|
+
headers["If-None-Match"] = if_none_match
|
|
639
|
+
|
|
640
|
+
request = Request(
|
|
641
|
+
method=Method.HEAD,
|
|
642
|
+
endpoint=f"/_api/document/{handle}",
|
|
643
|
+
headers=headers,
|
|
644
|
+
)
|
|
645
|
+
|
|
646
|
+
def response_handler(resp: Response) -> bool:
|
|
647
|
+
if resp.is_success:
|
|
648
|
+
return True
|
|
649
|
+
elif resp.status_code == HTTP_NOT_FOUND:
|
|
650
|
+
return False
|
|
651
|
+
elif resp.status_code == HTTP_PRECONDITION_FAILED:
|
|
652
|
+
raise DocumentRevisionError(resp, request)
|
|
653
|
+
else:
|
|
654
|
+
raise DocumentGetError(resp, request)
|
|
655
|
+
|
|
656
|
+
return await self._executor.execute(request, response_handler)
|
|
657
|
+
|
|
658
|
+
async def insert(
|
|
659
|
+
self,
|
|
660
|
+
document: T,
|
|
661
|
+
wait_for_sync: Optional[bool] = None,
|
|
662
|
+
return_new: Optional[bool] = None,
|
|
663
|
+
return_old: Optional[bool] = None,
|
|
664
|
+
silent: Optional[bool] = None,
|
|
665
|
+
overwrite: Optional[bool] = None,
|
|
666
|
+
overwrite_mode: Optional[str] = None,
|
|
667
|
+
keep_null: Optional[bool] = None,
|
|
668
|
+
merge_objects: Optional[bool] = None,
|
|
669
|
+
refill_index_caches: Optional[bool] = None,
|
|
670
|
+
version_attribute: Optional[str] = None,
|
|
671
|
+
) -> Result[bool | Json]:
|
|
672
|
+
"""Insert a new document.
|
|
673
|
+
|
|
674
|
+
Args:
|
|
675
|
+
document (dict): Document to insert. If it contains the "_key" or "_id"
|
|
676
|
+
field, the value is used as the key of the new document (otherwise
|
|
677
|
+
it is auto-generated). Any "_rev" field is ignored.
|
|
678
|
+
wait_for_sync (bool | None): Wait until document has been synced to disk.
|
|
679
|
+
return_new (bool | None): Additionally return the complete new document
|
|
680
|
+
under the attribute `new` in the result.
|
|
681
|
+
return_old (bool | None): Additionally return the complete old document
|
|
682
|
+
under the attribute `old` in the result. Only available if the
|
|
683
|
+
`overwrite` option is used.
|
|
684
|
+
silent (bool | None): If set to `True`, no document metadata is returned.
|
|
685
|
+
This can be used to save resources.
|
|
686
|
+
overwrite (bool | None): If set to `True`, operation does not fail on
|
|
687
|
+
duplicate key and existing document is overwritten (replace-insert).
|
|
688
|
+
overwrite_mode (str | None): Overwrite mode. Supersedes **overwrite**
|
|
689
|
+
option. May be one of "ignore", "replace", "update" or "conflict".
|
|
690
|
+
keep_null (bool | None): If set to `True`, fields with value None are
|
|
691
|
+
retained in the document. Otherwise, they are removed completely.
|
|
692
|
+
Applies only when **overwrite_mode** is set to "update"
|
|
693
|
+
(update-insert).
|
|
694
|
+
merge_objects (bool | None): If set to `True`, sub-dictionaries are merged
|
|
695
|
+
instead of the new one overwriting the old one. Applies only when
|
|
696
|
+
**overwrite_mode** is set to "update" (update-insert).
|
|
697
|
+
refill_index_caches (bool | None): Whether to add new entries to
|
|
698
|
+
in-memory index caches if document insertions affect the edge index
|
|
699
|
+
or cache-enabled persistent indexes.
|
|
700
|
+
version_attribute (str | None): Support for simple external versioning to
|
|
701
|
+
document operations. Only applicable if **overwrite** is set to `True`
|
|
702
|
+
or **overwrite_mode** is set to "update" or "replace".
|
|
703
|
+
|
|
704
|
+
Returns:
|
|
705
|
+
bool | dict: Document metadata (e.g. document id, key, revision) or `True`
|
|
706
|
+
if **silent** is set to `True`.
|
|
707
|
+
|
|
708
|
+
Raises:
|
|
709
|
+
DocumentInsertError: If insertion fails.
|
|
710
|
+
|
|
711
|
+
References:
|
|
712
|
+
- `create-a-document <https://docs.arangodb.com/stable/develop/http-api/documents/#create-a-document>`__
|
|
713
|
+
""" # noqa: E501
|
|
714
|
+
if isinstance(document, dict):
|
|
715
|
+
# We assume that the document deserializer works with dictionaries.
|
|
716
|
+
document = cast(T, self._ensure_key_from_id(document))
|
|
717
|
+
|
|
718
|
+
params: Params = {}
|
|
719
|
+
if wait_for_sync is not None:
|
|
720
|
+
params["waitForSync"] = wait_for_sync
|
|
721
|
+
if return_new is not None:
|
|
722
|
+
params["returnNew"] = return_new
|
|
723
|
+
if return_old is not None:
|
|
724
|
+
params["returnOld"] = return_old
|
|
725
|
+
if silent is not None:
|
|
726
|
+
params["silent"] = silent
|
|
727
|
+
if overwrite is not None:
|
|
728
|
+
params["overwrite"] = overwrite
|
|
729
|
+
if overwrite_mode is not None:
|
|
730
|
+
params["overwriteMode"] = overwrite_mode
|
|
731
|
+
if keep_null is not None:
|
|
732
|
+
params["keepNull"] = keep_null
|
|
733
|
+
if merge_objects is not None:
|
|
734
|
+
params["mergeObjects"] = merge_objects
|
|
735
|
+
if refill_index_caches is not None:
|
|
736
|
+
params["refillIndexCaches"] = refill_index_caches
|
|
737
|
+
if version_attribute is not None:
|
|
738
|
+
params["versionAttribute"] = version_attribute
|
|
739
|
+
|
|
740
|
+
request = Request(
|
|
741
|
+
method=Method.POST,
|
|
742
|
+
endpoint=f"/_api/document/{self._name}",
|
|
743
|
+
params=params,
|
|
744
|
+
data=self._doc_serializer.dumps(document),
|
|
745
|
+
)
|
|
746
|
+
|
|
747
|
+
def response_handler(resp: Response) -> bool | Json:
|
|
748
|
+
if resp.is_success:
|
|
749
|
+
if silent is True:
|
|
750
|
+
return True
|
|
751
|
+
return self._executor.deserialize(resp.raw_body)
|
|
752
|
+
msg: Optional[str] = None
|
|
753
|
+
if resp.status_code == HTTP_BAD_PARAMETER:
|
|
754
|
+
msg = (
|
|
755
|
+
"Body does not contain a valid JSON representation of "
|
|
756
|
+
"one document."
|
|
757
|
+
)
|
|
758
|
+
elif resp.status_code == HTTP_NOT_FOUND:
|
|
759
|
+
msg = "Collection not found."
|
|
760
|
+
raise DocumentInsertError(resp, request, msg)
|
|
761
|
+
|
|
762
|
+
return await self._executor.execute(request, response_handler)
|
|
763
|
+
|
|
764
|
+
async def update(
|
|
765
|
+
self,
|
|
766
|
+
document: T,
|
|
767
|
+
ignore_revs: Optional[bool] = None,
|
|
768
|
+
wait_for_sync: Optional[bool] = None,
|
|
769
|
+
return_new: Optional[bool] = None,
|
|
770
|
+
return_old: Optional[bool] = None,
|
|
771
|
+
silent: Optional[bool] = None,
|
|
772
|
+
keep_null: Optional[bool] = None,
|
|
773
|
+
merge_objects: Optional[bool] = None,
|
|
774
|
+
refill_index_caches: Optional[bool] = None,
|
|
775
|
+
version_attribute: Optional[str] = None,
|
|
776
|
+
if_match: Optional[str] = None,
|
|
777
|
+
) -> Result[bool | Json]:
|
|
778
|
+
"""Insert a new document.
|
|
779
|
+
|
|
780
|
+
Args:
|
|
781
|
+
document (dict): Partial or full document with the updated values.
|
|
782
|
+
It must contain the "_key" or "_id" field.
|
|
783
|
+
ignore_revs (bool | None): If set to `True`, the `_rev` attribute in the
|
|
784
|
+
document is ignored. If this is set to `False`, then the `_rev`
|
|
785
|
+
attribute given in the body document is taken as a precondition.
|
|
786
|
+
The document is only updated if the current revision is the one
|
|
787
|
+
specified.
|
|
788
|
+
wait_for_sync (bool | None): Wait until document has been synced to disk.
|
|
789
|
+
return_new (bool | None): Additionally return the complete new document
|
|
790
|
+
under the attribute `new` in the result.
|
|
791
|
+
return_old (bool | None): Additionally return the complete old document
|
|
792
|
+
under the attribute `old` in the result.
|
|
793
|
+
silent (bool | None): If set to `True`, no document metadata is returned.
|
|
794
|
+
This can be used to save resources.
|
|
795
|
+
keep_null (bool | None): If the intention is to delete existing attributes
|
|
796
|
+
with the patch command, set this parameter to `False`.
|
|
797
|
+
merge_objects (bool | None): Controls whether objects (not arrays) are
|
|
798
|
+
merged if present in both the existing and the patch document.
|
|
799
|
+
If set to `False`, the value in the patch document overwrites the
|
|
800
|
+
existing document’s value. If set to `True`, objects are merged.
|
|
801
|
+
refill_index_caches (bool | None): Whether to add new entries to
|
|
802
|
+
in-memory index caches if document updates affect the edge index
|
|
803
|
+
or cache-enabled persistent indexes.
|
|
804
|
+
version_attribute (str | None): Support for simple external versioning to
|
|
805
|
+
document operations.
|
|
806
|
+
if_match (str | None): You can conditionally update a document based on a
|
|
807
|
+
target revision id by using the "if-match" HTTP header.
|
|
808
|
+
|
|
809
|
+
Returns:
|
|
810
|
+
bool | dict: Document metadata (e.g. document id, key, revision) or `True`
|
|
811
|
+
if **silent** is set to `True`.
|
|
812
|
+
|
|
813
|
+
Raises:
|
|
814
|
+
DocumentRevisionError: If precondition was violated.
|
|
815
|
+
DocumentUpdateError: If update fails.
|
|
816
|
+
|
|
817
|
+
References:
|
|
818
|
+
- `update-a-document <https://docs.arangodb.com/stable/develop/http-api/documents/#update-a-document>`__
|
|
819
|
+
""" # noqa: E501
|
|
820
|
+
params: Params = {}
|
|
821
|
+
if ignore_revs is not None:
|
|
822
|
+
params["ignoreRevs"] = ignore_revs
|
|
823
|
+
if wait_for_sync is not None:
|
|
824
|
+
params["waitForSync"] = wait_for_sync
|
|
825
|
+
if return_new is not None:
|
|
826
|
+
params["returnNew"] = return_new
|
|
827
|
+
if return_old is not None:
|
|
828
|
+
params["returnOld"] = return_old
|
|
829
|
+
if silent is not None:
|
|
830
|
+
params["silent"] = silent
|
|
831
|
+
if keep_null is not None:
|
|
832
|
+
params["keepNull"] = keep_null
|
|
833
|
+
if merge_objects is not None:
|
|
834
|
+
params["mergeObjects"] = merge_objects
|
|
835
|
+
if refill_index_caches is not None:
|
|
836
|
+
params["refillIndexCaches"] = refill_index_caches
|
|
837
|
+
if version_attribute is not None:
|
|
838
|
+
params["versionAttribute"] = version_attribute
|
|
839
|
+
|
|
840
|
+
headers: RequestHeaders = {}
|
|
841
|
+
if if_match is not None:
|
|
842
|
+
headers["If-Match"] = if_match
|
|
843
|
+
|
|
844
|
+
request = Request(
|
|
845
|
+
method=Method.PATCH,
|
|
846
|
+
endpoint=f"/_api/document/{self._extract_id(cast(Json, document))}",
|
|
847
|
+
params=params,
|
|
848
|
+
headers=headers,
|
|
849
|
+
data=self._doc_serializer.dumps(document),
|
|
850
|
+
)
|
|
851
|
+
|
|
852
|
+
def response_handler(resp: Response) -> bool | Json:
|
|
853
|
+
if resp.is_success:
|
|
854
|
+
if silent is True:
|
|
855
|
+
return True
|
|
856
|
+
return self._executor.deserialize(resp.raw_body)
|
|
857
|
+
msg: Optional[str] = None
|
|
858
|
+
if resp.status_code == HTTP_PRECONDITION_FAILED:
|
|
859
|
+
raise DocumentRevisionError(resp, request)
|
|
860
|
+
elif resp.status_code == HTTP_NOT_FOUND:
|
|
861
|
+
msg = "Document, collection or transaction not found."
|
|
862
|
+
raise DocumentUpdateError(resp, request, msg)
|
|
863
|
+
|
|
864
|
+
return await self._executor.execute(request, response_handler)
|
|
865
|
+
|
|
866
|
+
async def replace(
|
|
867
|
+
self,
|
|
868
|
+
document: T,
|
|
869
|
+
ignore_revs: Optional[bool] = None,
|
|
870
|
+
wait_for_sync: Optional[bool] = None,
|
|
871
|
+
return_new: Optional[bool] = None,
|
|
872
|
+
return_old: Optional[bool] = None,
|
|
873
|
+
silent: Optional[bool] = None,
|
|
874
|
+
refill_index_caches: Optional[bool] = None,
|
|
875
|
+
version_attribute: Optional[str] = None,
|
|
876
|
+
if_match: Optional[str] = None,
|
|
877
|
+
) -> Result[bool | Json]:
|
|
878
|
+
"""Replace a document.
|
|
879
|
+
|
|
880
|
+
Args:
|
|
881
|
+
document (dict): New document. It must contain the "_key" or "_id" field.
|
|
882
|
+
Edge document must also have "_from" and "_to" fields.
|
|
883
|
+
ignore_revs (bool | None): If set to `True`, the `_rev` attribute in the
|
|
884
|
+
document is ignored. If this is set to `False`, then the `_rev`
|
|
885
|
+
attribute given in the body document is taken as a precondition.
|
|
886
|
+
The document is only replaced if the current revision is the one
|
|
887
|
+
specified.
|
|
888
|
+
wait_for_sync (bool | None): Wait until document has been synced to disk.
|
|
889
|
+
return_new (bool | None): Additionally return the complete new document
|
|
890
|
+
under the attribute `new` in the result.
|
|
891
|
+
return_old (bool | None): Additionally return the complete old document
|
|
892
|
+
under the attribute `old` in the result.
|
|
893
|
+
silent (bool | None): If set to `True`, no document metadata is returned.
|
|
894
|
+
This can be used to save resources.
|
|
895
|
+
refill_index_caches (bool | None): Whether to add new entries to
|
|
896
|
+
in-memory index caches if document updates affect the edge index
|
|
897
|
+
or cache-enabled persistent indexes.
|
|
898
|
+
version_attribute (str | None): Support for simple external versioning to
|
|
899
|
+
document operations.
|
|
900
|
+
if_match (str | None): You can conditionally replace a document based on a
|
|
901
|
+
target revision id by using the "if-match" HTTP header.
|
|
902
|
+
|
|
903
|
+
Returns:
|
|
904
|
+
bool | dict: Document metadata (e.g. document id, key, revision) or `True`
|
|
905
|
+
if **silent** is set to `True`.
|
|
906
|
+
|
|
907
|
+
Raises:
|
|
908
|
+
DocumentRevisionError: If precondition was violated.
|
|
909
|
+
DocumentReplaceError: If replace fails.
|
|
910
|
+
|
|
911
|
+
References:
|
|
912
|
+
- `replace-a-document <https://docs.arangodb.com/stable/develop/http-api/documents/#replace-a-document>`__
|
|
913
|
+
""" # noqa: E501
|
|
914
|
+
params: Params = {}
|
|
915
|
+
if ignore_revs is not None:
|
|
916
|
+
params["ignoreRevs"] = ignore_revs
|
|
917
|
+
if wait_for_sync is not None:
|
|
918
|
+
params["waitForSync"] = wait_for_sync
|
|
919
|
+
if return_new is not None:
|
|
920
|
+
params["returnNew"] = return_new
|
|
921
|
+
if return_old is not None:
|
|
922
|
+
params["returnOld"] = return_old
|
|
923
|
+
if silent is not None:
|
|
924
|
+
params["silent"] = silent
|
|
925
|
+
if refill_index_caches is not None:
|
|
926
|
+
params["refillIndexCaches"] = refill_index_caches
|
|
927
|
+
if version_attribute is not None:
|
|
928
|
+
params["versionAttribute"] = version_attribute
|
|
929
|
+
|
|
930
|
+
headers: RequestHeaders = {}
|
|
931
|
+
if if_match is not None:
|
|
932
|
+
headers["If-Match"] = if_match
|
|
933
|
+
|
|
934
|
+
request = Request(
|
|
935
|
+
method=Method.PUT,
|
|
936
|
+
endpoint=f"/_api/document/{self._extract_id(cast(Json, document))}",
|
|
937
|
+
params=params,
|
|
938
|
+
headers=headers,
|
|
939
|
+
data=self._doc_serializer.dumps(document),
|
|
940
|
+
)
|
|
941
|
+
|
|
942
|
+
def response_handler(resp: Response) -> bool | Json:
|
|
943
|
+
if resp.is_success:
|
|
944
|
+
if silent is True:
|
|
945
|
+
return True
|
|
946
|
+
return self._executor.deserialize(resp.raw_body)
|
|
947
|
+
msg: Optional[str] = None
|
|
948
|
+
if resp.status_code == HTTP_PRECONDITION_FAILED:
|
|
949
|
+
raise DocumentRevisionError(resp, request)
|
|
950
|
+
elif resp.status_code == HTTP_NOT_FOUND:
|
|
951
|
+
msg = "Document, collection or transaction not found."
|
|
952
|
+
raise DocumentReplaceError(resp, request, msg)
|
|
953
|
+
|
|
954
|
+
return await self._executor.execute(request, response_handler)
|
|
955
|
+
|
|
956
|
+
async def delete(
|
|
957
|
+
self,
|
|
958
|
+
document: T,
|
|
959
|
+
ignore_revs: Optional[bool] = None,
|
|
960
|
+
ignore_missing: bool = False,
|
|
961
|
+
wait_for_sync: Optional[bool] = None,
|
|
962
|
+
return_old: Optional[bool] = None,
|
|
963
|
+
silent: Optional[bool] = None,
|
|
964
|
+
refill_index_caches: Optional[bool] = None,
|
|
965
|
+
if_match: Optional[str] = None,
|
|
966
|
+
) -> Result[bool | Json]:
|
|
967
|
+
"""Delete a document.
|
|
968
|
+
|
|
969
|
+
Args:
|
|
970
|
+
document (dict): Document ID, key or body. The body must contain the
|
|
971
|
+
"_key" or "_id" field.
|
|
972
|
+
ignore_revs (bool | None): If set to `True`, the `_rev` attribute in the
|
|
973
|
+
document is ignored. If this is set to `False`, then the `_rev`
|
|
974
|
+
attribute given in the body document is taken as a precondition.
|
|
975
|
+
The document is only replaced if the current revision is the one
|
|
976
|
+
specified.
|
|
977
|
+
ignore_missing (bool): Do not raise an exception on missing document.
|
|
978
|
+
This parameter has no effect in transactions where an exception is
|
|
979
|
+
always raised on failures.
|
|
980
|
+
wait_for_sync (bool | None): Wait until operation has been synced to disk.
|
|
981
|
+
return_old (bool | None): Additionally return the complete old document
|
|
982
|
+
under the attribute `old` in the result.
|
|
983
|
+
silent (bool | None): If set to `True`, no document metadata is returned.
|
|
984
|
+
This can be used to save resources.
|
|
985
|
+
refill_index_caches (bool | None): Whether to add new entries to
|
|
986
|
+
in-memory index caches if document updates affect the edge index
|
|
987
|
+
or cache-enabled persistent indexes.
|
|
988
|
+
if_match (bool | None): You can conditionally remove a document based
|
|
989
|
+
on a target revision id by using the "if-match" HTTP header.
|
|
990
|
+
|
|
991
|
+
Returns:
|
|
992
|
+
bool | dict: Document metadata (e.g. document id, key, revision) or `True`
|
|
993
|
+
if **silent** is set to `True` and the document was found.
|
|
994
|
+
|
|
995
|
+
Raises:
|
|
996
|
+
DocumentRevisionError: If precondition was violated.
|
|
997
|
+
DocumentDeleteError: If deletion fails.
|
|
998
|
+
|
|
999
|
+
References:
|
|
1000
|
+
- `remove-a-document <https://docs.arangodb.com/stable/develop/http-api/documents/#remove-a-document>`__
|
|
1001
|
+
""" # noqa: E501
|
|
1002
|
+
params: Params = {}
|
|
1003
|
+
if ignore_revs is not None:
|
|
1004
|
+
params["ignoreRevs"] = ignore_revs
|
|
1005
|
+
if wait_for_sync is not None:
|
|
1006
|
+
params["waitForSync"] = wait_for_sync
|
|
1007
|
+
if return_old is not None:
|
|
1008
|
+
params["returnOld"] = return_old
|
|
1009
|
+
if silent is not None:
|
|
1010
|
+
params["silent"] = silent
|
|
1011
|
+
if refill_index_caches is not None:
|
|
1012
|
+
params["refillIndexCaches"] = refill_index_caches
|
|
1013
|
+
|
|
1014
|
+
headers: RequestHeaders = {}
|
|
1015
|
+
if if_match is not None:
|
|
1016
|
+
headers["If-Match"] = if_match
|
|
1017
|
+
|
|
1018
|
+
request = Request(
|
|
1019
|
+
method=Method.DELETE,
|
|
1020
|
+
endpoint=f"/_api/document/{self._extract_id(cast(Json, document))}",
|
|
1021
|
+
params=params,
|
|
1022
|
+
headers=headers,
|
|
1023
|
+
)
|
|
1024
|
+
|
|
1025
|
+
def response_handler(resp: Response) -> bool | Json:
|
|
1026
|
+
if resp.is_success:
|
|
1027
|
+
if silent is True:
|
|
1028
|
+
return True
|
|
1029
|
+
return self._executor.deserialize(resp.raw_body)
|
|
1030
|
+
msg: Optional[str] = None
|
|
1031
|
+
if resp.status_code == HTTP_PRECONDITION_FAILED:
|
|
1032
|
+
raise DocumentRevisionError(resp, request)
|
|
1033
|
+
elif resp.status_code == HTTP_NOT_FOUND:
|
|
1034
|
+
if resp.error_code == DOCUMENT_NOT_FOUND and ignore_missing:
|
|
1035
|
+
return False
|
|
1036
|
+
msg = "Document, collection or transaction not found."
|
|
1037
|
+
raise DocumentDeleteError(resp, request, msg)
|
|
1038
|
+
|
|
1039
|
+
return await self._executor.execute(request, response_handler)
|
|
1040
|
+
|
|
1041
|
+
async def get_many(
|
|
1042
|
+
self,
|
|
1043
|
+
documents: Sequence[str | T],
|
|
1044
|
+
allow_dirty_read: Optional[bool] = None,
|
|
1045
|
+
ignore_revs: Optional[bool] = None,
|
|
1046
|
+
) -> Result[V]:
|
|
1047
|
+
"""Return multiple documents ignoring any missing ones.
|
|
1048
|
+
|
|
1049
|
+
Args:
|
|
1050
|
+
documents (list): List of document IDs, keys or bodies. A search document
|
|
1051
|
+
must contain at least a value for the `_key` field. A value for `_rev`
|
|
1052
|
+
may be specified to verify whether the document has the same revision
|
|
1053
|
+
value, unless `ignoreRevs` is set to false.
|
|
1054
|
+
allow_dirty_read (bool | None): Allow reads from followers in a cluster.
|
|
1055
|
+
ignore_revs (bool | None): If set to `True`, the `_rev` attribute in the
|
|
1056
|
+
document is ignored. If this is set to `False`, then the `_rev`
|
|
1057
|
+
attribute given in the body document is taken as a precondition.
|
|
1058
|
+
The document is only replaced if the current revision is the one
|
|
1059
|
+
specified.
|
|
1060
|
+
|
|
1061
|
+
Returns:
|
|
1062
|
+
list: List of documents. Missing ones are not included.
|
|
1063
|
+
|
|
1064
|
+
Raises:
|
|
1065
|
+
DocumentGetError: If retrieval fails.
|
|
1066
|
+
|
|
1067
|
+
References:
|
|
1068
|
+
- `get-multiple-documents <https://docs.arangodb.com/stable/develop/http-api/documents/#get-multiple-documents>`__
|
|
1069
|
+
""" # noqa: E501
|
|
1070
|
+
params: Params = {"onlyget": True}
|
|
1071
|
+
if ignore_revs is not None:
|
|
1072
|
+
params["ignoreRevs"] = ignore_revs
|
|
1073
|
+
|
|
1074
|
+
headers: RequestHeaders = {}
|
|
1075
|
+
if allow_dirty_read is not None:
|
|
1076
|
+
if allow_dirty_read is True:
|
|
1077
|
+
headers["x-arango-allow-dirty-read"] = "true"
|
|
1078
|
+
else:
|
|
1079
|
+
headers["x-arango-allow-dirty-read"] = "false"
|
|
1080
|
+
|
|
1081
|
+
request = Request(
|
|
1082
|
+
method=Method.PUT,
|
|
1083
|
+
endpoint=f"/_api/document/{self.name}",
|
|
1084
|
+
params=params,
|
|
1085
|
+
headers=headers,
|
|
1086
|
+
data=self._doc_serializer.dumps(documents),
|
|
1087
|
+
)
|
|
1088
|
+
|
|
1089
|
+
def response_handler(resp: Response) -> V:
|
|
1090
|
+
if not resp.is_success:
|
|
1091
|
+
raise DocumentGetError(resp, request)
|
|
1092
|
+
return self._doc_deserializer.loads_many(resp.raw_body)
|
|
1093
|
+
|
|
1094
|
+
return await self._executor.execute(request, response_handler)
|
|
1095
|
+
|
|
1096
|
+
async def find(
|
|
1097
|
+
self,
|
|
1098
|
+
filters: Optional[Json] = None,
|
|
1099
|
+
skip: Optional[int] = None,
|
|
1100
|
+
limit: Optional[int | str] = None,
|
|
1101
|
+
allow_dirty_read: Optional[bool] = False,
|
|
1102
|
+
sort: Optional[Jsons] = None,
|
|
1103
|
+
) -> Result[Cursor]:
|
|
1104
|
+
"""Return all documents that match the given filters.
|
|
1105
|
+
|
|
1106
|
+
Args:
|
|
1107
|
+
filters (dict | None): Query filters.
|
|
1108
|
+
skip (int | None): Number of documents to skip.
|
|
1109
|
+
limit (int | str | None): Maximum number of documents to return.
|
|
1110
|
+
allow_dirty_read (bool): Allow reads from followers in a cluster.
|
|
1111
|
+
sort (list | None): Document sort parameters.
|
|
1112
|
+
|
|
1113
|
+
Returns:
|
|
1114
|
+
Cursor: Document cursor.
|
|
1115
|
+
|
|
1116
|
+
Raises:
|
|
1117
|
+
DocumentGetError: If retrieval fails.
|
|
1118
|
+
SortValidationError: If sort parameters are invalid.
|
|
1119
|
+
"""
|
|
1120
|
+
if not self._is_none_or_dict(filters):
|
|
1121
|
+
raise ValueError("filters parameter must be a dict")
|
|
1122
|
+
self._validate_sort_parameters(sort)
|
|
1123
|
+
if not self._is_none_or_int(skip):
|
|
1124
|
+
raise ValueError("skip parameter must be a non-negative int")
|
|
1125
|
+
if not (self._is_none_or_int(limit) or limit == "null"):
|
|
1126
|
+
raise ValueError("limit parameter must be a non-negative int")
|
|
1127
|
+
|
|
1128
|
+
skip = skip if skip is not None else 0
|
|
1129
|
+
limit = limit if limit is not None else "null"
|
|
1130
|
+
query = f"""
|
|
1131
|
+
FOR doc IN @@collection
|
|
1132
|
+
{self._build_filter_conditions(filters)}
|
|
1133
|
+
LIMIT {skip}, {limit}
|
|
1134
|
+
{self._build_sort_expression(sort)}
|
|
1135
|
+
RETURN doc
|
|
1136
|
+
"""
|
|
1137
|
+
bind_vars = {"@collection": self.name}
|
|
1138
|
+
data: Json = {"query": query, "bindVars": bind_vars, "count": True}
|
|
1139
|
+
headers: RequestHeaders = {}
|
|
1140
|
+
if allow_dirty_read is not None:
|
|
1141
|
+
if allow_dirty_read is True:
|
|
1142
|
+
headers["x-arango-allow-dirty-read"] = "true"
|
|
1143
|
+
else:
|
|
1144
|
+
headers["x-arango-allow-dirty-read"] = "false"
|
|
1145
|
+
|
|
1146
|
+
request = Request(
|
|
1147
|
+
method=Method.POST,
|
|
1148
|
+
endpoint="/_api/cursor",
|
|
1149
|
+
data=self.serializer.dumps(data),
|
|
1150
|
+
headers=headers,
|
|
1151
|
+
)
|
|
1152
|
+
|
|
1153
|
+
def response_handler(resp: Response) -> Cursor:
|
|
1154
|
+
if not resp.is_success:
|
|
1155
|
+
raise DocumentGetError(resp, request)
|
|
1156
|
+
if self._executor.context == "async":
|
|
1157
|
+
# We cannot have a cursor giving back async jobs
|
|
1158
|
+
executor: NonAsyncExecutor = DefaultApiExecutor(
|
|
1159
|
+
self._executor.connection
|
|
1160
|
+
)
|
|
1161
|
+
else:
|
|
1162
|
+
executor = cast(NonAsyncExecutor, self._executor)
|
|
1163
|
+
return Cursor(executor, self.deserializer.loads(resp.raw_body))
|
|
1164
|
+
|
|
1165
|
+
return await self._executor.execute(request, response_handler)
|
|
1166
|
+
|
|
1167
|
+
async def update_match(
|
|
1168
|
+
self,
|
|
1169
|
+
filters: Json,
|
|
1170
|
+
body: T,
|
|
1171
|
+
limit: Optional[int | str] = None,
|
|
1172
|
+
keep_none: Optional[bool] = None,
|
|
1173
|
+
wait_for_sync: Optional[bool] = None,
|
|
1174
|
+
merge_objects: Optional[bool] = None,
|
|
1175
|
+
) -> Result[int]:
|
|
1176
|
+
"""Update matching documents.
|
|
1177
|
+
|
|
1178
|
+
Args:
|
|
1179
|
+
filters (dict | None): Query filters.
|
|
1180
|
+
body (dict): Full or partial document body with the updates.
|
|
1181
|
+
limit (int | str | None): Maximum number of documents to update.
|
|
1182
|
+
keep_none (bool | None): If set to `True`, fields with value `None` are
|
|
1183
|
+
retained in the document. Otherwise, they are removed completely.
|
|
1184
|
+
wait_for_sync (bool | None): Wait until operation has been synced to disk.
|
|
1185
|
+
merge_objects (bool | None): If set to `True`, sub-dictionaries are merged
|
|
1186
|
+
instead of the new one overwriting the old one.
|
|
1187
|
+
|
|
1188
|
+
Returns:
|
|
1189
|
+
int: Number of documents that got updated.
|
|
1190
|
+
|
|
1191
|
+
Raises:
|
|
1192
|
+
DocumentUpdateError: If update fails.
|
|
1193
|
+
"""
|
|
1194
|
+
if not self._is_none_or_dict(filters):
|
|
1195
|
+
raise ValueError("filters parameter must be a dict")
|
|
1196
|
+
if not (self._is_none_or_int(limit) or limit == "null"):
|
|
1197
|
+
raise ValueError("limit parameter must be a non-negative int")
|
|
1198
|
+
|
|
1199
|
+
sync = f", waitForSync: {wait_for_sync}" if wait_for_sync is not None else ""
|
|
1200
|
+
query = f"""
|
|
1201
|
+
FOR doc IN @@collection
|
|
1202
|
+
{self._build_filter_conditions(filters)}
|
|
1203
|
+
{f"LIMIT {limit}" if limit is not None else ""}
|
|
1204
|
+
UPDATE doc WITH @body IN @@collection
|
|
1205
|
+
OPTIONS {{ keepNull: @keep_none, mergeObjects: @merge {sync} }}
|
|
1206
|
+
""" # noqa: E201 E202
|
|
1207
|
+
bind_vars = {
|
|
1208
|
+
"@collection": self.name,
|
|
1209
|
+
"body": body,
|
|
1210
|
+
"keep_none": keep_none,
|
|
1211
|
+
"merge": merge_objects,
|
|
1212
|
+
}
|
|
1213
|
+
data = {"query": query, "bindVars": bind_vars}
|
|
1214
|
+
|
|
1215
|
+
request = Request(
|
|
1216
|
+
method=Method.POST,
|
|
1217
|
+
endpoint="/_api/cursor",
|
|
1218
|
+
data=self.serializer.dumps(data),
|
|
1219
|
+
)
|
|
1220
|
+
|
|
1221
|
+
def response_handler(resp: Response) -> int:
|
|
1222
|
+
if resp.is_success:
|
|
1223
|
+
result = self.deserializer.loads(resp.raw_body)
|
|
1224
|
+
return cast(int, result["extra"]["stats"]["writesExecuted"])
|
|
1225
|
+
raise DocumentUpdateError(resp, request)
|
|
1226
|
+
|
|
1227
|
+
return await self._executor.execute(request, response_handler)
|
|
1228
|
+
|
|
1229
|
+
async def replace_match(
|
|
1230
|
+
self,
|
|
1231
|
+
filters: Json,
|
|
1232
|
+
body: T,
|
|
1233
|
+
limit: Optional[int | str] = None,
|
|
1234
|
+
wait_for_sync: Optional[bool] = None,
|
|
1235
|
+
) -> Result[int]:
|
|
1236
|
+
"""Replace matching documents.
|
|
1237
|
+
|
|
1238
|
+
Args:
|
|
1239
|
+
filters (dict | None): Query filters.
|
|
1240
|
+
body (dict): New document body.
|
|
1241
|
+
limit (int | str | None): Maximum number of documents to replace.
|
|
1242
|
+
wait_for_sync (bool | None): Wait until operation has been synced to disk.
|
|
1243
|
+
|
|
1244
|
+
Returns:
|
|
1245
|
+
int: Number of documents that got replaced.
|
|
1246
|
+
|
|
1247
|
+
Raises:
|
|
1248
|
+
DocumentReplaceError: If replace fails.
|
|
1249
|
+
"""
|
|
1250
|
+
if not self._is_none_or_dict(filters):
|
|
1251
|
+
raise ValueError("filters parameter must be a dict")
|
|
1252
|
+
if not (self._is_none_or_int(limit) or limit == "null"):
|
|
1253
|
+
raise ValueError("limit parameter must be a non-negative int")
|
|
1254
|
+
|
|
1255
|
+
sync = f"waitForSync: {wait_for_sync}" if wait_for_sync is not None else ""
|
|
1256
|
+
query = f"""
|
|
1257
|
+
FOR doc IN @@collection
|
|
1258
|
+
{self._build_filter_conditions(filters)}
|
|
1259
|
+
{f"LIMIT {limit}" if limit is not None else ""}
|
|
1260
|
+
REPLACE doc WITH @body IN @@collection
|
|
1261
|
+
{f"OPTIONS {{ {sync} }}" if sync else ""}
|
|
1262
|
+
""" # noqa: E201 E202
|
|
1263
|
+
bind_vars = {
|
|
1264
|
+
"@collection": self.name,
|
|
1265
|
+
"body": body,
|
|
1266
|
+
}
|
|
1267
|
+
data = {"query": query, "bindVars": bind_vars}
|
|
1268
|
+
|
|
1269
|
+
request = Request(
|
|
1270
|
+
method=Method.POST,
|
|
1271
|
+
endpoint="/_api/cursor",
|
|
1272
|
+
data=self.serializer.dumps(data),
|
|
1273
|
+
)
|
|
1274
|
+
|
|
1275
|
+
def response_handler(resp: Response) -> int:
|
|
1276
|
+
if resp.is_success:
|
|
1277
|
+
result = self.deserializer.loads(resp.raw_body)
|
|
1278
|
+
return cast(int, result["extra"]["stats"]["writesExecuted"])
|
|
1279
|
+
raise DocumentReplaceError(resp, request)
|
|
1280
|
+
|
|
1281
|
+
return await self._executor.execute(request, response_handler)
|
|
1282
|
+
|
|
1283
|
+
async def delete_match(
|
|
1284
|
+
self,
|
|
1285
|
+
filters: Json,
|
|
1286
|
+
limit: Optional[int | str] = None,
|
|
1287
|
+
wait_for_sync: Optional[bool] = None,
|
|
1288
|
+
) -> Result[int]:
|
|
1289
|
+
"""Delete matching documents.
|
|
1290
|
+
|
|
1291
|
+
Args:
|
|
1292
|
+
filters (dict | None): Query filters.
|
|
1293
|
+
limit (int | str | None): Maximum number of documents to delete.
|
|
1294
|
+
wait_for_sync (bool | None): Wait until operation has been synced to disk.
|
|
1295
|
+
|
|
1296
|
+
Returns:
|
|
1297
|
+
int: Number of documents that got deleted.
|
|
1298
|
+
|
|
1299
|
+
Raises:
|
|
1300
|
+
DocumentDeleteError: If delete fails.
|
|
1301
|
+
"""
|
|
1302
|
+
if not self._is_none_or_dict(filters):
|
|
1303
|
+
raise ValueError("filters parameter must be a dict")
|
|
1304
|
+
if not (self._is_none_or_int(limit) or limit == "null"):
|
|
1305
|
+
raise ValueError("limit parameter must be a non-negative int")
|
|
1306
|
+
|
|
1307
|
+
sync = f"waitForSync: {wait_for_sync}" if wait_for_sync is not None else ""
|
|
1308
|
+
query = f"""
|
|
1309
|
+
FOR doc IN @@collection
|
|
1310
|
+
{self._build_filter_conditions(filters)}
|
|
1311
|
+
{f"LIMIT {limit}" if limit is not None else ""}
|
|
1312
|
+
REMOVE doc IN @@collection
|
|
1313
|
+
{f"OPTIONS {{ {sync} }}" if sync else ""}
|
|
1314
|
+
""" # noqa: E201 E202
|
|
1315
|
+
bind_vars = {"@collection": self.name}
|
|
1316
|
+
data = {"query": query, "bindVars": bind_vars}
|
|
1317
|
+
|
|
1318
|
+
request = Request(
|
|
1319
|
+
method=Method.POST,
|
|
1320
|
+
endpoint="/_api/cursor",
|
|
1321
|
+
data=self.serializer.dumps(data),
|
|
1322
|
+
)
|
|
1323
|
+
|
|
1324
|
+
def response_handler(resp: Response) -> int:
|
|
1325
|
+
if resp.is_success:
|
|
1326
|
+
result = self.deserializer.loads(resp.raw_body)
|
|
1327
|
+
return cast(int, result["extra"]["stats"]["writesExecuted"])
|
|
1328
|
+
raise DocumentDeleteError(resp, request)
|
|
1329
|
+
|
|
1330
|
+
return await self._executor.execute(request, response_handler)
|
|
1331
|
+
|
|
1332
|
+
async def insert_many(
|
|
1333
|
+
self,
|
|
1334
|
+
documents: Sequence[T],
|
|
1335
|
+
wait_for_sync: Optional[bool] = None,
|
|
1336
|
+
return_new: Optional[bool] = None,
|
|
1337
|
+
return_old: Optional[bool] = None,
|
|
1338
|
+
silent: Optional[bool] = None,
|
|
1339
|
+
overwrite: Optional[bool] = None,
|
|
1340
|
+
overwrite_mode: Optional[str] = None,
|
|
1341
|
+
keep_null: Optional[bool] = None,
|
|
1342
|
+
merge_objects: Optional[bool] = None,
|
|
1343
|
+
refill_index_caches: Optional[bool] = None,
|
|
1344
|
+
version_attribute: Optional[str] = None,
|
|
1345
|
+
) -> Result[Jsons]:
|
|
1346
|
+
"""Insert multiple documents.
|
|
1347
|
+
|
|
1348
|
+
Note:
|
|
1349
|
+
If inserting a document fails, the exception is not raised but
|
|
1350
|
+
returned as an object in the "errors" list. It is up to you to
|
|
1351
|
+
inspect the list to determine which documents were inserted
|
|
1352
|
+
successfully (returns document metadata) and which were not
|
|
1353
|
+
(returns exception object).
|
|
1354
|
+
|
|
1355
|
+
Args:
|
|
1356
|
+
documents (list): Documents to insert. If an item contains the "_key" or
|
|
1357
|
+
"_id" field, the value is used as the key of the new document
|
|
1358
|
+
(otherwise it is auto-generated). Any "_rev" field is ignored.
|
|
1359
|
+
wait_for_sync (bool | None): Wait until documents have been synced to disk.
|
|
1360
|
+
return_new (bool | None): Additionally return the complete new document
|
|
1361
|
+
under the attribute `new` in the result.
|
|
1362
|
+
return_old (bool | None): Additionally return the complete old document
|
|
1363
|
+
under the attribute `old` in the result. Only available if the
|
|
1364
|
+
`overwrite` option is used.
|
|
1365
|
+
silent (bool | None): If set to `True`, an empty object is returned as
|
|
1366
|
+
response if all document operations succeed. No meta-data is returned
|
|
1367
|
+
for the created documents. If any of the operations raises an error,
|
|
1368
|
+
an array with the error object(s) is returned.
|
|
1369
|
+
overwrite (bool | None): If set to `True`, operation does not fail on
|
|
1370
|
+
duplicate key and existing document is overwritten (replace-insert).
|
|
1371
|
+
overwrite_mode (str | None): Overwrite mode. Supersedes **overwrite**
|
|
1372
|
+
option. May be one of "ignore", "replace", "update" or "conflict".
|
|
1373
|
+
keep_null (bool | None): If set to `True`, fields with value None are
|
|
1374
|
+
retained in the document. Otherwise, they are removed completely.
|
|
1375
|
+
Applies only when **overwrite_mode** is set to "update"
|
|
1376
|
+
(update-insert).
|
|
1377
|
+
merge_objects (bool | None): If set to `True`, sub-dictionaries are merged
|
|
1378
|
+
instead of the new one overwriting the old one. Applies only when
|
|
1379
|
+
**overwrite_mode** is set to "update" (update-insert).
|
|
1380
|
+
refill_index_caches (bool | None): Whether to add new entries to
|
|
1381
|
+
in-memory index caches if document operations affect the edge index
|
|
1382
|
+
or cache-enabled persistent indexes.
|
|
1383
|
+
version_attribute (str | None): Support for simple external versioning to
|
|
1384
|
+
document operations. Only applicable if **overwrite** is set to `True`
|
|
1385
|
+
or **overwrite_mode** is set to "update" or "replace".
|
|
1386
|
+
|
|
1387
|
+
Returns:
|
|
1388
|
+
list: Documents metadata (e.g. document id, key, revision) and
|
|
1389
|
+
errors or just errors if **silent** is set to `True`.
|
|
1390
|
+
|
|
1391
|
+
Raises:
|
|
1392
|
+
DocumentInsertError: If insertion fails.
|
|
1393
|
+
|
|
1394
|
+
References:
|
|
1395
|
+
- `create-multiple-documents <https://docs.arangodb.com/stable/develop/http-api/documents/#create-multiple-documents>`__
|
|
1396
|
+
""" # noqa: E501
|
|
1397
|
+
params: Params = {}
|
|
1398
|
+
if wait_for_sync is not None:
|
|
1399
|
+
params["waitForSync"] = wait_for_sync
|
|
1400
|
+
if return_new is not None:
|
|
1401
|
+
params["returnNew"] = return_new
|
|
1402
|
+
if return_old is not None:
|
|
1403
|
+
params["returnOld"] = return_old
|
|
1404
|
+
if silent is not None:
|
|
1405
|
+
params["silent"] = silent
|
|
1406
|
+
if overwrite is not None:
|
|
1407
|
+
params["overwrite"] = overwrite
|
|
1408
|
+
if overwrite_mode is not None:
|
|
1409
|
+
params["overwriteMode"] = overwrite_mode
|
|
1410
|
+
if keep_null is not None:
|
|
1411
|
+
params["keepNull"] = keep_null
|
|
1412
|
+
if merge_objects is not None:
|
|
1413
|
+
params["mergeObjects"] = merge_objects
|
|
1414
|
+
if refill_index_caches is not None:
|
|
1415
|
+
params["refillIndexCaches"] = refill_index_caches
|
|
1416
|
+
if version_attribute is not None:
|
|
1417
|
+
params["versionAttribute"] = version_attribute
|
|
1418
|
+
|
|
1419
|
+
request = Request(
|
|
1420
|
+
method=Method.POST,
|
|
1421
|
+
endpoint=f"/_api/document/{self.name}",
|
|
1422
|
+
data=self._doc_serializer.dumps(documents),
|
|
1423
|
+
params=params,
|
|
1424
|
+
)
|
|
1425
|
+
|
|
1426
|
+
def response_handler(
|
|
1427
|
+
resp: Response,
|
|
1428
|
+
) -> Jsons:
|
|
1429
|
+
if not resp.is_success:
|
|
1430
|
+
raise DocumentInsertError(resp, request)
|
|
1431
|
+
return self.deserializer.loads_many(resp.raw_body)
|
|
1432
|
+
|
|
1433
|
+
return await self._executor.execute(request, response_handler)
|
|
1434
|
+
|
|
1435
|
+
async def replace_many(
|
|
1436
|
+
self,
|
|
1437
|
+
documents: Sequence[T],
|
|
1438
|
+
wait_for_sync: Optional[bool] = None,
|
|
1439
|
+
ignore_revs: Optional[bool] = None,
|
|
1440
|
+
return_new: Optional[bool] = None,
|
|
1441
|
+
return_old: Optional[bool] = None,
|
|
1442
|
+
silent: Optional[bool] = None,
|
|
1443
|
+
refill_index_caches: Optional[bool] = None,
|
|
1444
|
+
version_attribute: Optional[str] = None,
|
|
1445
|
+
) -> Result[Jsons]:
|
|
1446
|
+
"""Insert multiple documents.
|
|
1447
|
+
|
|
1448
|
+
Note:
|
|
1449
|
+
If replacing a document fails, the exception is not raised but
|
|
1450
|
+
returned as an object in the "errors" list. It is up to you to
|
|
1451
|
+
inspect the list to determine which documents were replaced
|
|
1452
|
+
successfully (returns document metadata) and which were not
|
|
1453
|
+
(returns exception object).
|
|
1454
|
+
|
|
1455
|
+
Args:
|
|
1456
|
+
documents (list): New documents to replace the old ones. An item must
|
|
1457
|
+
contain the "_key" or "_id" field.
|
|
1458
|
+
wait_for_sync (bool | None): Wait until documents have been synced to disk.
|
|
1459
|
+
ignore_revs (bool | None): If this is set to `False`, then any `_rev`
|
|
1460
|
+
attribute given in a body document is taken as a precondition. The
|
|
1461
|
+
document is only replaced if the current revision is the one
|
|
1462
|
+
specified.
|
|
1463
|
+
return_new (bool | None): Additionally return the complete new document
|
|
1464
|
+
under the attribute `new` in the result.
|
|
1465
|
+
return_old (bool | None): Additionally return the complete old document
|
|
1466
|
+
under the attribute `old` in the result.
|
|
1467
|
+
silent (bool | None): If set to `True`, an empty object is returned as
|
|
1468
|
+
response if all document operations succeed. No meta-data is returned
|
|
1469
|
+
for the created documents. If any of the operations raises an error,
|
|
1470
|
+
an array with the error object(s) is returned.
|
|
1471
|
+
refill_index_caches (bool | None): Whether to add new entries to
|
|
1472
|
+
in-memory index caches if document operations affect the edge index
|
|
1473
|
+
or cache-enabled persistent indexes.
|
|
1474
|
+
version_attribute (str | None): Support for simple external versioning to
|
|
1475
|
+
document operations.
|
|
1476
|
+
|
|
1477
|
+
Returns:
|
|
1478
|
+
list: Documents metadata (e.g. document id, key, revision) and
|
|
1479
|
+
errors or just errors if **silent** is set to `True`.
|
|
1480
|
+
|
|
1481
|
+
Raises:
|
|
1482
|
+
DocumentReplaceError: If replacing fails.
|
|
1483
|
+
|
|
1484
|
+
References:
|
|
1485
|
+
- `replace-multiple-documents <https://docs.arangodb.com/stable/develop/http-api/documents/#replace-multiple-documents>`__
|
|
1486
|
+
""" # noqa: E501
|
|
1487
|
+
params: Params = {}
|
|
1488
|
+
if wait_for_sync is not None:
|
|
1489
|
+
params["waitForSync"] = wait_for_sync
|
|
1490
|
+
if ignore_revs is not None:
|
|
1491
|
+
params["ignoreRevs"] = ignore_revs
|
|
1492
|
+
if return_new is not None:
|
|
1493
|
+
params["returnNew"] = return_new
|
|
1494
|
+
if return_old is not None:
|
|
1495
|
+
params["returnOld"] = return_old
|
|
1496
|
+
if silent is not None:
|
|
1497
|
+
params["silent"] = silent
|
|
1498
|
+
if refill_index_caches is not None:
|
|
1499
|
+
params["refillIndexCaches"] = refill_index_caches
|
|
1500
|
+
if version_attribute is not None:
|
|
1501
|
+
params["versionAttribute"] = version_attribute
|
|
1502
|
+
|
|
1503
|
+
request = Request(
|
|
1504
|
+
method=Method.PUT,
|
|
1505
|
+
endpoint=f"/_api/document/{self.name}",
|
|
1506
|
+
data=self._doc_serializer.dumps(documents),
|
|
1507
|
+
params=params,
|
|
1508
|
+
)
|
|
1509
|
+
|
|
1510
|
+
def response_handler(
|
|
1511
|
+
resp: Response,
|
|
1512
|
+
) -> Jsons:
|
|
1513
|
+
if not resp.is_success:
|
|
1514
|
+
raise DocumentReplaceError(resp, request)
|
|
1515
|
+
return self.deserializer.loads_many(resp.raw_body)
|
|
1516
|
+
|
|
1517
|
+
return await self._executor.execute(request, response_handler)
|
|
1518
|
+
|
|
1519
|
+
async def update_many(
|
|
1520
|
+
self,
|
|
1521
|
+
documents: Sequence[T],
|
|
1522
|
+
wait_for_sync: Optional[bool] = None,
|
|
1523
|
+
ignore_revs: Optional[bool] = None,
|
|
1524
|
+
return_new: Optional[bool] = None,
|
|
1525
|
+
return_old: Optional[bool] = None,
|
|
1526
|
+
silent: Optional[bool] = None,
|
|
1527
|
+
keep_null: Optional[bool] = None,
|
|
1528
|
+
merge_objects: Optional[bool] = None,
|
|
1529
|
+
refill_index_caches: Optional[bool] = None,
|
|
1530
|
+
version_attribute: Optional[str] = None,
|
|
1531
|
+
) -> Result[Jsons]:
|
|
1532
|
+
"""Insert multiple documents.
|
|
1533
|
+
|
|
1534
|
+
Note:
|
|
1535
|
+
If updating a document fails, the exception is not raised but
|
|
1536
|
+
returned as an object in the "errors" list. It is up to you to
|
|
1537
|
+
inspect the list to determine which documents were updated
|
|
1538
|
+
successfully (returned as document metadata) and which were not
|
|
1539
|
+
(returned as exception object).
|
|
1540
|
+
|
|
1541
|
+
Args:
|
|
1542
|
+
documents (list): Documents to update. An item must contain the "_key" or
|
|
1543
|
+
"_id" field.
|
|
1544
|
+
wait_for_sync (bool | None): Wait until documents have been synced to disk.
|
|
1545
|
+
ignore_revs (bool | None): If this is set to `False`, then any `_rev`
|
|
1546
|
+
attribute given in a body document is taken as a precondition. The
|
|
1547
|
+
document is only updated if the current revision is the one
|
|
1548
|
+
specified.
|
|
1549
|
+
return_new (bool | None): Additionally return the complete new document
|
|
1550
|
+
under the attribute `new` in the result.
|
|
1551
|
+
return_old (bool | None): Additionally return the complete old document
|
|
1552
|
+
under the attribute `old` in the result.
|
|
1553
|
+
silent (bool | None): If set to `True`, an empty object is returned as
|
|
1554
|
+
response if all document operations succeed. No meta-data is returned
|
|
1555
|
+
for the created documents. If any of the operations raises an error,
|
|
1556
|
+
an array with the error object(s) is returned.
|
|
1557
|
+
keep_null (bool | None): If set to `True`, fields with value None are
|
|
1558
|
+
retained in the document. Otherwise, they are removed completely.
|
|
1559
|
+
Applies only when **overwrite_mode** is set to "update"
|
|
1560
|
+
(update-insert).
|
|
1561
|
+
merge_objects (bool | None): If set to `True`, sub-dictionaries are merged
|
|
1562
|
+
instead of the new one overwriting the old one. Applies only when
|
|
1563
|
+
**overwrite_mode** is set to "update" (update-insert).
|
|
1564
|
+
refill_index_caches (bool | None): Whether to add new entries to
|
|
1565
|
+
in-memory index caches if document operations affect the edge index
|
|
1566
|
+
or cache-enabled persistent indexes.
|
|
1567
|
+
version_attribute (str | None): Support for simple external versioning to
|
|
1568
|
+
document operations.
|
|
1569
|
+
|
|
1570
|
+
Returns:
|
|
1571
|
+
list: Documents metadata (e.g. document id, key, revision) and
|
|
1572
|
+
errors or just errors if **silent** is set to `True`.
|
|
1573
|
+
|
|
1574
|
+
Raises:
|
|
1575
|
+
DocumentUpdateError: If update fails.
|
|
1576
|
+
|
|
1577
|
+
References:
|
|
1578
|
+
- `update-multiple-documents <https://docs.arangodb.com/stable/develop/http-api/documents/#update-multiple-documents>`__
|
|
1579
|
+
""" # noqa: E501
|
|
1580
|
+
params: Params = {}
|
|
1581
|
+
if wait_for_sync is not None:
|
|
1582
|
+
params["waitForSync"] = wait_for_sync
|
|
1583
|
+
if ignore_revs is not None:
|
|
1584
|
+
params["ignoreRevs"] = ignore_revs
|
|
1585
|
+
if return_new is not None:
|
|
1586
|
+
params["returnNew"] = return_new
|
|
1587
|
+
if return_old is not None:
|
|
1588
|
+
params["returnOld"] = return_old
|
|
1589
|
+
if silent is not None:
|
|
1590
|
+
params["silent"] = silent
|
|
1591
|
+
if keep_null is not None:
|
|
1592
|
+
params["keepNull"] = keep_null
|
|
1593
|
+
if merge_objects is not None:
|
|
1594
|
+
params["mergeObjects"] = merge_objects
|
|
1595
|
+
if refill_index_caches is not None:
|
|
1596
|
+
params["refillIndexCaches"] = refill_index_caches
|
|
1597
|
+
if version_attribute is not None:
|
|
1598
|
+
params["versionAttribute"] = version_attribute
|
|
1599
|
+
|
|
1600
|
+
request = Request(
|
|
1601
|
+
method=Method.PATCH,
|
|
1602
|
+
endpoint=f"/_api/document/{self.name}",
|
|
1603
|
+
data=self._doc_serializer.dumps(documents),
|
|
1604
|
+
params=params,
|
|
1605
|
+
)
|
|
1606
|
+
|
|
1607
|
+
def response_handler(
|
|
1608
|
+
resp: Response,
|
|
1609
|
+
) -> Jsons:
|
|
1610
|
+
if not resp.is_success:
|
|
1611
|
+
raise DocumentUpdateError(resp, request)
|
|
1612
|
+
return self.deserializer.loads_many(resp.raw_body)
|
|
1613
|
+
|
|
1614
|
+
return await self._executor.execute(request, response_handler)
|
|
1615
|
+
|
|
1616
|
+
async def delete_many(
|
|
1617
|
+
self,
|
|
1618
|
+
documents: Sequence[T],
|
|
1619
|
+
wait_for_sync: Optional[bool] = None,
|
|
1620
|
+
ignore_revs: Optional[bool] = None,
|
|
1621
|
+
return_old: Optional[bool] = None,
|
|
1622
|
+
silent: Optional[bool] = None,
|
|
1623
|
+
refill_index_caches: Optional[bool] = None,
|
|
1624
|
+
) -> Result[Jsons]:
|
|
1625
|
+
"""Delete multiple documents.
|
|
1626
|
+
|
|
1627
|
+
Note:
|
|
1628
|
+
If deleting a document fails, the exception is not raised but
|
|
1629
|
+
returned as an object in the "errors" list. It is up to you to
|
|
1630
|
+
inspect the list to determine which documents were deleted
|
|
1631
|
+
successfully (returned as document metadata) and which were not
|
|
1632
|
+
(returned as exception object).
|
|
1633
|
+
|
|
1634
|
+
Args:
|
|
1635
|
+
documents (list): Documents to delete. An item must contain the "_key" or
|
|
1636
|
+
"_id" field.
|
|
1637
|
+
wait_for_sync (bool | None): Wait until documents have been synced to disk.
|
|
1638
|
+
ignore_revs (bool | None): If this is set to `False`, then any `_rev`
|
|
1639
|
+
attribute given in a body document is taken as a precondition. The
|
|
1640
|
+
document is only updated if the current revision is the one
|
|
1641
|
+
specified.
|
|
1642
|
+
return_old (bool | None): Additionally return the complete old document
|
|
1643
|
+
under the attribute `old` in the result.
|
|
1644
|
+
silent (bool | None): If set to `True`, an empty object is returned as
|
|
1645
|
+
response if all document operations succeed. No meta-data is returned
|
|
1646
|
+
for the created documents. If any of the operations raises an error,
|
|
1647
|
+
an array with the error object(s) is returned.
|
|
1648
|
+
refill_index_caches (bool | None): Whether to add new entries to
|
|
1649
|
+
in-memory index caches if document operations affect the edge index
|
|
1650
|
+
or cache-enabled persistent indexes.
|
|
1651
|
+
|
|
1652
|
+
Returns:
|
|
1653
|
+
list: Documents metadata (e.g. document id, key, revision) and
|
|
1654
|
+
errors or just errors if **silent** is set to `True`.
|
|
1655
|
+
|
|
1656
|
+
Raises:
|
|
1657
|
+
DocumentRemoveError: If removal fails.
|
|
1658
|
+
|
|
1659
|
+
References:
|
|
1660
|
+
- `remove-multiple-documents <https://docs.arangodb.com/stable/develop/http-api/documents/#remove-multiple-documents>`__
|
|
1661
|
+
""" # noqa: E501
|
|
1662
|
+
params: Params = {}
|
|
1663
|
+
if wait_for_sync is not None:
|
|
1664
|
+
params["waitForSync"] = wait_for_sync
|
|
1665
|
+
if ignore_revs is not None:
|
|
1666
|
+
params["ignoreRevs"] = ignore_revs
|
|
1667
|
+
if return_old is not None:
|
|
1668
|
+
params["returnOld"] = return_old
|
|
1669
|
+
if silent is not None:
|
|
1670
|
+
params["silent"] = silent
|
|
1671
|
+
if refill_index_caches is not None:
|
|
1672
|
+
params["refillIndexCaches"] = refill_index_caches
|
|
1673
|
+
|
|
1674
|
+
request = Request(
|
|
1675
|
+
method=Method.DELETE,
|
|
1676
|
+
endpoint=f"/_api/document/{self.name}",
|
|
1677
|
+
data=self._doc_serializer.dumps(documents),
|
|
1678
|
+
params=params,
|
|
1679
|
+
)
|
|
1680
|
+
|
|
1681
|
+
def response_handler(
|
|
1682
|
+
resp: Response,
|
|
1683
|
+
) -> Jsons:
|
|
1684
|
+
if not resp.is_success:
|
|
1685
|
+
raise DocumentDeleteError(resp, request)
|
|
1686
|
+
return self.deserializer.loads_many(resp.raw_body)
|
|
1687
|
+
|
|
1688
|
+
return await self._executor.execute(request, response_handler)
|