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