1__all__ = ["StandardCollection", "VertexCollection", "EdgeCollection"]
2
3from numbers import Number
4from typing import List, Optional, Sequence, Tuple, Union
5
6from arango.api import ApiGroup
7from arango.connection import Connection
8from arango.cursor import Cursor
9from arango.exceptions import (
10    ArangoServerError,
11    CollectionChecksumError,
12    CollectionConfigureError,
13    CollectionLoadError,
14    CollectionPropertiesError,
15    CollectionRecalculateCountError,
16    CollectionRenameError,
17    CollectionResponsibleShardError,
18    CollectionRevisionError,
19    CollectionStatisticsError,
20    CollectionTruncateError,
21    CollectionUnloadError,
22    DocumentCountError,
23    DocumentDeleteError,
24    DocumentGetError,
25    DocumentIDsError,
26    DocumentInError,
27    DocumentInsertError,
28    DocumentKeysError,
29    DocumentParseError,
30    DocumentReplaceError,
31    DocumentRevisionError,
32    DocumentUpdateError,
33    EdgeListError,
34    IndexCreateError,
35    IndexDeleteError,
36    IndexListError,
37    IndexLoadError,
38)
39from arango.executor import ApiExecutor
40from arango.formatter import format_collection, format_edge, format_index, format_vertex
41from arango.request import Request
42from arango.response import Response
43from arango.result import Result
44from arango.typings import Fields, Headers, Json, Params
45from arango.utils import get_doc_id, is_none_or_int, is_none_or_str
46
47
48class Collection(ApiGroup):
49    """Base class for collection API wrappers.
50
51    :param connection: HTTP connection.
52    :param executor: API executor.
53    :param name: Collection name.
54    """
55
56    types = {2: "document", 3: "edge"}
57
58    statuses = {
59        1: "new",
60        2: "unloaded",
61        3: "loaded",
62        4: "unloading",
63        5: "deleted",
64        6: "loading",
65    }
66
67    def __init__(
68        self, connection: Connection, executor: ApiExecutor, name: str
69    ) -> None:
70        super().__init__(connection, executor)
71        self._name = name
72        self._id_prefix = name + "/"
73
74    def __iter__(self) -> Result[Cursor]:
75        return self.all()
76
77    def __len__(self) -> Result[int]:
78        return self.count()
79
80    def __contains__(self, document: Union[str, Json]) -> Result[bool]:
81        return self.has(document, check_rev=False)
82
83    def _get_status_text(self, code: int) -> str:  # pragma: no cover
84        """Return the collection status text.
85
86        :param code: Collection status code.
87        :type code: int
88        :return: Collection status text or None if code is None.
89        :rtype: str
90        """
91        return None if code is None else self.statuses[code]
92
93    def _validate_id(self, doc_id: str) -> str:
94        """Check the collection name in the document ID.
95
96        :param doc_id: Document ID.
97        :type doc_id: str
98        :return: Verified document ID.
99        :rtype: str
100        :raise arango.exceptions.DocumentParseError: On bad collection name.
101        """
102        if not doc_id.startswith(self._id_prefix):
103            raise DocumentParseError(f'bad collection name in document ID "{doc_id}"')
104        return doc_id
105
106    def _extract_id(self, body: Json) -> str:
107        """Extract the document ID from document body.
108
109        :param body: Document body.
110        :type body: dict
111        :return: Document ID.
112        :rtype: str
113        :raise arango.exceptions.DocumentParseError: On missing ID and key.
114        """
115        try:
116            if "_id" in body:
117                return self._validate_id(body["_id"])
118            else:
119                key: str = body["_key"]
120                return self._id_prefix + key
121        except KeyError:
122            raise DocumentParseError('field "_key" or "_id" required')
123
124    def _prep_from_body(self, document: Json, check_rev: bool) -> Tuple[str, Headers]:
125        """Prepare document ID and request headers.
126
127        :param document: Document body.
128        :type document: dict
129        :param check_rev: Whether to check the revision.
130        :type check_rev: bool
131        :return: Document ID and request headers.
132        :rtype: (str, dict)
133        """
134        doc_id = self._extract_id(document)
135        if not check_rev or "_rev" not in document:
136            return doc_id, {}
137        return doc_id, {"If-Match": document["_rev"]}
138
139    def _prep_from_doc(
140        self, document: Union[str, Json], rev: Optional[str], check_rev: bool
141    ) -> Tuple[str, Union[str, Json], Json]:
142        """Prepare document ID, body and request headers.
143
144        :param document: Document ID, key or body.
145        :type document: str | dict
146        :param rev: Document revision or None.
147        :type rev: str | None
148        :param check_rev: Whether to check the revision.
149        :type check_rev: bool
150        :return: Document ID, body and request headers.
151        :rtype: (str, str | body, dict)
152        """
153        if isinstance(document, dict):
154            doc_id = self._extract_id(document)
155            rev = rev or document.get("_rev")
156
157            if not check_rev or rev is None:
158                return doc_id, doc_id, {}
159            else:
160                return doc_id, doc_id, {"If-Match": rev}
161        else:
162            if "/" in document:
163                doc_id = self._validate_id(document)
164            else:
165                doc_id = self._id_prefix + document
166
167            if not check_rev or rev is None:
168                return doc_id, doc_id, {}
169            else:
170                return doc_id, doc_id, {"If-Match": rev}
171
172    def _ensure_key_in_body(self, body: Json) -> Json:
173        """Return the document body with "_key" field populated.
174
175        :param body: Document body.
176        :type body: dict
177        :return: Document body with "_key" field.
178        :rtype: dict
179        :raise arango.exceptions.DocumentParseError: On missing ID and key.
180        """
181        if "_key" in body:
182            return body
183        elif "_id" in body:
184            doc_id = self._validate_id(body["_id"])
185            body = body.copy()
186            body["_key"] = doc_id[len(self._id_prefix) :]
187            return body
188        raise DocumentParseError('field "_key" or "_id" required')
189
190    def _ensure_key_from_id(self, body: Json) -> Json:
191        """Return the body with "_key" field if it has "_id" field.
192
193        :param body: Document body.
194        :type body: dict
195        :return: Document body with "_key" field if it has "_id" field.
196        :rtype: dict
197        """
198        if "_id" in body and "_key" not in body:
199            doc_id = self._validate_id(body["_id"])
200            body = body.copy()
201            body["_key"] = doc_id[len(self._id_prefix) :]
202        return body
203
204    @property
205    def name(self) -> str:
206        """Return collection name.
207
208        :return: Collection name.
209        :rtype: str
210        """
211        return self._name
212
213    def recalculate_count(self) -> Result[bool]:
214        """Recalculate the document count.
215
216        :return: True if recalculation was successful.
217        :rtype: bool
218        :raise arango.exceptions.CollectionRecalculateCountError: If operation fails.
219        """
220        request = Request(
221            method="put",
222            endpoint=f"/_api/collection/{self.name}/recalculateCount",
223        )
224
225        def response_handler(resp: Response) -> bool:
226            if resp.is_success:
227                return True
228            raise CollectionRecalculateCountError(resp, request)
229
230        return self._execute(request, response_handler)
231
232    def responsible_shard(self, document: Json) -> Result[str]:  # pragma: no cover
233        """Return the ID of the shard responsible for given **document**.
234
235        If the document does not exist, return the shard that would be
236        responsible.
237
238        :return: Shard ID
239        :rtype: str
240        """
241        request = Request(
242            method="put",
243            endpoint=f"/_api/collection/{self.name}/responsibleShard",
244            data=document,
245            read=self.name,
246        )
247
248        def response_handler(resp: Response) -> str:
249            if resp.is_success:
250                return str(resp.body["shardId"])
251            raise CollectionResponsibleShardError(resp, request)
252
253        return self._execute(request, response_handler)
254
255    def rename(self, new_name: str) -> Result[bool]:
256        """Rename the collection.
257
258        Renames may not be reflected immediately in async execution, batch
259        execution or transactions. It is recommended to initialize new API
260        wrappers after a rename.
261
262        :param new_name: New collection name.
263        :type new_name: str
264        :return: True if collection was renamed successfully.
265        :rtype: bool
266        :raise arango.exceptions.CollectionRenameError: If rename fails.
267        """
268        request = Request(
269            method="put",
270            endpoint=f"/_api/collection/{self.name}/rename",
271            data={"name": new_name},
272        )
273
274        def response_handler(resp: Response) -> bool:
275            if not resp.is_success:
276                raise CollectionRenameError(resp, request)
277            self._name = new_name
278            self._id_prefix = new_name + "/"
279            return True
280
281        return self._execute(request, response_handler)
282
283    def properties(self) -> Result[Json]:
284        """Return collection properties.
285
286        :return: Collection properties.
287        :rtype: dict
288        :raise arango.exceptions.CollectionPropertiesError: If retrieval fails.
289        """
290        request = Request(
291            method="get",
292            endpoint=f"/_api/collection/{self.name}/properties",
293            read=self.name,
294        )
295
296        def response_handler(resp: Response) -> Json:
297            if resp.is_success:
298                return format_collection(resp.body)
299            raise CollectionPropertiesError(resp, request)
300
301        return self._execute(request, response_handler)
302
303    def configure(
304        self, sync: Optional[bool] = None, schema: Optional[Json] = None
305    ) -> Result[Json]:
306        """Configure collection properties.
307
308        :param sync: Block until operations are synchronized to disk.
309        :type sync: bool | None
310        :param schema: document schema for validation of objects.
311        :type schema: dict
312        :return: New collection properties.
313        :rtype: dict
314        :raise arango.exceptions.CollectionConfigureError: If operation fails.
315        """
316        data: Json = {}
317        if sync is not None:
318            data["waitForSync"] = sync
319        if schema is not None:
320            data["schema"] = schema
321
322        request = Request(
323            method="put",
324            endpoint=f"/_api/collection/{self.name}/properties",
325            data=data,
326        )
327
328        def response_handler(resp: Response) -> Json:
329            if not resp.is_success:
330                raise CollectionConfigureError(resp, request)
331            return format_collection(resp.body)
332
333        return self._execute(request, response_handler)
334
335    def statistics(self) -> Result[Json]:
336        """Return collection statistics.
337
338        :return: Collection statistics.
339        :rtype: dict
340        :raise arango.exceptions.CollectionStatisticsError: If retrieval fails.
341        """
342        request = Request(
343            method="get",
344            endpoint=f"/_api/collection/{self.name}/figures",
345            read=self.name,
346        )
347
348        def response_handler(resp: Response) -> Json:
349            if not resp.is_success:
350                raise CollectionStatisticsError(resp, request)
351
352            stats: Json = resp.body.get("figures", resp.body)
353            if "documentReferences" in stats:  # pragma: no cover
354                stats["document_refs"] = stats.pop("documentReferences")
355            if "lastTick" in stats:  # pragma: no cover
356                stats["last_tick"] = stats.pop("lastTick")
357            if "waitingFor" in stats:  # pragma: no cover
358                stats["waiting_for"] = stats.pop("waitingFor")
359            if "documentsSize" in stats:  # pragma: no cover
360                stats["documents_size"] = stats.pop("documentsSize")
361            if "cacheInUse" in stats:  # pragma: no cover
362                stats["cache_in_use"] = stats.pop("cacheInUse")
363            if "cacheSize" in stats:  # pragma: no cover
364                stats["cache_size"] = stats.pop("cacheSize")
365            if "cacheUsage" in stats:  # pragma: no cover
366                stats["cache_usage"] = stats.pop("cacheUsage")
367            if "uncollectedLogfileEntries" in stats:  # pragma: no cover
368                stats["uncollected_logfile_entries"] = stats.pop(
369                    "uncollectedLogfileEntries"
370                )
371            return stats
372
373        return self._execute(request, response_handler)
374
375    def revision(self) -> Result[str]:
376        """Return collection revision.
377
378        :return: Collection revision.
379        :rtype: str
380        :raise arango.exceptions.CollectionRevisionError: If retrieval fails.
381        """
382        request = Request(
383            method="get",
384            endpoint=f"/_api/collection/{self.name}/revision",
385            read=self.name,
386        )
387
388        def response_handler(resp: Response) -> str:
389            if resp.is_success:
390                return str(resp.body["revision"])
391            raise CollectionRevisionError(resp, request)
392
393        return self._execute(request, response_handler)
394
395    def checksum(self, with_rev: bool = False, with_data: bool = False) -> Result[str]:
396        """Return collection checksum.
397
398        :param with_rev: Include document revisions in checksum calculation.
399        :type with_rev: bool
400        :param with_data: Include document data in checksum calculation.
401        :type with_data: bool
402        :return: Collection checksum.
403        :rtype: str
404        :raise arango.exceptions.CollectionChecksumError: If retrieval fails.
405        """
406        request = Request(
407            method="get",
408            endpoint=f"/_api/collection/{self.name}/checksum",
409            params={"withRevision": with_rev, "withData": with_data},
410        )
411
412        def response_handler(resp: Response) -> str:
413            if resp.is_success:
414                return str(resp.body["checksum"])
415            raise CollectionChecksumError(resp, request)
416
417        return self._execute(request, response_handler)
418
419    def load(self) -> Result[bool]:
420        """Load the collection into memory.
421
422        :return: True if collection was loaded successfully.
423        :rtype: bool
424        :raise arango.exceptions.CollectionLoadError: If operation fails.
425        """
426        request = Request(method="put", endpoint=f"/_api/collection/{self.name}/load")
427
428        def response_handler(resp: Response) -> bool:
429            if not resp.is_success:
430                raise CollectionLoadError(resp, request)
431            return True
432
433        return self._execute(request, response_handler)
434
435    def unload(self) -> Result[bool]:
436        """Unload the collection from memory.
437
438        :return: True if collection was unloaded successfully.
439        :rtype: bool
440        :raise arango.exceptions.CollectionUnloadError: If operation fails.
441        """
442        request = Request(method="put", endpoint=f"/_api/collection/{self.name}/unload")
443
444        def response_handler(resp: Response) -> bool:
445            if not resp.is_success:
446                raise CollectionUnloadError(resp, request)
447            return True
448
449        return self._execute(request, response_handler)
450
451    def truncate(self) -> Result[bool]:
452        """Delete all documents in the collection.
453
454        :return: True if collection was truncated successfully.
455        :rtype: bool
456        :raise arango.exceptions.CollectionTruncateError: If operation fails.
457        """
458        request = Request(
459            method="put", endpoint=f"/_api/collection/{self.name}/truncate"
460        )
461
462        def response_handler(resp: Response) -> bool:
463            if not resp.is_success:
464                raise CollectionTruncateError(resp, request)
465            return True
466
467        return self._execute(request, response_handler)
468
469    def count(self) -> Result[int]:
470        """Return the total document count.
471
472        :return: Total document count.
473        :rtype: int
474        :raise arango.exceptions.DocumentCountError: If retrieval fails.
475        """
476        request = Request(method="get", endpoint=f"/_api/collection/{self.name}/count")
477
478        def response_handler(resp: Response) -> int:
479            if resp.is_success:
480                result: int = resp.body["count"]
481                return result
482            raise DocumentCountError(resp, request)
483
484        return self._execute(request, response_handler)
485
486    def has(
487        self,
488        document: Union[str, Json],
489        rev: Optional[str] = None,
490        check_rev: bool = True,
491    ) -> Result[bool]:
492        """Check if a document exists in the collection.
493
494        :param document: Document ID, key or body. Document body must contain
495            the "_id" or "_key" field.
496        :type document: str | dict
497        :param rev: Expected document revision. Overrides value of "_rev" field
498            in **document** if present.
499        :type rev: str | None
500        :param check_rev: If set to True, revision of **document** (if given)
501            is compared against the revision of target document.
502        :type check_rev: bool
503        :return: True if document exists, False otherwise.
504        :rtype: bool
505        :raise arango.exceptions.DocumentInError: If check fails.
506        :raise arango.exceptions.DocumentRevisionError: If revisions mismatch.
507        """
508        handle, body, headers = self._prep_from_doc(document, rev, check_rev)
509
510        request = Request(
511            method="get",
512            endpoint=f"/_api/document/{handle}",
513            headers=headers,
514            read=self.name,
515        )
516
517        def response_handler(resp: Response) -> bool:
518            if resp.error_code == 1202:
519                return False
520            if resp.status_code == 412:
521                raise DocumentRevisionError(resp, request)
522            if not resp.is_success:
523                raise DocumentInError(resp, request)
524            return bool(resp.body)
525
526        return self._execute(request, response_handler)
527
528    def ids(self) -> Result[Cursor]:
529        """Return the IDs of all documents in the collection.
530
531        :return: Document ID cursor.
532        :rtype: arango.cursor.Cursor
533        :raise arango.exceptions.DocumentIDsError: If retrieval fails.
534        """
535        request = Request(
536            method="put",
537            endpoint="/_api/simple/all-keys",
538            data={"collection": self.name, "type": "id"},
539            read=self.name,
540        )
541
542        def response_handler(resp: Response) -> Cursor:
543            if not resp.is_success:
544                raise DocumentIDsError(resp, request)
545            return Cursor(self._conn, resp.body)
546
547        return self._execute(request, response_handler)
548
549    def keys(self) -> Result[Cursor]:
550        """Return the keys of all documents in the collection.
551
552        :return: Document key cursor.
553        :rtype: arango.cursor.Cursor
554        :raise arango.exceptions.DocumentKeysError: If retrieval fails.
555        """
556        request = Request(
557            method="put",
558            endpoint="/_api/simple/all-keys",
559            data={"collection": self.name, "type": "key"},
560            read=self.name,
561        )
562
563        def response_handler(resp: Response) -> Cursor:
564            if not resp.is_success:
565                raise DocumentKeysError(resp, request)
566            return Cursor(self._conn, resp.body)
567
568        return self._execute(request, response_handler)
569
570    def all(
571        self, skip: Optional[int] = None, limit: Optional[int] = None
572    ) -> Result[Cursor]:
573        """Return all documents in the collection.
574
575        :param skip: Number of documents to skip.
576        :type skip: int | None
577        :param limit: Max number of documents returned.
578        :type limit: int | None
579        :return: Document cursor.
580        :rtype: arango.cursor.Cursor
581        :raise arango.exceptions.DocumentGetError: If retrieval fails.
582        """
583        assert is_none_or_int(skip), "skip must be a non-negative int"
584        assert is_none_or_int(limit), "limit must be a non-negative int"
585
586        data: Json = {"collection": self.name}
587        if skip is not None:
588            data["skip"] = skip
589        if limit is not None:
590            data["limit"] = limit
591
592        request = Request(
593            method="put", endpoint="/_api/simple/all", data=data, read=self.name
594        )
595
596        def response_handler(resp: Response) -> Cursor:
597            if not resp.is_success:
598                raise DocumentGetError(resp, request)
599            return Cursor(self._conn, resp.body)
600
601        return self._execute(request, response_handler)
602
603    def export(
604        self,
605        limit: Optional[int] = None,
606        count: bool = False,
607        batch_size: Optional[int] = None,
608        flush: bool = False,
609        flush_wait: Optional[int] = None,
610        ttl: Optional[Number] = None,
611        filter_fields: Optional[Sequence[str]] = None,
612        filter_type: str = "include",
613    ) -> Result[Cursor]:
614        """Export all documents in the collection using a server cursor.
615
616        :param flush: If set to True, flush the write-ahead log prior to the
617            export. If set to False, documents in the write-ahead log during
618            the export are not included in the result.
619        :type flush: bool
620        :param flush_wait: Max wait time in seconds for write-ahead log flush.
621        :type flush_wait: int | None
622        :param count: Include the document count in the server cursor.
623        :type count: bool
624        :param batch_size: Max number of documents in the batch fetched by
625            the cursor in one round trip.
626        :type batch_size: int | None
627        :param limit: Max number of documents fetched by the cursor.
628        :type limit: int | None
629        :param ttl: Time-to-live for the cursor on the server.
630        :type ttl: int | float | None
631        :param filter_fields: Document fields to filter with.
632        :type filter_fields: [str] | None
633        :param filter_type: Allowed values are "include" or "exclude".
634        :type filter_type: str
635        :return: Document cursor.
636        :rtype: arango.cursor.Cursor
637        :raise arango.exceptions.DocumentGetError: If export fails.
638        """
639        data: Json = {"count": count, "flush": flush}
640        if flush_wait is not None:
641            data["flushWait"] = flush_wait
642        if batch_size is not None:
643            data["batchSize"] = batch_size
644        if limit is not None:
645            data["limit"] = limit
646        if ttl is not None:
647            data["ttl"] = ttl
648        if filter_fields is not None:
649            data["restrict"] = {"fields": filter_fields, "type": filter_type}
650        request = Request(
651            method="post",
652            endpoint="/_api/export",
653            params={"collection": self.name},
654            data=data,
655        )
656
657        def response_handler(resp: Response) -> Cursor:
658            if not resp.is_success:
659                raise DocumentGetError(resp, request)
660            return Cursor(self._conn, resp.body, "export")
661
662        return self._execute(request, response_handler)
663
664    def find(
665        self, filters: Json, skip: Optional[int] = None, limit: Optional[int] = None
666    ) -> Result[Cursor]:
667        """Return all documents that match the given filters.
668
669        :param filters: Document filters.
670        :type filters: dict
671        :param skip: Number of documents to skip.
672        :type skip: int | None
673        :param limit: Max number of documents returned.
674        :type limit: int | None
675        :return: Document cursor.
676        :rtype: arango.cursor.Cursor
677        :raise arango.exceptions.DocumentGetError: If retrieval fails.
678        """
679        assert isinstance(filters, dict), "filters must be a dict"
680        assert is_none_or_int(skip), "skip must be a non-negative int"
681        assert is_none_or_int(limit), "limit must be a non-negative int"
682
683        data: Json = {
684            "collection": self.name,
685            "example": filters,
686            "skip": skip,
687        }
688        if limit is not None:
689            data["limit"] = limit
690
691        request = Request(
692            method="put", endpoint="/_api/simple/by-example", data=data, read=self.name
693        )
694
695        def response_handler(resp: Response) -> Cursor:
696            if not resp.is_success:
697                raise DocumentGetError(resp, request)
698            return Cursor(self._conn, resp.body)
699
700        return self._execute(request, response_handler)
701
702    def find_near(
703        self, latitude: Number, longitude: Number, limit: Optional[int] = None
704    ) -> Result[Cursor]:
705        """Return documents near a given coordinate.
706
707        Documents returned are sorted according to distance, with the nearest
708        document being the first. If there are documents of equal distance,
709        they are randomly chosen from the set until the limit is reached. A geo
710        index must be defined in the collection to use this method.
711
712        :param latitude: Latitude.
713        :type latitude: int | float
714        :param longitude: Longitude.
715        :type longitude: int | float
716        :param limit: Max number of documents returned.
717        :type limit: int | None
718        :returns: Document cursor.
719        :rtype: arango.cursor.Cursor
720        :raises arango.exceptions.DocumentGetError: If retrieval fails.
721        """
722        assert isinstance(latitude, Number), "latitude must be a number"
723        assert isinstance(longitude, Number), "longitude must be a number"
724        assert is_none_or_int(limit), "limit must be a non-negative int"
725
726        query = """
727        FOR doc IN NEAR(@collection, @latitude, @longitude{})
728            RETURN doc
729        """.format(
730            "" if limit is None else ", @limit "
731        )
732
733        bind_vars = {
734            "collection": self._name,
735            "latitude": latitude,
736            "longitude": longitude,
737        }
738        if limit is not None:
739            bind_vars["limit"] = limit
740
741        request = Request(
742            method="post",
743            endpoint="/_api/cursor",
744            data={"query": query, "bindVars": bind_vars, "count": True},
745            read=self.name,
746        )
747
748        def response_handler(resp: Response) -> Cursor:
749            if not resp.is_success:
750                raise DocumentGetError(resp, request)
751            return Cursor(self._conn, resp.body)
752
753        return self._execute(request, response_handler)
754
755    def find_in_range(
756        self,
757        field: str,
758        lower: int,
759        upper: int,
760        skip: Optional[int] = None,
761        limit: Optional[int] = None,
762    ) -> Result[Cursor]:
763        """Return documents within a given range in a random order.
764
765        A skiplist index must be defined in the collection to use this method.
766
767        :param field: Document field name.
768        :type field: str
769        :param lower: Lower bound (inclusive).
770        :type lower: int
771        :param upper: Upper bound (exclusive).
772        :type upper: int
773        :param skip: Number of documents to skip.
774        :type skip: int | None
775        :param limit: Max number of documents returned.
776        :type limit: int | None
777        :returns: Document cursor.
778        :rtype: arango.cursor.Cursor
779        :raises arango.exceptions.DocumentGetError: If retrieval fails.
780        """
781        assert is_none_or_int(skip), "skip must be a non-negative int"
782        assert is_none_or_int(limit), "limit must be a non-negative int"
783
784        bind_vars = {
785            "@collection": self._name,
786            "field": field,
787            "lower": lower,
788            "upper": upper,
789            "skip": 0 if skip is None else skip,
790            "limit": 2147483647 if limit is None else limit,  # 2 ^ 31 - 1
791        }
792
793        query = """
794        FOR doc IN @@collection
795            FILTER doc.@field >= @lower && doc.@field < @upper
796            LIMIT @skip, @limit
797            RETURN doc
798        """
799
800        request = Request(
801            method="post",
802            endpoint="/_api/cursor",
803            data={"query": query, "bindVars": bind_vars, "count": True},
804            read=self.name,
805        )
806
807        def response_handler(resp: Response) -> Cursor:
808            if not resp.is_success:
809                raise DocumentGetError(resp, request)
810            return Cursor(self._conn, resp.body)
811
812        return self._execute(request, response_handler)
813
814    def find_in_radius(
815        self,
816        latitude: Number,
817        longitude: Number,
818        radius: Number,
819        distance_field: Optional[str] = None,
820    ) -> Result[Cursor]:
821        """Return documents within a given radius around a coordinate.
822
823        A geo index must be defined in the collection to use this method.
824
825        :param latitude: Latitude.
826        :type latitude: int | float
827        :param longitude: Longitude.
828        :type longitude: int | float
829        :param radius: Max radius.
830        :type radius: int | float
831        :param distance_field: Document field used to indicate the distance to
832            the given coordinate. This parameter is ignored in transactions.
833        :type distance_field: str
834        :returns: Document cursor.
835        :rtype: arango.cursor.Cursor
836        :raises arango.exceptions.DocumentGetError: If retrieval fails.
837        """
838        assert isinstance(latitude, Number), "latitude must be a number"
839        assert isinstance(longitude, Number), "longitude must be a number"
840        assert isinstance(radius, Number), "radius must be a number"
841        assert is_none_or_str(distance_field), "distance_field must be a str"
842
843        query = """
844        FOR doc IN WITHIN(@@collection, @latitude, @longitude, @radius{})
845            RETURN doc
846        """.format(
847            "" if distance_field is None else ", @distance"
848        )
849
850        bind_vars = {
851            "@collection": self._name,
852            "latitude": latitude,
853            "longitude": longitude,
854            "radius": radius,
855        }
856        if distance_field is not None:
857            bind_vars["distance"] = distance_field
858
859        request = Request(
860            method="post",
861            endpoint="/_api/cursor",
862            data={"query": query, "bindVars": bind_vars, "count": True},
863            read=self.name,
864        )
865
866        def response_handler(resp: Response) -> Cursor:
867            if not resp.is_success:
868                raise DocumentGetError(resp, request)
869            return Cursor(self._conn, resp.body)
870
871        return self._execute(request, response_handler)
872
873    def find_in_box(
874        self,
875        latitude1: Number,
876        longitude1: Number,
877        latitude2: Number,
878        longitude2: Number,
879        skip: Optional[int] = None,
880        limit: Optional[int] = None,
881        index: Optional[str] = None,
882    ) -> Result[Cursor]:
883        """Return all documents in an rectangular area.
884
885        :param latitude1: First latitude.
886        :type latitude1: int | float
887        :param longitude1: First longitude.
888        :type longitude1: int | float
889        :param latitude2: Second latitude.
890        :type latitude2: int | float
891        :param longitude2: Second longitude
892        :type longitude2: int | float
893        :param skip: Number of documents to skip.
894        :type skip: int | None
895        :param limit: Max number of documents returned.
896        :type limit: int | None
897        :param index: ID of the geo index to use (without the collection
898            prefix). This parameter is ignored in transactions.
899        :type index: str | None
900        :returns: Document cursor.
901        :rtype: arango.cursor.Cursor
902        :raises arango.exceptions.DocumentGetError: If retrieval fails.
903        """
904        assert isinstance(latitude1, Number), "latitude1 must be a number"
905        assert isinstance(longitude1, Number), "longitude1 must be a number"
906        assert isinstance(latitude2, Number), "latitude2 must be a number"
907        assert isinstance(longitude2, Number), "longitude2 must be a number"
908        assert is_none_or_int(skip), "skip must be a non-negative int"
909        assert is_none_or_int(limit), "limit must be a non-negative int"
910
911        data: Json = {
912            "collection": self._name,
913            "latitude1": latitude1,
914            "longitude1": longitude1,
915            "latitude2": latitude2,
916            "longitude2": longitude2,
917        }
918        if skip is not None:
919            data["skip"] = skip
920        if limit is not None:
921            data["limit"] = limit
922        if index is not None:
923            data["geo"] = self._name + "/" + index
924
925        request = Request(
926            method="put",
927            endpoint="/_api/simple/within-rectangle",
928            data=data,
929            read=self.name,
930        )
931
932        def response_handler(resp: Response) -> Cursor:
933            if not resp.is_success:
934                raise DocumentGetError(resp, request)
935            return Cursor(self._conn, resp.body)
936
937        return self._execute(request, response_handler)
938
939    def find_by_text(
940        self, field: str, query: str, limit: Optional[int] = None
941    ) -> Result[Cursor]:
942        """Return documents that match the given fulltext query.
943
944        :param field: Document field with fulltext index.
945        :type field: str
946        :param query: Fulltext query.
947        :type query: str
948        :param limit: Max number of documents returned.
949        :type limit: int | None
950        :returns: Document cursor.
951        :rtype: arango.cursor.Cursor
952        :raises arango.exceptions.DocumentGetError: If retrieval fails.
953        """
954        assert is_none_or_int(limit), "limit must be a non-negative int"
955
956        bind_vars: Json = {
957            "collection": self._name,
958            "field": field,
959            "query": query,
960        }
961        if limit is not None:
962            bind_vars["limit"] = limit
963
964        aql = """
965        FOR doc IN FULLTEXT(@collection, @field, @query{})
966            RETURN doc
967        """.format(
968            "" if limit is None else ", @limit"
969        )
970
971        request = Request(
972            method="post",
973            endpoint="/_api/cursor",
974            data={"query": aql, "bindVars": bind_vars, "count": True},
975            read=self.name,
976        )
977
978        def response_handler(resp: Response) -> Cursor:
979            if not resp.is_success:
980                raise DocumentGetError(resp, request)
981            return Cursor(self._conn, resp.body)
982
983        return self._execute(request, response_handler)
984
985    def get_many(self, documents: Sequence[Union[str, Json]]) -> Result[List[Json]]:
986        """Return multiple documents ignoring any missing ones.
987
988        :param documents: List of document keys, IDs or bodies. Document bodies
989            must contain the "_id" or "_key" fields.
990        :type documents: [str | dict]
991        :return: Documents. Missing ones are not included.
992        :rtype: [dict]
993        :raise arango.exceptions.DocumentGetError: If retrieval fails.
994        """
995        handles = [self._extract_id(d) if isinstance(d, dict) else d for d in documents]
996
997        request = Request(
998            method="put",
999            endpoint="/_api/simple/lookup-by-keys",
1000            data={"collection": self.name, "keys": handles},
1001            read=self.name,
1002        )
1003
1004        def response_handler(resp: Response) -> List[Json]:
1005            if not resp.is_success:
1006                raise DocumentGetError(resp, request)
1007            docs = resp.body["documents"]
1008            return [doc for doc in docs if "_id" in doc]
1009
1010        return self._execute(request, response_handler)
1011
1012    def random(self) -> Result[Json]:
1013        """Return a random document from the collection.
1014
1015        :return: A random document.
1016        :rtype: dict
1017        :raise arango.exceptions.DocumentGetError: If retrieval fails.
1018        """
1019        request = Request(
1020            method="put",
1021            endpoint="/_api/simple/any",
1022            data={"collection": self.name},
1023            read=self.name,
1024        )
1025
1026        def response_handler(resp: Response) -> Json:
1027            if resp.is_success:
1028                result: Json = resp.body["document"]
1029                return result
1030            raise DocumentGetError(resp, request)
1031
1032        return self._execute(request, response_handler)
1033
1034    ####################
1035    # Index Management #
1036    ####################
1037
1038    def indexes(self) -> Result[List[Json]]:
1039        """Return the collection indexes.
1040
1041        :return: Collection indexes.
1042        :rtype: [dict]
1043        :raise arango.exceptions.IndexListError: If retrieval fails.
1044        """
1045        request = Request(
1046            method="get",
1047            endpoint="/_api/index",
1048            params={"collection": self.name},
1049        )
1050
1051        def response_handler(resp: Response) -> List[Json]:
1052            if not resp.is_success:
1053                raise IndexListError(resp, request)
1054            result = resp.body["indexes"]
1055            return [format_index(index) for index in result]
1056
1057        return self._execute(request, response_handler)
1058
1059    def _add_index(self, data: Json) -> Result[Json]:
1060        """Helper method for creating a new index.
1061
1062        :param data: Index data.
1063        :type data: dict
1064        :return: New index details.
1065        :rtype: dict
1066        :raise arango.exceptions.IndexCreateError: If create fails.
1067        """
1068        request = Request(
1069            method="post",
1070            endpoint="/_api/index",
1071            data=data,
1072            params={"collection": self.name},
1073        )
1074
1075        def response_handler(resp: Response) -> Json:
1076            if not resp.is_success:
1077                raise IndexCreateError(resp, request)
1078            return format_index(resp.body)
1079
1080        return self._execute(request, response_handler)
1081
1082    def add_hash_index(
1083        self,
1084        fields: Sequence[str],
1085        unique: Optional[bool] = None,
1086        sparse: Optional[bool] = None,
1087        deduplicate: Optional[bool] = None,
1088        name: Optional[str] = None,
1089        in_background: Optional[bool] = None,
1090    ) -> Result[Json]:
1091        """Create a new hash index.
1092
1093        :param fields: Document fields to index.
1094        :type fields: [str]
1095        :param unique: Whether the index is unique.
1096        :type unique: bool | None
1097        :param sparse: If set to True, documents with None in the field
1098            are also indexed. If set to False, they are skipped.
1099        :type sparse: bool | None
1100        :param deduplicate: If set to True, inserting duplicate index values
1101            from the same document triggers unique constraint errors.
1102        :type deduplicate: bool | None
1103        :param name: Optional name for the index.
1104        :type name: str | None
1105        :param in_background: Do not hold the collection lock.
1106        :type in_background: bool | None
1107        :return: New index details.
1108        :rtype: dict
1109        :raise arango.exceptions.IndexCreateError: If create fails.
1110        """
1111        data: Json = {"type": "hash", "fields": fields}
1112
1113        if unique is not None:
1114            data["unique"] = unique
1115        if sparse is not None:
1116            data["sparse"] = sparse
1117        if deduplicate is not None:
1118            data["deduplicate"] = deduplicate
1119        if name is not None:
1120            data["name"] = name
1121        if in_background is not None:
1122            data["inBackground"] = in_background
1123
1124        return self._add_index(data)
1125
1126    def add_skiplist_index(
1127        self,
1128        fields: Sequence[str],
1129        unique: Optional[bool] = None,
1130        sparse: Optional[bool] = None,
1131        deduplicate: Optional[bool] = None,
1132        name: Optional[str] = None,
1133        in_background: Optional[bool] = None,
1134    ) -> Result[Json]:
1135        """Create a new skiplist index.
1136
1137        :param fields: Document fields to index.
1138        :type fields: [str]
1139        :param unique: Whether the index is unique.
1140        :type unique: bool | None
1141        :param sparse: If set to True, documents with None in the field
1142            are also indexed. If set to False, they are skipped.
1143        :type sparse: bool | None
1144        :param deduplicate: If set to True, inserting duplicate index values
1145            from the same document triggers unique constraint errors.
1146        :type deduplicate: bool | None
1147        :param name: Optional name for the index.
1148        :type name: str | None
1149        :param in_background: Do not hold the collection lock.
1150        :type in_background: bool | None
1151        :return: New index details.
1152        :rtype: dict
1153        :raise arango.exceptions.IndexCreateError: If create fails.
1154        """
1155        data: Json = {"type": "skiplist", "fields": fields}
1156
1157        if unique is not None:
1158            data["unique"] = unique
1159        if sparse is not None:
1160            data["sparse"] = sparse
1161        if deduplicate is not None:
1162            data["deduplicate"] = deduplicate
1163        if name is not None:
1164            data["name"] = name
1165        if in_background is not None:
1166            data["inBackground"] = in_background
1167
1168        return self._add_index(data)
1169
1170    def add_geo_index(
1171        self,
1172        fields: Fields,
1173        ordered: Optional[bool] = None,
1174        name: Optional[str] = None,
1175        in_background: Optional[bool] = None,
1176    ) -> Result[Json]:
1177        """Create a new geo-spatial index.
1178
1179        :param fields: A single document field or a list of document fields. If
1180            a single field is given, the field must have values that are lists
1181            with at least two floats. Documents with missing fields or invalid
1182            values are excluded.
1183        :type fields: str | [str]
1184        :param ordered: Whether the order is longitude, then latitude.
1185        :type ordered: bool | None
1186        :param name: Optional name for the index.
1187        :type name: str | None
1188        :param in_background: Do not hold the collection lock.
1189        :type in_background: bool | None
1190        :return: New index details.
1191        :rtype: dict
1192        :raise arango.exceptions.IndexCreateError: If create fails.
1193        """
1194        data: Json = {"type": "geo", "fields": fields}
1195
1196        if ordered is not None:
1197            data["geoJson"] = ordered
1198        if name is not None:
1199            data["name"] = name
1200        if in_background is not None:
1201            data["inBackground"] = in_background
1202
1203        return self._add_index(data)
1204
1205    def add_fulltext_index(
1206        self,
1207        fields: Sequence[str],
1208        min_length: Optional[int] = None,
1209        name: Optional[str] = None,
1210        in_background: Optional[bool] = None,
1211    ) -> Result[Json]:
1212        """Create a new fulltext index.
1213
1214        :param fields: Document fields to index.
1215        :type fields: [str]
1216        :param min_length: Minimum number of characters to index.
1217        :type min_length: int | None
1218        :param name: Optional name for the index.
1219        :type name: str | None
1220        :param in_background: Do not hold the collection lock.
1221        :type in_background: bool | None
1222        :return: New index details.
1223        :rtype: dict
1224        :raise arango.exceptions.IndexCreateError: If create fails.
1225        """
1226        data: Json = {"type": "fulltext", "fields": fields}
1227
1228        if min_length is not None:
1229            data["minLength"] = min_length
1230        if name is not None:
1231            data["name"] = name
1232        if in_background is not None:
1233            data["inBackground"] = in_background
1234
1235        return self._add_index(data)
1236
1237    def add_persistent_index(
1238        self,
1239        fields: Sequence[str],
1240        unique: Optional[bool] = None,
1241        sparse: Optional[bool] = None,
1242        name: Optional[str] = None,
1243        in_background: Optional[bool] = None,
1244    ) -> Result[Json]:
1245        """Create a new persistent index.
1246
1247        Unique persistent indexes on non-sharded keys are not supported in a
1248        cluster.
1249
1250        :param fields: Document fields to index.
1251        :type fields: [str]
1252        :param unique: Whether the index is unique.
1253        :type unique: bool | None
1254        :param sparse: Exclude documents that do not contain at least one of
1255            the indexed fields, or documents that have a value of None in any
1256            of the indexed fields.
1257        :type sparse: bool | None
1258        :param name: Optional name for the index.
1259        :type name: str | None
1260        :param in_background: Do not hold the collection lock.
1261        :type in_background: bool | None
1262        :return: New index details.
1263        :rtype: dict
1264        :raise arango.exceptions.IndexCreateError: If create fails.
1265        """
1266        data: Json = {"type": "persistent", "fields": fields}
1267
1268        if unique is not None:
1269            data["unique"] = unique
1270        if sparse is not None:
1271            data["sparse"] = sparse
1272        if name is not None:
1273            data["name"] = name
1274        if in_background is not None:
1275            data["inBackground"] = in_background
1276
1277        return self._add_index(data)
1278
1279    def add_ttl_index(
1280        self,
1281        fields: Sequence[str],
1282        expiry_time: int,
1283        name: Optional[str] = None,
1284        in_background: Optional[bool] = None,
1285    ) -> Result[Json]:
1286        """Create a new TTL (time-to-live) index.
1287
1288        :param fields: Document field to index.
1289        :type fields: [str]
1290        :param expiry_time: Time of expiry in seconds after document creation.
1291        :type expiry_time: int
1292        :param name: Optional name for the index.
1293        :type name: str | None
1294        :param in_background: Do not hold the collection lock.
1295        :type in_background: bool | None
1296        :return: New index details.
1297        :rtype: dict
1298        :raise arango.exceptions.IndexCreateError: If create fails.
1299        """
1300        data: Json = {"type": "ttl", "fields": fields, "expireAfter": expiry_time}
1301
1302        if name is not None:
1303            data["name"] = name
1304        if in_background is not None:
1305            data["inBackground"] = in_background
1306
1307        return self._add_index(data)
1308
1309    def delete_index(self, index_id: str, ignore_missing: bool = False) -> Result[bool]:
1310        """Delete an index.
1311
1312        :param index_id: Index ID.
1313        :type index_id: str
1314        :param ignore_missing: Do not raise an exception on missing index.
1315        :type ignore_missing: bool
1316        :return: True if index was deleted successfully, False if index was
1317            not found and **ignore_missing** was set to True.
1318        :rtype: bool
1319        :raise arango.exceptions.IndexDeleteError: If delete fails.
1320        """
1321        request = Request(
1322            method="delete", endpoint=f"/_api/index/{self.name}/{index_id}"
1323        )
1324
1325        def response_handler(resp: Response) -> bool:
1326            if resp.error_code == 1212 and ignore_missing:
1327                return False
1328            if not resp.is_success:
1329                raise IndexDeleteError(resp, request)
1330            return True
1331
1332        return self._execute(request, response_handler)
1333
1334    def load_indexes(self) -> Result[bool]:
1335        """Cache all indexes in the collection into memory.
1336
1337        :return: True if index was loaded successfully.
1338        :rtype: bool
1339        :raise arango.exceptions.IndexLoadError: If operation fails.
1340        """
1341        request = Request(
1342            method="put",
1343            endpoint=f"/_api/collection/{self.name}/loadIndexesIntoMemory",
1344        )
1345
1346        def response_handler(resp: Response) -> bool:
1347            if not resp.is_success:
1348                raise IndexLoadError(resp, request)
1349            return True
1350
1351        return self._execute(request, response_handler)
1352
1353    def insert_many(
1354        self,
1355        documents: Sequence[Json],
1356        return_new: bool = False,
1357        sync: Optional[bool] = None,
1358        silent: bool = False,
1359        overwrite: bool = False,
1360        return_old: bool = False,
1361    ) -> Result[Union[bool, List[Union[Json, ArangoServerError]]]]:
1362        """Insert multiple documents.
1363
1364        .. note::
1365
1366            If inserting a document fails, the exception is not raised but
1367            returned as an object in the result list. It is up to you to
1368            inspect the list to determine which documents were inserted
1369            successfully (returns document metadata) and which were not
1370            (returns exception object).
1371
1372        .. note::
1373
1374            In edge/vertex collections, this method does NOT provide the
1375            transactional guarantees and validations that single insert
1376            operation does for graphs. If these properties are required, see
1377            :func:`arango.database.StandardDatabase.begin_batch_execution`
1378            for an alternative approach.
1379
1380        :param documents: List of new documents to insert. If they contain the
1381            "_key" or "_id" fields, the values are used as the keys of the new
1382            documents (auto-generated otherwise). Any "_rev" field is ignored.
1383        :type documents: [dict]
1384        :param return_new: Include bodies of the new documents in the returned
1385            metadata. Ignored if parameter **silent** is set to True
1386        :type return_new: bool
1387        :param sync: Block until operation is synchronized to disk.
1388        :type sync: bool | None
1389        :param silent: If set to True, no document metadata is returned. This
1390            can be used to save resources.
1391        :type silent: bool
1392        :param overwrite: If set to True, operation does not fail on duplicate
1393            keys and the existing documents are replaced.
1394        :type overwrite: bool
1395        :param return_old: Include body of the old documents if replaced.
1396            Applies only when value of **overwrite** is set to True.
1397        :type return_old: bool
1398        :return: List of document metadata (e.g. document keys, revisions) and
1399            any exception, or True if parameter **silent** was set to True.
1400        :rtype: [dict | ArangoServerError] | bool
1401        :raise arango.exceptions.DocumentInsertError: If insert fails.
1402        """
1403        documents = [self._ensure_key_from_id(doc) for doc in documents]
1404
1405        params: Params = {
1406            "returnNew": return_new,
1407            "silent": silent,
1408            "overwrite": overwrite,
1409            "returnOld": return_old,
1410        }
1411        if sync is not None:
1412            params["waitForSync"] = sync
1413
1414        request = Request(
1415            method="post",
1416            endpoint=f"/_api/document/{self.name}",
1417            data=documents,
1418            params=params,
1419        )
1420
1421        def response_handler(
1422            resp: Response,
1423        ) -> Union[bool, List[Union[Json, ArangoServerError]]]:
1424            if not resp.is_success:
1425                raise DocumentInsertError(resp, request)
1426            if silent is True:
1427                return True
1428
1429            results: List[Union[Json, ArangoServerError]] = []
1430            for body in resp.body:
1431                if "_id" in body:
1432                    if "_oldRev" in body:
1433                        body["_old_rev"] = body.pop("_oldRev")
1434                    results.append(body)
1435                else:
1436                    sub_resp = self._conn.prep_bulk_err_response(resp, body)
1437                    results.append(DocumentInsertError(sub_resp, request))
1438
1439            return results
1440
1441        return self._execute(request, response_handler)
1442
1443    def update_many(
1444        self,
1445        documents: Sequence[Json],
1446        check_rev: bool = True,
1447        merge: bool = True,
1448        keep_none: bool = True,
1449        return_new: bool = False,
1450        return_old: bool = False,
1451        sync: Optional[bool] = None,
1452        silent: bool = False,
1453    ) -> Result[Union[bool, List[Union[Json, ArangoServerError]]]]:
1454        """Update multiple documents.
1455
1456        .. note::
1457
1458            If updating a document fails, the exception is not raised but
1459            returned as an object in the result list. It is up to you to
1460            inspect the list to determine which documents were updated
1461            successfully (returns document metadata) and which were not
1462            (returns exception object).
1463
1464        .. note::
1465
1466            In edge/vertex collections, this method does NOT provide the
1467            transactional guarantees and validations that single update
1468            operation does for graphs. If these properties are required, see
1469            :func:`arango.database.StandardDatabase.begin_batch_execution`
1470            for an alternative approach.
1471
1472        :param documents: Partial or full documents with the updated values.
1473            They must contain the "_id" or "_key" fields.
1474        :type documents: [dict]
1475        :param check_rev: If set to True, revisions of **documents** (if given)
1476            are compared against the revisions of target documents.
1477        :type check_rev: bool
1478        :param merge: If set to True, sub-dictionaries are merged instead of
1479            the new ones overwriting the old ones.
1480        :type merge: bool | None
1481        :param keep_none: If set to True, fields with value None are retained
1482            in the document. Otherwise, they are removed completely.
1483        :type keep_none: bool | None
1484        :param return_new: Include body of the new document in the returned
1485            metadata. Ignored if parameter **silent** is set to True.
1486        :type return_new: bool
1487        :param return_old: Include body of the old document in the returned
1488            metadata. Ignored if parameter **silent** is set to True.
1489        :type return_old: bool
1490        :param sync: Block until operation is synchronized to disk.
1491        :type sync: bool | None
1492        :param silent: If set to True, no document metadata is returned. This
1493            can be used to save resources.
1494        :type silent: bool
1495        :return: List of document metadata (e.g. document keys, revisions) and
1496            any exceptions, or True if parameter **silent** was set to True.
1497        :rtype: [dict | ArangoError] | bool
1498        :raise arango.exceptions.DocumentUpdateError: If update fails.
1499        """
1500        params: Params = {
1501            "keepNull": keep_none,
1502            "mergeObjects": merge,
1503            "returnNew": return_new,
1504            "returnOld": return_old,
1505            "ignoreRevs": not check_rev,
1506            "overwrite": not check_rev,
1507            "silent": silent,
1508        }
1509        if sync is not None:
1510            params["waitForSync"] = sync
1511
1512        documents = [self._ensure_key_in_body(doc) for doc in documents]
1513
1514        request = Request(
1515            method="patch",
1516            endpoint=f"/_api/document/{self.name}",
1517            data=documents,
1518            params=params,
1519            write=self.name,
1520        )
1521
1522        def response_handler(
1523            resp: Response,
1524        ) -> Union[bool, List[Union[Json, ArangoServerError]]]:
1525            if not resp.is_success:
1526                raise DocumentUpdateError(resp, request)
1527            if silent is True:
1528                return True
1529
1530            results = []
1531            for body in resp.body:
1532                if "_id" in body:
1533                    body["_old_rev"] = body.pop("_oldRev")
1534                    results.append(body)
1535                else:
1536                    sub_resp = self._conn.prep_bulk_err_response(resp, body)
1537
1538                    error: ArangoServerError
1539                    if sub_resp.error_code == 1200:
1540                        error = DocumentRevisionError(sub_resp, request)
1541                    else:  # pragma: no cover
1542                        error = DocumentUpdateError(sub_resp, request)
1543
1544                    results.append(error)
1545
1546            return results
1547
1548        return self._execute(request, response_handler)
1549
1550    def update_match(
1551        self,
1552        filters: Json,
1553        body: Json,
1554        limit: Optional[int] = None,
1555        keep_none: bool = True,
1556        sync: Optional[bool] = None,
1557        merge: bool = True,
1558    ) -> Result[int]:
1559        """Update matching documents.
1560
1561        .. note::
1562
1563            In edge/vertex collections, this method does NOT provide the
1564            transactional guarantees and validations that single update
1565            operation does for graphs. If these properties are required, see
1566            :func:`arango.database.StandardDatabase.begin_batch_execution`
1567            for an alternative approach.
1568
1569        :param filters: Document filters.
1570        :type filters: dict
1571        :param body: Full or partial document body with the updates.
1572        :type body: dict
1573        :param limit: Max number of documents to update. If the limit is lower
1574            than the number of matched documents, random documents are
1575            chosen. This parameter is not supported on sharded collections.
1576        :type limit: int | None
1577        :param keep_none: If set to True, fields with value None are retained
1578            in the document. Otherwise, they are removed completely.
1579        :type keep_none: bool | None
1580        :param sync: Block until operation is synchronized to disk.
1581        :type sync: bool | None
1582        :param merge: If set to True, sub-dictionaries are merged instead of
1583            the new ones overwriting the old ones.
1584        :type merge: bool | None
1585        :return: Number of documents updated.
1586        :rtype: int
1587        :raise arango.exceptions.DocumentUpdateError: If update fails.
1588        """
1589        data: Json = {
1590            "collection": self.name,
1591            "example": filters,
1592            "newValue": body,
1593            "keepNull": keep_none,
1594            "mergeObjects": merge,
1595        }
1596        if limit is not None:
1597            data["limit"] = limit
1598        if sync is not None:
1599            data["waitForSync"] = sync
1600
1601        request = Request(
1602            method="put",
1603            endpoint="/_api/simple/update-by-example",
1604            data=data,
1605            write=self.name,
1606        )
1607
1608        def response_handler(resp: Response) -> int:
1609            if resp.is_success:
1610                result: int = resp.body["updated"]
1611                return result
1612            raise DocumentUpdateError(resp, request)
1613
1614        return self._execute(request, response_handler)
1615
1616    def replace_many(
1617        self,
1618        documents: Sequence[Json],
1619        check_rev: bool = True,
1620        return_new: bool = False,
1621        return_old: bool = False,
1622        sync: Optional[bool] = None,
1623        silent: bool = False,
1624    ) -> Result[Union[bool, List[Union[Json, ArangoServerError]]]]:
1625        """Replace multiple documents.
1626
1627        .. note::
1628
1629            If replacing a document fails, the exception is not raised but
1630            returned as an object in the result list. It is up to you to
1631            inspect the list to determine which documents were replaced
1632            successfully (returns document metadata) and which were not
1633            (returns exception object).
1634
1635        .. note::
1636
1637            In edge/vertex collections, this method does NOT provide the
1638            transactional guarantees and validations that single replace
1639            operation does for graphs. If these properties are required, see
1640            :func:`arango.database.StandardDatabase.begin_batch_execution`
1641            for an alternative approach.
1642
1643        :param documents: New documents to replace the old ones with. They must
1644            contain the "_id" or "_key" fields. Edge documents must also have
1645            "_from" and "_to" fields.
1646        :type documents: [dict]
1647        :param check_rev: If set to True, revisions of **documents** (if given)
1648            are compared against the revisions of target documents.
1649        :type check_rev: bool
1650        :param return_new: Include body of the new document in the returned
1651            metadata. Ignored if parameter **silent** is set to True.
1652        :type return_new: bool
1653        :param return_old: Include body of the old document in the returned
1654            metadata. Ignored if parameter **silent** is set to True.
1655        :type return_old: bool
1656        :param sync: Block until operation is synchronized to disk.
1657        :type sync: bool | None
1658        :param silent: If set to True, no document metadata is returned. This
1659            can be used to save resources.
1660        :type silent: bool
1661        :return: List of document metadata (e.g. document keys, revisions) and
1662            any exceptions, or True if parameter **silent** was set to True.
1663        :rtype: [dict | ArangoServerError] | bool
1664        :raise arango.exceptions.DocumentReplaceError: If replace fails.
1665        """
1666        params: Params = {
1667            "returnNew": return_new,
1668            "returnOld": return_old,
1669            "ignoreRevs": not check_rev,
1670            "overwrite": not check_rev,
1671            "silent": silent,
1672        }
1673        if sync is not None:
1674            params["waitForSync"] = sync
1675
1676        documents = [self._ensure_key_in_body(doc) for doc in documents]
1677
1678        request = Request(
1679            method="put",
1680            endpoint=f"/_api/document/{self.name}",
1681            params=params,
1682            data=documents,
1683            write=self.name,
1684        )
1685
1686        def response_handler(
1687            resp: Response,
1688        ) -> Union[bool, List[Union[Json, ArangoServerError]]]:
1689            if not resp.is_success:
1690                raise DocumentReplaceError(resp, request)
1691            if silent is True:
1692                return True
1693
1694            results: List[Union[Json, ArangoServerError]] = []
1695            for body in resp.body:
1696                if "_id" in body:
1697                    body["_old_rev"] = body.pop("_oldRev")
1698                    results.append(body)
1699                else:
1700                    sub_resp = self._conn.prep_bulk_err_response(resp, body)
1701
1702                    error: ArangoServerError
1703                    if sub_resp.error_code == 1200:
1704                        error = DocumentRevisionError(sub_resp, request)
1705                    else:  # pragma: no cover
1706                        error = DocumentReplaceError(sub_resp, request)
1707
1708                    results.append(error)
1709
1710            return results
1711
1712        return self._execute(request, response_handler)
1713
1714    def replace_match(
1715        self,
1716        filters: Json,
1717        body: Json,
1718        limit: Optional[int] = None,
1719        sync: Optional[bool] = None,
1720    ) -> Result[int]:
1721        """Replace matching documents.
1722
1723        .. note::
1724
1725            In edge/vertex collections, this method does NOT provide the
1726            transactional guarantees and validations that single replace
1727            operation does for graphs. If these properties are required, see
1728            :func:`arango.database.StandardDatabase.begin_batch_execution`
1729            for an alternative approach.
1730
1731        :param filters: Document filters.
1732        :type filters: dict
1733        :param body: New document body.
1734        :type body: dict
1735        :param limit: Max number of documents to replace. If the limit is lower
1736            than the number of matched documents, random documents are chosen.
1737        :type limit: int | None
1738        :param sync: Block until operation is synchronized to disk.
1739        :type sync: bool | None
1740        :return: Number of documents replaced.
1741        :rtype: int
1742        :raise arango.exceptions.DocumentReplaceError: If replace fails.
1743        """
1744        data: Json = {"collection": self.name, "example": filters, "newValue": body}
1745        if limit is not None:
1746            data["limit"] = limit
1747        if sync is not None:
1748            data["waitForSync"] = sync
1749
1750        request = Request(
1751            method="put",
1752            endpoint="/_api/simple/replace-by-example",
1753            data=data,
1754            write=self.name,
1755        )
1756
1757        def response_handler(resp: Response) -> int:
1758            if not resp.is_success:
1759                raise DocumentReplaceError(resp, request)
1760            result: int = resp.body["replaced"]
1761            return result
1762
1763        return self._execute(request, response_handler)
1764
1765    def delete_many(
1766        self,
1767        documents: Sequence[Json],
1768        return_old: bool = False,
1769        check_rev: bool = True,
1770        sync: Optional[bool] = None,
1771        silent: bool = False,
1772    ) -> Result[Union[bool, List[Union[Json, ArangoServerError]]]]:
1773        """Delete multiple documents.
1774
1775        .. note::
1776
1777            If deleting a document fails, the exception is not raised but
1778            returned as an object in the result list. It is up to you to
1779            inspect the list to determine which documents were deleted
1780            successfully (returns document metadata) and which were not
1781            (returns exception object).
1782
1783        .. note::
1784
1785            In edge/vertex collections, this method does NOT provide the
1786            transactional guarantees and validations that single delete
1787            operation does for graphs. If these properties are required, see
1788            :func:`arango.database.StandardDatabase.begin_batch_execution`
1789            for an alternative approach.
1790
1791        :param documents: Document IDs, keys or bodies. Document bodies must
1792            contain the "_id" or "_key" fields.
1793        :type documents: [str | dict]
1794        :param return_old: Include bodies of the old documents in the result.
1795        :type return_old: bool
1796        :param check_rev: If set to True, revisions of **documents** (if given)
1797            are compared against the revisions of target documents.
1798        :type check_rev: bool
1799        :param sync: Block until operation is synchronized to disk.
1800        :type sync: bool | None
1801        :param silent: If set to True, no document metadata is returned. This
1802            can be used to save resources.
1803        :type silent: bool
1804        :return: List of document metadata (e.g. document keys, revisions) and
1805            any exceptions, or True if parameter **silent** was set to True.
1806        :rtype: [dict | ArangoServerError] | bool
1807        :raise arango.exceptions.DocumentDeleteError: If delete fails.
1808        """
1809        params: Params = {
1810            "returnOld": return_old,
1811            "ignoreRevs": not check_rev,
1812            "overwrite": not check_rev,
1813            "silent": silent,
1814        }
1815        if sync is not None:
1816            params["waitForSync"] = sync
1817
1818        documents = [
1819            self._ensure_key_in_body(doc) if isinstance(doc, dict) else doc
1820            for doc in documents
1821        ]
1822
1823        request = Request(
1824            method="delete",
1825            endpoint=f"/_api/document/{self.name}",
1826            params=params,
1827            data=documents,
1828            write=self.name,
1829        )
1830
1831        def response_handler(
1832            resp: Response,
1833        ) -> Union[bool, List[Union[Json, ArangoServerError]]]:
1834            if not resp.is_success:
1835                raise DocumentDeleteError(resp, request)
1836            if silent is True:
1837                return True
1838
1839            results: List[Union[Json, ArangoServerError]] = []
1840            for body in resp.body:
1841                if "_id" in body:
1842                    results.append(body)
1843                else:
1844                    sub_resp = self._conn.prep_bulk_err_response(resp, body)
1845
1846                    error: ArangoServerError
1847                    if sub_resp.error_code == 1200:
1848                        error = DocumentRevisionError(sub_resp, request)
1849                    else:
1850                        error = DocumentDeleteError(sub_resp, request)
1851                    results.append(error)
1852
1853            return results
1854
1855        return self._execute(request, response_handler)
1856
1857    def delete_match(
1858        self, filters: Json, limit: Optional[int] = None, sync: Optional[bool] = None
1859    ) -> Result[int]:
1860        """Delete matching documents.
1861
1862        .. note::
1863
1864            In edge/vertex collections, this method does NOT provide the
1865            transactional guarantees and validations that single delete
1866            operation does for graphs. If these properties are required, see
1867            :func:`arango.database.StandardDatabase.begin_batch_execution`
1868            for an alternative approach.
1869
1870        :param filters: Document filters.
1871        :type filters: dict
1872        :param limit: Max number of documents to delete. If the limit is lower
1873            than the number of matched documents, random documents are chosen.
1874        :type limit: int | None
1875        :param sync: Block until operation is synchronized to disk.
1876        :type sync: bool | None
1877        :return: Number of documents deleted.
1878        :rtype: int
1879        :raise arango.exceptions.DocumentDeleteError: If delete fails.
1880        """
1881        data: Json = {"collection": self.name, "example": filters}
1882        if sync is not None:
1883            data["waitForSync"] = sync
1884        if limit is not None and limit != 0:
1885            data["limit"] = limit
1886
1887        request = Request(
1888            method="put",
1889            endpoint="/_api/simple/remove-by-example",
1890            data=data,
1891            write=self.name,
1892        )
1893
1894        def response_handler(resp: Response) -> int:
1895            if resp.is_success:
1896                result: int = resp.body["deleted"]
1897                return result
1898            raise DocumentDeleteError(resp, request)
1899
1900        return self._execute(request, response_handler)
1901
1902    def import_bulk(
1903        self,
1904        documents: Sequence[Json],
1905        halt_on_error: bool = True,
1906        details: bool = True,
1907        from_prefix: Optional[str] = None,
1908        to_prefix: Optional[str] = None,
1909        overwrite: Optional[bool] = None,
1910        on_duplicate: Optional[str] = None,
1911        sync: Optional[bool] = None,
1912    ) -> Result[Json]:
1913        """Insert multiple documents into the collection.
1914
1915        .. note::
1916
1917            This method is faster than :func:`arango.collection.Collection.insert_many`
1918            but does not return as many details.
1919
1920        .. note::
1921
1922            In edge/vertex collections, this method does NOT provide the
1923            transactional guarantees and validations that single insert
1924            operation does for graphs. If these properties are required, see
1925            :func:`arango.database.StandardDatabase.begin_batch_execution`
1926            for an alternative approach.
1927
1928        :param documents: List of new documents to insert. If they contain the
1929            "_key" or "_id" fields, the values are used as the keys of the new
1930            documents (auto-generated otherwise). Any "_rev" field is ignored.
1931        :type documents: [dict]
1932        :param halt_on_error: Halt the entire import on an error.
1933        :type halt_on_error: bool
1934        :param details: If set to True, the returned result will include an
1935            additional list of detailed error messages.
1936        :type details: bool
1937        :param from_prefix: String prefix prepended to the value of "_from"
1938            field in each edge document inserted. For example, prefix "foo"
1939            prepended to "_from": "bar" will result in "_from": "foo/bar".
1940            Applies only to edge collections.
1941        :type from_prefix: str
1942        :param to_prefix: String prefix prepended to the value of "_to" field
1943            in edge document inserted. For example, prefix "foo" prepended to
1944            "_to": "bar" will result in "_to": "foo/bar". Applies only to edge
1945            collections.
1946        :type to_prefix: str
1947        :param overwrite: If set to True, all existing documents are removed
1948            prior to the import. Indexes are still preserved.
1949        :type overwrite: bool
1950        :param on_duplicate: Action to take on unique key constraint violations
1951            (for documents with "_key" fields). Allowed values are "error" (do
1952            not import the new documents and count them as errors), "update"
1953            (update the existing documents while preserving any fields missing
1954            in the new ones), "replace" (replace the existing documents with
1955            new ones), and  "ignore" (do not import the new documents and count
1956            them as ignored, as opposed to counting them as errors). Options
1957            "update" and "replace" may fail on secondary unique key constraint
1958            violations.
1959        :type on_duplicate: str
1960        :param sync: Block until operation is synchronized to disk.
1961        :type sync: bool | None
1962        :return: Result of the bulk import.
1963        :rtype: dict
1964        :raise arango.exceptions.DocumentInsertError: If import fails.
1965        """
1966        documents = [self._ensure_key_from_id(doc) for doc in documents]
1967
1968        params: Params = {"type": "array", "collection": self.name}
1969        if halt_on_error is not None:
1970            params["complete"] = halt_on_error
1971        if details is not None:
1972            params["details"] = details
1973        if from_prefix is not None:  # pragma: no cover
1974            params["fromPrefix"] = from_prefix
1975        if to_prefix is not None:  # pragma: no cover
1976            params["toPrefix"] = to_prefix
1977        if overwrite is not None:
1978            params["overwrite"] = overwrite
1979        if on_duplicate is not None:
1980            params["onDuplicate"] = on_duplicate
1981        if sync is not None:
1982            params["waitForSync"] = sync
1983
1984        request = Request(
1985            method="post",
1986            endpoint="/_api/import",
1987            data=documents,
1988            params=params,
1989            write=self.name,
1990        )
1991
1992        def response_handler(resp: Response) -> Json:
1993            if resp.is_success:
1994                result: Json = resp.body
1995                return result
1996            raise DocumentInsertError(resp, request)
1997
1998        return self._execute(request, response_handler)
1999
2000
2001class StandardCollection(Collection):
2002    """Standard ArangoDB collection API wrapper."""
2003
2004    def __repr__(self) -> str:
2005        return f"<StandardCollection {self.name}>"
2006
2007    def __getitem__(self, key: Union[str, Json]) -> Result[Optional[Json]]:
2008        return self.get(key)
2009
2010    def get(
2011        self,
2012        document: Union[str, Json],
2013        rev: Optional[str] = None,
2014        check_rev: bool = True,
2015    ) -> Result[Optional[Json]]:
2016        """Return a document.
2017
2018        :param document: Document ID, key or body. Document body must contain
2019            the "_id" or "_key" field.
2020        :type document: str | dict
2021        :param rev: Expected document revision. Overrides the value of "_rev"
2022            field in **document** if present.
2023        :type rev: str | None
2024        :param check_rev: If set to True, revision of **document** (if given)
2025            is compared against the revision of target document.
2026        :type check_rev: bool
2027        :return: Document, or None if not found.
2028        :rtype: dict | None
2029        :raise arango.exceptions.DocumentGetError: If retrieval fails.
2030        :raise arango.exceptions.DocumentRevisionError: If revisions mismatch.
2031        """
2032        handle, body, headers = self._prep_from_doc(document, rev, check_rev)
2033
2034        request = Request(
2035            method="get",
2036            endpoint=f"/_api/document/{handle}",
2037            headers=headers,
2038            read=self.name,
2039        )
2040
2041        def response_handler(resp: Response) -> Optional[Json]:
2042            if resp.error_code == 1202:
2043                return None
2044            if resp.status_code == 412:
2045                raise DocumentRevisionError(resp, request)
2046            if not resp.is_success:
2047                raise DocumentGetError(resp, request)
2048
2049            result: Json = resp.body
2050            return result
2051
2052        return self._execute(request, response_handler)
2053
2054    def insert(
2055        self,
2056        document: Json,
2057        return_new: bool = False,
2058        sync: Optional[bool] = None,
2059        silent: bool = False,
2060        overwrite: bool = False,
2061        return_old: bool = False,
2062        overwrite_mode: Optional[str] = None,
2063        keep_none: Optional[bool] = None,
2064        merge: Optional[bool] = None,
2065    ) -> Result[Union[bool, Json]]:
2066        """Insert a new document.
2067
2068        :param document: Document to insert. If it contains the "_key" or "_id"
2069            field, the value is used as the key of the new document (otherwise
2070            it is auto-generated). Any "_rev" field is ignored.
2071        :type document: dict
2072        :param return_new: Include body of the new document in the returned
2073            metadata. Ignored if parameter **silent** is set to True.
2074        :type return_new: bool
2075        :param sync: Block until operation is synchronized to disk.
2076        :type sync: bool | None
2077        :param silent: If set to True, no document metadata is returned. This
2078            can be used to save resources.
2079        :type silent: bool
2080        :param overwrite: If set to True, operation does not fail on duplicate
2081            key and existing document is overwritten (replace-insert).
2082        :type overwrite: bool
2083        :param return_old: Include body of the old document if overwritten.
2084            Ignored if parameter **silent** is set to True.
2085        :type return_old: bool
2086        :param overwrite_mode: Overwrite behavior used when the document key
2087            exists already. Allowed values are "replace" (replace-insert) or
2088            "update" (update-insert). Implicitly sets the value of parameter
2089            **overwrite**.
2090        :type overwrite_mode: str | None
2091        :param keep_none: If set to True, fields with value None are retained
2092            in the document. Otherwise, they are removed completely. Applies
2093            only when **overwrite_mode** is set to "update" (update-insert).
2094        :type keep_none: bool | None
2095        :param merge: If set to True (default), sub-dictionaries are merged
2096            instead of the new one overwriting the old one. Applies only when
2097            **overwrite_mode** is set to "update" (update-insert).
2098        :type merge: bool | None
2099        :return: Document metadata (e.g. document key, revision) or True if
2100            parameter **silent** was set to True.
2101        :rtype: bool | dict
2102        :raise arango.exceptions.DocumentInsertError: If insert fails.
2103        """
2104        document = self._ensure_key_from_id(document)
2105
2106        params: Params = {
2107            "returnNew": return_new,
2108            "silent": silent,
2109            "overwrite": overwrite,
2110            "returnOld": return_old,
2111        }
2112        if sync is not None:
2113            params["waitForSync"] = sync
2114        if overwrite_mode is not None:
2115            params["overwriteMode"] = overwrite_mode
2116        if keep_none is not None:
2117            params["keepNull"] = keep_none
2118        if merge is not None:
2119            params["mergeObjects"] = merge
2120
2121        request = Request(
2122            method="post",
2123            endpoint=f"/_api/document/{self.name}",
2124            data=document,
2125            params=params,
2126            write=self.name,
2127        )
2128
2129        def response_handler(resp: Response) -> Union[bool, Json]:
2130            if not resp.is_success:
2131                raise DocumentInsertError(resp, request)
2132
2133            if silent:
2134                return True
2135
2136            result: Json = resp.body
2137            if "_oldRev" in result:
2138                result["_old_rev"] = result.pop("_oldRev")
2139            return result
2140
2141        return self._execute(request, response_handler)
2142
2143    def update(
2144        self,
2145        document: Json,
2146        check_rev: bool = True,
2147        merge: bool = True,
2148        keep_none: bool = True,
2149        return_new: bool = False,
2150        return_old: bool = False,
2151        sync: Optional[bool] = None,
2152        silent: bool = False,
2153    ) -> Result[Union[bool, Json]]:
2154        """Update a document.
2155
2156        :param document: Partial or full document with the updated values. It
2157            must contain the "_id" or "_key" field.
2158        :type document: dict
2159        :param check_rev: If set to True, revision of **document** (if given)
2160            is compared against the revision of target document.
2161        :type check_rev: bool
2162        :param merge: If set to True, sub-dictionaries are merged instead of
2163            the new one overwriting the old one.
2164        :type merge: bool | None
2165        :param keep_none: If set to True, fields with value None are retained
2166            in the document. Otherwise, they are removed completely.
2167        :type keep_none: bool | None
2168        :param return_new: Include body of the new document in the returned
2169            metadata. Ignored if parameter **silent** is set to True.
2170        :type return_new: bool
2171        :param return_old: Include body of the old document in the returned
2172            metadata. Ignored if parameter **silent** is set to True.
2173        :type return_old: bool
2174        :param sync: Block until operation is synchronized to disk.
2175        :type sync: bool | None
2176        :param silent: If set to True, no document metadata is returned. This
2177            can be used to save resources.
2178        :type silent: bool
2179        :return: Document metadata (e.g. document key, revision) or True if
2180            parameter **silent** was set to True.
2181        :rtype: bool | dict
2182        :raise arango.exceptions.DocumentUpdateError: If update fails.
2183        :raise arango.exceptions.DocumentRevisionError: If revisions mismatch.
2184        """
2185        params: Params = {
2186            "keepNull": keep_none,
2187            "mergeObjects": merge,
2188            "returnNew": return_new,
2189            "returnOld": return_old,
2190            "ignoreRevs": not check_rev,
2191            "overwrite": not check_rev,
2192            "silent": silent,
2193        }
2194        if sync is not None:
2195            params["waitForSync"] = sync
2196
2197        request = Request(
2198            method="patch",
2199            endpoint=f"/_api/document/{self._extract_id(document)}",
2200            data=document,
2201            params=params,
2202            write=self.name,
2203        )
2204
2205        def response_handler(resp: Response) -> Union[bool, Json]:
2206            if resp.status_code == 412:
2207                raise DocumentRevisionError(resp, request)
2208            elif not resp.is_success:
2209                raise DocumentUpdateError(resp, request)
2210            if silent is True:
2211                return True
2212
2213            result: Json = resp.body
2214            result["_old_rev"] = result.pop("_oldRev")
2215            return result
2216
2217        return self._execute(request, response_handler)
2218
2219    def replace(
2220        self,
2221        document: Json,
2222        check_rev: bool = True,
2223        return_new: bool = False,
2224        return_old: bool = False,
2225        sync: Optional[bool] = None,
2226        silent: bool = False,
2227    ) -> Result[Union[bool, Json]]:
2228        """Replace a document.
2229
2230        :param document: New document to replace the old one with. It must
2231            contain the "_id" or "_key" field. Edge document must also have
2232            "_from" and "_to" fields.
2233        :type document: dict
2234        :param check_rev: If set to True, revision of **document** (if given)
2235            is compared against the revision of target document.
2236        :type check_rev: bool
2237        :param return_new: Include body of the new document in the returned
2238            metadata. Ignored if parameter **silent** is set to True.
2239        :type return_new: bool
2240        :param return_old: Include body of the old document in the returned
2241            metadata. Ignored if parameter **silent** is set to True.
2242        :type return_old: bool
2243        :param sync: Block until operation is synchronized to disk.
2244        :type sync: bool | None
2245        :param silent: If set to True, no document metadata is returned. This
2246            can be used to save resources.
2247        :type silent: bool
2248        :return: Document metadata (e.g. document key, revision) or True if
2249            parameter **silent** was set to True.
2250        :rtype: bool | dict
2251        :raise arango.exceptions.DocumentReplaceError: If replace fails.
2252        :raise arango.exceptions.DocumentRevisionError: If revisions mismatch.
2253        """
2254        params: Params = {
2255            "returnNew": return_new,
2256            "returnOld": return_old,
2257            "ignoreRevs": not check_rev,
2258            "overwrite": not check_rev,
2259            "silent": silent,
2260        }
2261        if sync is not None:
2262            params["waitForSync"] = sync
2263
2264        request = Request(
2265            method="put",
2266            endpoint=f"/_api/document/{self._extract_id(document)}",
2267            params=params,
2268            data=document,
2269            write=self.name,
2270        )
2271
2272        def response_handler(resp: Response) -> Union[bool, Json]:
2273            if resp.status_code == 412:
2274                raise DocumentRevisionError(resp, request)
2275            if not resp.is_success:
2276                raise DocumentReplaceError(resp, request)
2277
2278            if silent is True:
2279                return True
2280
2281            result: Json = resp.body
2282            if "_oldRev" in result:
2283                result["_old_rev"] = result.pop("_oldRev")
2284            return result
2285
2286        return self._execute(request, response_handler)
2287
2288    def delete(
2289        self,
2290        document: Union[str, Json],
2291        rev: Optional[str] = None,
2292        check_rev: bool = True,
2293        ignore_missing: bool = False,
2294        return_old: bool = False,
2295        sync: Optional[bool] = None,
2296        silent: bool = False,
2297    ) -> Result[Union[bool, Json]]:
2298        """Delete a document.
2299
2300        :param document: Document ID, key or body. Document body must contain
2301            the "_id" or "_key" field.
2302        :type document: str | dict
2303        :param rev: Expected document revision. Overrides the value of "_rev"
2304            field in **document** if present.
2305        :type rev: str | None
2306        :param check_rev: If set to True, revision of **document** (if given)
2307            is compared against the revision of target document.
2308        :type check_rev: bool
2309        :param ignore_missing: Do not raise an exception on missing document.
2310            This parameter has no effect in transactions where an exception is
2311            always raised on failures.
2312        :type ignore_missing: bool
2313        :param return_old: Include body of the old document in the returned
2314            metadata. Ignored if parameter **silent** is set to True.
2315        :type return_old: bool
2316        :param sync: Block until operation is synchronized to disk.
2317        :type sync: bool | None
2318        :param silent: If set to True, no document metadata is returned. This
2319            can be used to save resources.
2320        :type silent: bool
2321        :return: Document metadata (e.g. document key, revision), or True if
2322            parameter **silent** was set to True, or False if document was not
2323            found and **ignore_missing** was set to True (does not apply in
2324            transactions).
2325        :rtype: bool | dict
2326        :raise arango.exceptions.DocumentDeleteError: If delete fails.
2327        :raise arango.exceptions.DocumentRevisionError: If revisions mismatch.
2328        """
2329        handle, body, headers = self._prep_from_doc(document, rev, check_rev)
2330
2331        params: Params = {
2332            "returnOld": return_old,
2333            "ignoreRevs": not check_rev,
2334            "overwrite": not check_rev,
2335            "silent": silent,
2336        }
2337        if sync is not None:
2338            params["waitForSync"] = sync
2339
2340        request = Request(
2341            method="delete",
2342            endpoint=f"/_api/document/{handle}",
2343            params=params,
2344            headers=headers,
2345            write=self.name,
2346        )
2347
2348        def response_handler(resp: Response) -> Union[bool, Json]:
2349            if resp.error_code == 1202 and ignore_missing:
2350                return False
2351            if resp.status_code == 412:
2352                raise DocumentRevisionError(resp, request)
2353            if not resp.is_success:
2354                raise DocumentDeleteError(resp, request)
2355            return True if silent else resp.body
2356
2357        return self._execute(request, response_handler)
2358
2359
2360class VertexCollection(Collection):
2361    """Vertex collection API wrapper.
2362
2363    :param connection: HTTP connection.
2364    :param executor: API executor.
2365    :param graph: Graph name.
2366    :param name: Vertex collection name.
2367    """
2368
2369    def __init__(
2370        self, connection: Connection, executor: ApiExecutor, graph: str, name: str
2371    ) -> None:
2372        super().__init__(connection, executor, name)
2373        self._graph = graph
2374
2375    def __repr__(self) -> str:
2376        return f"<VertexCollection {self.name}>"
2377
2378    def __getitem__(self, key: Union[str, Json]) -> Result[Optional[Json]]:
2379        return self.get(key)
2380
2381    @property
2382    def graph(self) -> str:
2383        """Return the graph name.
2384
2385        :return: Graph name.
2386        :rtype: str
2387        """
2388        return self._graph
2389
2390    def get(
2391        self,
2392        vertex: Union[str, Json],
2393        rev: Optional[str] = None,
2394        check_rev: bool = True,
2395    ) -> Result[Optional[Json]]:
2396        """Return a vertex document.
2397
2398        :param vertex: Vertex document ID, key or body. Document body must
2399            contain the "_id" or "_key" field.
2400        :type vertex: str | dict
2401        :param rev: Expected document revision. Overrides the value of "_rev"
2402            field in **vertex** if present.
2403        :type rev: str | None
2404        :param check_rev: If set to True, revision of **vertex** (if given) is
2405            compared against the revision of target vertex document.
2406        :type check_rev: bool
2407        :return: Vertex document or None if not found.
2408        :rtype: dict | None
2409        :raise arango.exceptions.DocumentGetError: If retrieval fails.
2410        :raise arango.exceptions.DocumentRevisionError: If revisions mismatch.
2411        """
2412        handle, body, headers = self._prep_from_doc(vertex, rev, check_rev)
2413
2414        request = Request(
2415            method="get",
2416            endpoint=f"/_api/gharial/{self._graph}/vertex/{handle}",
2417            headers=headers,
2418            read=self.name,
2419        )
2420
2421        def response_handler(resp: Response) -> Optional[Json]:
2422            if resp.error_code == 1202:
2423                return None
2424            if resp.status_code == 412:
2425                raise DocumentRevisionError(resp, request)
2426            if not resp.is_success:
2427                raise DocumentGetError(resp, request)
2428            result: Json = resp.body["vertex"]
2429            return result
2430
2431        return self._execute(request, response_handler)
2432
2433    def insert(
2434        self,
2435        vertex: Json,
2436        sync: Optional[bool] = None,
2437        silent: bool = False,
2438        return_new: bool = False,
2439    ) -> Result[Union[bool, Json]]:
2440        """Insert a new vertex document.
2441
2442        :param vertex: New vertex document to insert. If it has "_key" or "_id"
2443            field, its value is used as key of the new vertex (otherwise it is
2444            auto-generated). Any "_rev" field is ignored.
2445        :type vertex: dict
2446        :param sync: Block until operation is synchronized to disk.
2447        :type sync: bool | None
2448        :param silent: If set to True, no document metadata is returned. This
2449            can be used to save resources.
2450        :type silent: bool
2451        :param return_new: Include body of the new document in the returned
2452            metadata. Ignored if parameter **silent** is set to True.
2453        :type return_new: bool
2454        :return: Document metadata (e.g. document key, revision), or True if
2455            parameter **silent** was set to True.
2456        :rtype: bool | dict
2457        :raise arango.exceptions.DocumentInsertError: If insert fails.
2458        """
2459        vertex = self._ensure_key_from_id(vertex)
2460
2461        params: Params = {"silent": silent, "returnNew": return_new}
2462        if sync is not None:
2463            params["waitForSync"] = sync
2464
2465        request = Request(
2466            method="post",
2467            endpoint=f"/_api/gharial/{self._graph}/vertex/{self.name}",
2468            data=vertex,
2469            params=params,
2470            write=self.name,
2471        )
2472
2473        def response_handler(resp: Response) -> Union[bool, Json]:
2474            if not resp.is_success:
2475                raise DocumentInsertError(resp, request)
2476            if silent:
2477                return True
2478            return format_vertex(resp.body)
2479
2480        return self._execute(request, response_handler)
2481
2482    def update(
2483        self,
2484        vertex: Json,
2485        check_rev: bool = True,
2486        keep_none: bool = True,
2487        sync: Optional[bool] = None,
2488        silent: bool = False,
2489        return_old: bool = False,
2490        return_new: bool = False,
2491    ) -> Result[Union[bool, Json]]:
2492        """Update a vertex document.
2493
2494        :param vertex: Partial or full vertex document with updated values. It
2495            must contain the "_key" or "_id" field.
2496        :type vertex: dict
2497        :param check_rev: If set to True, revision of **vertex** (if given) is
2498            compared against the revision of target vertex document.
2499        :type check_rev: bool
2500        :param keep_none: If set to True, fields with value None are retained
2501            in the document. If set to False, they are removed completely.
2502        :type keep_none: bool | None
2503        :param sync: Block until operation is synchronized to disk.
2504        :type sync: bool | None
2505        :param silent: If set to True, no document metadata is returned. This
2506            can be used to save resources.
2507        :type silent: bool
2508        :param return_old: Include body of the old document in the returned
2509            metadata. Ignored if parameter **silent** is set to True.
2510        :type return_old: bool
2511        :param return_new: Include body of the new document in the returned
2512            metadata. Ignored if parameter **silent** is set to True.
2513        :type return_new: bool
2514        :return: Document metadata (e.g. document key, revision) or True if
2515            parameter **silent** was set to True.
2516        :rtype: bool | dict
2517        :raise arango.exceptions.DocumentUpdateError: If update fails.
2518        :raise arango.exceptions.DocumentRevisionError: If revisions mismatch.
2519        """
2520        vertex_id, headers = self._prep_from_body(vertex, check_rev)
2521
2522        params: Params = {
2523            "keepNull": keep_none,
2524            "overwrite": not check_rev,
2525            "silent": silent,
2526            "returnNew": return_new,
2527            "returnOld": return_old,
2528        }
2529        if sync is not None:
2530            params["waitForSync"] = sync
2531
2532        request = Request(
2533            method="patch",
2534            endpoint=f"/_api/gharial/{self._graph}/vertex/{vertex_id}",
2535            headers=headers,
2536            params=params,
2537            data=vertex,
2538            write=self.name,
2539        )
2540
2541        def response_handler(resp: Response) -> Union[bool, Json]:
2542            if resp.status_code == 412:  # pragma: no cover
2543                raise DocumentRevisionError(resp, request)
2544            elif not resp.is_success:
2545                raise DocumentUpdateError(resp, request)
2546            if silent is True:
2547                return True
2548            return format_vertex(resp.body)
2549
2550        return self._execute(request, response_handler)
2551
2552    def replace(
2553        self,
2554        vertex: Json,
2555        check_rev: bool = True,
2556        sync: Optional[bool] = None,
2557        silent: bool = False,
2558        return_old: bool = False,
2559        return_new: bool = False,
2560    ) -> Result[Union[bool, Json]]:
2561        """Replace a vertex document.
2562
2563        :param vertex: New vertex document to replace the old one with. It must
2564            contain the "_key" or "_id" field.
2565        :type vertex: dict
2566        :param check_rev: If set to True, revision of **vertex** (if given) is
2567            compared against the revision of target vertex document.
2568        :type check_rev: bool
2569        :param sync: Block until operation is synchronized to disk.
2570        :type sync: bool | None
2571        :param silent: If set to True, no document metadata is returned. This
2572            can be used to save resources.
2573        :type silent: bool
2574        :param return_old: Include body of the old document in the returned
2575            metadata. Ignored if parameter **silent** is set to True.
2576        :type return_old: bool
2577        :param return_new: Include body of the new document in the returned
2578            metadata. Ignored if parameter **silent** is set to True.
2579        :type return_new: bool
2580        :return: Document metadata (e.g. document key, revision) or True if
2581            parameter **silent** was set to True.
2582        :rtype: bool | dict
2583        :raise arango.exceptions.DocumentReplaceError: If replace fails.
2584        :raise arango.exceptions.DocumentRevisionError: If revisions mismatch.
2585        """
2586        vertex_id, headers = self._prep_from_body(vertex, check_rev)
2587
2588        params: Params = {
2589            "silent": silent,
2590            "returnNew": return_new,
2591            "returnOld": return_old,
2592        }
2593        if sync is not None:
2594            params["waitForSync"] = sync
2595
2596        request = Request(
2597            method="put",
2598            endpoint=f"/_api/gharial/{self._graph}/vertex/{vertex_id}",
2599            headers=headers,
2600            params=params,
2601            data=vertex,
2602            write=self.name,
2603        )
2604
2605        def response_handler(resp: Response) -> Union[bool, Json]:
2606            if resp.status_code == 412:  # pragma: no cover
2607                raise DocumentRevisionError(resp, request)
2608            elif not resp.is_success:
2609                raise DocumentReplaceError(resp, request)
2610            if silent is True:
2611                return True
2612            return format_vertex(resp.body)
2613
2614        return self._execute(request, response_handler)
2615
2616    def delete(
2617        self,
2618        vertex: Union[str, Json],
2619        rev: Optional[str] = None,
2620        check_rev: bool = True,
2621        ignore_missing: bool = False,
2622        sync: Optional[bool] = None,
2623        return_old: bool = False,
2624    ) -> Result[Union[bool, Json]]:
2625        """Delete a vertex document. All connected edges are also deleted.
2626
2627        :param vertex: Vertex document ID, key or body. Document body must
2628            contain the "_id" or "_key" field.
2629        :type vertex: str | dict
2630        :param rev: Expected document revision. Overrides the value of "_rev"
2631            field in **vertex** if present.
2632        :type rev: str | None
2633        :param check_rev: If set to True, revision of **vertex** (if given) is
2634            compared against the revision of target vertex document.
2635        :type check_rev: bool
2636        :param ignore_missing: Do not raise an exception on missing document.
2637            This parameter has no effect in transactions where an exception is
2638            always raised on failures.
2639        :type ignore_missing: bool
2640        :param sync: Block until operation is synchronized to disk.
2641        :type sync: bool | None
2642        :param return_old: Return body of the old document in the result.
2643        :type return_old: bool
2644        :return: True if vertex was deleted successfully, False if vertex was
2645            not found and **ignore_missing** was set to True (does not apply in
2646            transactions). Old document is returned if **return_old** is set to
2647            True.
2648        :rtype: bool | dict
2649        :raise arango.exceptions.DocumentDeleteError: If delete fails.
2650        :raise arango.exceptions.DocumentRevisionError: If revisions mismatch.
2651        """
2652        handle, _, headers = self._prep_from_doc(vertex, rev, check_rev)
2653
2654        params: Params = {"returnOld": return_old}
2655        if sync is not None:
2656            params["waitForSync"] = sync
2657
2658        request = Request(
2659            method="delete",
2660            endpoint=f"/_api/gharial/{self._graph}/vertex/{handle}",
2661            params=params,
2662            headers=headers,
2663            write=self.name,
2664        )
2665
2666        def response_handler(resp: Response) -> Union[bool, Json]:
2667            if resp.error_code == 1202 and ignore_missing:
2668                return False
2669            if resp.status_code == 412:  # pragma: no cover
2670                raise DocumentRevisionError(resp, request)
2671            if not resp.is_success:
2672                raise DocumentDeleteError(resp, request)
2673
2674            result: Json = resp.body
2675            return {"old": result["old"]} if return_old else True
2676
2677        return self._execute(request, response_handler)
2678
2679
2680class EdgeCollection(Collection):
2681    """ArangoDB edge collection API wrapper.
2682
2683    :param connection: HTTP connection.
2684    :param executor: API executor.
2685    :param graph: Graph name.
2686    :param name: Edge collection name.
2687    """
2688
2689    def __init__(
2690        self, connection: Connection, executor: ApiExecutor, graph: str, name: str
2691    ) -> None:
2692        super().__init__(connection, executor, name)
2693        self._graph = graph
2694
2695    def __repr__(self) -> str:
2696        return f"<EdgeCollection {self.name}>"
2697
2698    def __getitem__(self, key: Union[str, Json]) -> Result[Optional[Json]]:
2699        return self.get(key)
2700
2701    @property
2702    def graph(self) -> str:
2703        """Return the graph name.
2704
2705        :return: Graph name.
2706        :rtype: str
2707        """
2708        return self._graph
2709
2710    def get(
2711        self, edge: Union[str, Json], rev: Optional[str] = None, check_rev: bool = True
2712    ) -> Result[Optional[Json]]:
2713        """Return an edge document.
2714
2715        :param edge: Edge document ID, key or body. Document body must contain
2716            the "_id" or "_key" field.
2717        :type edge: str | dict
2718        :param rev: Expected document revision. Overrides the value of "_rev"
2719            field in **edge** if present.
2720        :type rev: str | None
2721        :param check_rev: If set to True, revision of **edge** (if given) is
2722            compared against the revision of target edge document.
2723        :type check_rev: bool
2724        :return: Edge document or None if not found.
2725        :rtype: dict | None
2726        :raise arango.exceptions.DocumentGetError: If retrieval fails.
2727        :raise arango.exceptions.DocumentRevisionError: If revisions mismatch.
2728        """
2729        handle, body, headers = self._prep_from_doc(edge, rev, check_rev)
2730
2731        request = Request(
2732            method="get",
2733            endpoint=f"/_api/gharial/{self._graph}/edge/{handle}",
2734            headers=headers,
2735            read=self.name,
2736        )
2737
2738        def response_handler(resp: Response) -> Optional[Json]:
2739            if resp.error_code == 1202:
2740                return None
2741            if resp.status_code == 412:  # pragma: no cover
2742                raise DocumentRevisionError(resp, request)
2743            if not resp.is_success:
2744                raise DocumentGetError(resp, request)
2745
2746            result: Json = resp.body["edge"]
2747            return result
2748
2749        return self._execute(request, response_handler)
2750
2751    def insert(
2752        self,
2753        edge: Json,
2754        sync: Optional[bool] = None,
2755        silent: bool = False,
2756        return_new: bool = False,
2757    ) -> Result[Union[bool, Json]]:
2758        """Insert a new edge document.
2759
2760        :param edge: New edge document to insert. It must contain "_from" and
2761            "_to" fields. If it has "_key" or "_id" field, its value is used
2762            as key of the new edge document (otherwise it is auto-generated).
2763            Any "_rev" field is ignored.
2764        :type edge: dict
2765        :param sync: Block until operation is synchronized to disk.
2766        :type sync: bool | None
2767        :param silent: If set to True, no document metadata is returned. This
2768            can be used to save resources.
2769        :type silent: bool
2770        :param return_new: Include body of the new document in the returned
2771            metadata. Ignored if parameter **silent** is set to True.
2772        :type return_new: bool
2773        :return: Document metadata (e.g. document key, revision) or True if
2774            parameter **silent** was set to True.
2775        :rtype: bool | dict
2776        :raise arango.exceptions.DocumentInsertError: If insert fails.
2777        """
2778        edge = self._ensure_key_from_id(edge)
2779
2780        params: Params = {"silent": silent, "returnNew": return_new}
2781        if sync is not None:
2782            params["waitForSync"] = sync
2783
2784        request = Request(
2785            method="post",
2786            endpoint=f"/_api/gharial/{self._graph}/edge/{self.name}",
2787            data=edge,
2788            params=params,
2789            write=self.name,
2790        )
2791
2792        def response_handler(resp: Response) -> Union[bool, Json]:
2793            if not resp.is_success:
2794                raise DocumentInsertError(resp, request)
2795            if silent:
2796                return True
2797            return format_edge(resp.body)
2798
2799        return self._execute(request, response_handler)
2800
2801    def update(
2802        self,
2803        edge: Json,
2804        check_rev: bool = True,
2805        keep_none: bool = True,
2806        sync: Optional[bool] = None,
2807        silent: bool = False,
2808        return_old: bool = False,
2809        return_new: bool = False,
2810    ) -> Result[Union[bool, Json]]:
2811        """Update an edge document.
2812
2813        :param edge: Partial or full edge document with updated values. It must
2814            contain the "_key" or "_id" field.
2815        :type edge: dict
2816        :param check_rev: If set to True, revision of **edge** (if given) is
2817            compared against the revision of target edge document.
2818        :type check_rev: bool
2819        :param keep_none: If set to True, fields with value None are retained
2820            in the document. If set to False, they are removed completely.
2821        :type keep_none: bool | None
2822        :param sync: Block until operation is synchronized to disk.
2823        :type sync: bool | None
2824        :param silent: If set to True, no document metadata is returned. This
2825            can be used to save resources.
2826        :type silent: bool
2827        :param return_old: Include body of the old document in the returned
2828            metadata. Ignored if parameter **silent** is set to True.
2829        :type return_old: bool
2830        :param return_new: Include body of the new document in the returned
2831            metadata. Ignored if parameter **silent** is set to True.
2832        :type return_new: bool
2833        :return: Document metadata (e.g. document key, revision) or True if
2834            parameter **silent** was set to True.
2835        :rtype: bool | dict
2836        :raise arango.exceptions.DocumentUpdateError: If update fails.
2837        :raise arango.exceptions.DocumentRevisionError: If revisions mismatch.
2838        """
2839        edge_id, headers = self._prep_from_body(edge, check_rev)
2840
2841        params: Params = {
2842            "keepNull": keep_none,
2843            "overwrite": not check_rev,
2844            "silent": silent,
2845            "returnNew": return_new,
2846            "returnOld": return_old,
2847        }
2848        if sync is not None:
2849            params["waitForSync"] = sync
2850
2851        request = Request(
2852            method="patch",
2853            endpoint=f"/_api/gharial/{self._graph}/edge/{edge_id}",
2854            headers=headers,
2855            params=params,
2856            data=edge,
2857            write=self.name,
2858        )
2859
2860        def response_handler(resp: Response) -> Union[bool, Json]:
2861            if resp.status_code == 412:  # pragma: no cover
2862                raise DocumentRevisionError(resp, request)
2863            if not resp.is_success:
2864                raise DocumentUpdateError(resp, request)
2865            if silent is True:
2866                return True
2867            return format_edge(resp.body)
2868
2869        return self._execute(request, response_handler)
2870
2871    def replace(
2872        self,
2873        edge: Json,
2874        check_rev: bool = True,
2875        sync: Optional[bool] = None,
2876        silent: bool = False,
2877        return_old: bool = False,
2878        return_new: bool = False,
2879    ) -> Result[Union[bool, Json]]:
2880        """Replace an edge document.
2881
2882        :param edge: New edge document to replace the old one with. It must
2883            contain the "_key" or "_id" field. It must also contain the "_from"
2884            and "_to" fields.
2885        :type edge: dict
2886        :param check_rev: If set to True, revision of **edge** (if given) is
2887            compared against the revision of target edge document.
2888        :type check_rev: bool
2889        :param sync: Block until operation is synchronized to disk.
2890        :type sync: bool | None
2891        :param silent: If set to True, no document metadata is returned. This
2892            can be used to save resources.
2893        :type silent: bool
2894        :param return_old: Include body of the old document in the returned
2895            metadata. Ignored if parameter **silent** is set to True.
2896        :type return_old: bool
2897        :param return_new: Include body of the new document in the returned
2898            metadata. Ignored if parameter **silent** is set to True.
2899        :type return_new: bool
2900        :return: Document metadata (e.g. document key, revision) or True if
2901            parameter **silent** was set to True.
2902        :rtype: bool | dict
2903        :raise arango.exceptions.DocumentReplaceError: If replace fails.
2904        :raise arango.exceptions.DocumentRevisionError: If revisions mismatch.
2905        """
2906        edge_id, headers = self._prep_from_body(edge, check_rev)
2907
2908        params: Params = {
2909            "silent": silent,
2910            "returnNew": return_new,
2911            "returnOld": return_old,
2912        }
2913        if sync is not None:
2914            params["waitForSync"] = sync
2915
2916        request = Request(
2917            method="put",
2918            endpoint=f"/_api/gharial/{self._graph}/edge/{edge_id}",
2919            headers=headers,
2920            params=params,
2921            data=edge,
2922            write=self.name,
2923        )
2924
2925        def response_handler(resp: Response) -> Union[bool, Json]:
2926            if resp.status_code == 412:  # pragma: no cover
2927                raise DocumentRevisionError(resp, request)
2928            if not resp.is_success:
2929                raise DocumentReplaceError(resp, request)
2930            if silent is True:
2931                return True
2932            return format_edge(resp.body)
2933
2934        return self._execute(request, response_handler)
2935
2936    def delete(
2937        self,
2938        edge: Union[str, Json],
2939        rev: Optional[str] = None,
2940        check_rev: bool = True,
2941        ignore_missing: bool = False,
2942        sync: Optional[bool] = None,
2943        return_old: bool = False,
2944    ) -> Result[Union[bool, Json]]:
2945        """Delete an edge document.
2946
2947        :param edge: Edge document ID, key or body. Document body must contain
2948            the "_id" or "_key" field.
2949        :type edge: str | dict
2950        :param rev: Expected document revision. Overrides the value of "_rev"
2951            field in **edge** if present.
2952        :type rev: str | None
2953        :param check_rev: If set to True, revision of **edge** (if given) is
2954            compared against the revision of target edge document.
2955        :type check_rev: bool
2956        :param ignore_missing: Do not raise an exception on missing document.
2957            This parameter has no effect in transactions where an exception is
2958            always raised on failures.
2959        :type ignore_missing: bool
2960        :param sync: Block until operation is synchronized to disk.
2961        :type sync: bool | None
2962        :param return_old: Return body of the old document in the result.
2963        :type return_old: bool
2964        :return: True if edge was deleted successfully, False if edge was not
2965            found and **ignore_missing** was set to True (does not  apply in
2966            transactions).
2967        :rtype: bool
2968        :raise arango.exceptions.DocumentDeleteError: If delete fails.
2969        :raise arango.exceptions.DocumentRevisionError: If revisions mismatch.
2970        """
2971        handle, _, headers = self._prep_from_doc(edge, rev, check_rev)
2972
2973        params: Params = {"returnOld": return_old}
2974        if sync is not None:
2975            params["waitForSync"] = sync
2976
2977        request = Request(
2978            method="delete",
2979            endpoint=f"/_api/gharial/{self._graph}/edge/{handle}",
2980            params=params,
2981            headers=headers,
2982            write=self.name,
2983        )
2984
2985        def response_handler(resp: Response) -> Union[bool, Json]:
2986            if resp.error_code == 1202 and ignore_missing:
2987                return False
2988            if resp.status_code == 412:  # pragma: no cover
2989                raise DocumentRevisionError(resp, request)
2990            if not resp.is_success:
2991                raise DocumentDeleteError(resp, request)
2992
2993            result: Json = resp.body
2994            return {"old": result["old"]} if return_old else True
2995
2996        return self._execute(request, response_handler)
2997
2998    def link(
2999        self,
3000        from_vertex: Union[str, Json],
3001        to_vertex: Union[str, Json],
3002        data: Optional[Json] = None,
3003        sync: Optional[bool] = None,
3004        silent: bool = False,
3005        return_new: bool = False,
3006    ) -> Result[Union[bool, Json]]:
3007        """Insert a new edge document linking the given vertices.
3008
3009        :param from_vertex: "From" vertex document ID or body with "_id" field.
3010        :type from_vertex: str | dict
3011        :param to_vertex: "To" vertex document ID or body with "_id" field.
3012        :type to_vertex: str | dict
3013        :param data: Any extra data for the new edge document. If it has "_key"
3014            or "_id" field, its value is used as key of the new edge document
3015            (otherwise it is auto-generated).
3016        :type data: dict | None
3017        :param sync: Block until operation is synchronized to disk.
3018        :type sync: bool | None
3019        :param silent: If set to True, no document metadata is returned. This
3020            can be used to save resources.
3021        :type silent: bool
3022        :param return_new: Include body of the new document in the returned
3023            metadata. Ignored if parameter **silent** is set to True.
3024        :type return_new: bool
3025        :return: Document metadata (e.g. document key, revision) or True if
3026            parameter **silent** was set to True.
3027        :rtype: bool | dict
3028        :raise arango.exceptions.DocumentInsertError: If insert fails.
3029        """
3030        edge = {"_from": get_doc_id(from_vertex), "_to": get_doc_id(to_vertex)}
3031        if data is not None:
3032            edge.update(self._ensure_key_from_id(data))
3033        return self.insert(edge, sync=sync, silent=silent, return_new=return_new)
3034
3035    def edges(
3036        self, vertex: Union[str, Json], direction: Optional[str] = None
3037    ) -> Result[Json]:
3038        """Return the edge documents coming in and/or out of the vertex.
3039
3040        :param vertex: Vertex document ID or body with "_id" field.
3041        :type vertex: str | dict
3042        :param direction: The direction of the edges. Allowed values are "in"
3043            and "out". If not set, edges in both directions are returned.
3044        :type direction: str
3045        :return: List of edges and statistics.
3046        :rtype: dict
3047        :raise arango.exceptions.EdgeListError: If retrieval fails.
3048        """
3049        params: Params = {"vertex": get_doc_id(vertex)}
3050        if direction is not None:
3051            params["direction"] = direction
3052
3053        request = Request(
3054            method="get",
3055            endpoint=f"/_api/edges/{self.name}",
3056            params=params,
3057            read=self.name,
3058        )
3059
3060        def response_handler(resp: Response) -> Json:
3061            if not resp.is_success:
3062                raise EdgeListError(resp, request)
3063            stats = resp.body["stats"]
3064            return {
3065                "edges": resp.body["edges"],
3066                "stats": {
3067                    "filtered": stats["filtered"],
3068                    "scanned_index": stats["scannedIndex"],
3069                },
3070            }
3071
3072        return self._execute(request, response_handler)
3073