1__all__ = ["StandardDatabase", "AsyncDatabase", "BatchDatabase", "TransactionDatabase"] 2 3from datetime import datetime 4from numbers import Number 5from typing import Any, List, Optional, Sequence, Union 6 7from arango.api import ApiGroup 8from arango.aql import AQL 9from arango.backup import Backup 10from arango.cluster import Cluster 11from arango.collection import StandardCollection 12from arango.connection import Connection 13from arango.exceptions import ( 14 AnalyzerCreateError, 15 AnalyzerDeleteError, 16 AnalyzerGetError, 17 AnalyzerListError, 18 AsyncJobClearError, 19 AsyncJobListError, 20 CollectionCreateError, 21 CollectionDeleteError, 22 CollectionListError, 23 DatabaseCreateError, 24 DatabaseDeleteError, 25 DatabaseListError, 26 DatabasePropertiesError, 27 GraphCreateError, 28 GraphDeleteError, 29 GraphListError, 30 JWTSecretListError, 31 JWTSecretReloadError, 32 PermissionGetError, 33 PermissionListError, 34 PermissionResetError, 35 PermissionUpdateError, 36 ServerDetailsError, 37 ServerEchoError, 38 ServerEncryptionError, 39 ServerEngineError, 40 ServerLogLevelError, 41 ServerLogLevelSetError, 42 ServerMetricsError, 43 ServerReadLogError, 44 ServerReloadRoutingError, 45 ServerRequiredDBVersionError, 46 ServerRoleError, 47 ServerRunTestsError, 48 ServerShutdownError, 49 ServerStatisticsError, 50 ServerStatusError, 51 ServerTimeError, 52 ServerTLSError, 53 ServerTLSReloadError, 54 ServerVersionError, 55 TaskCreateError, 56 TaskDeleteError, 57 TaskGetError, 58 TaskListError, 59 TransactionExecuteError, 60 UserCreateError, 61 UserDeleteError, 62 UserGetError, 63 UserListError, 64 UserReplaceError, 65 UserUpdateError, 66 ViewCreateError, 67 ViewDeleteError, 68 ViewGetError, 69 ViewListError, 70 ViewRenameError, 71 ViewReplaceError, 72 ViewUpdateError, 73) 74from arango.executor import ( 75 AsyncApiExecutor, 76 BatchApiExecutor, 77 DefaultApiExecutor, 78 TransactionApiExecutor, 79) 80from arango.formatter import ( 81 format_body, 82 format_database, 83 format_server_status, 84 format_tls, 85 format_view, 86) 87from arango.foxx import Foxx 88from arango.graph import Graph 89from arango.job import BatchJob 90from arango.pregel import Pregel 91from arango.replication import Replication 92from arango.request import Request 93from arango.response import Response 94from arango.result import Result 95from arango.typings import Json, Jsons, Params 96from arango.utils import get_col_name 97from arango.wal import WAL 98 99 100class Database(ApiGroup): 101 """Base class for Database API wrappers.""" 102 103 def __getitem__(self, name: str) -> StandardCollection: 104 """Return the collection API wrapper. 105 106 :param name: Collection name. 107 :type name: str 108 :return: Collection API wrapper. 109 :rtype: arango.collection.StandardCollection 110 """ 111 return self.collection(name) 112 113 def _get_col_by_doc(self, document: Union[str, Json]) -> StandardCollection: 114 """Return the collection of the given document. 115 116 :param document: Document ID or body with "_id" field. 117 :type document: str | dict 118 :return: Collection API wrapper. 119 :rtype: arango.collection.StandardCollection 120 :raise arango.exceptions.DocumentParseError: On malformed document. 121 """ 122 return self.collection(get_col_name(document)) 123 124 @property 125 def name(self) -> str: 126 """Return database name. 127 128 :return: Database name. 129 :rtype: str 130 """ 131 return self.db_name 132 133 @property 134 def aql(self) -> AQL: 135 """Return AQL (ArangoDB Query Language) API wrapper. 136 137 :return: AQL API wrapper. 138 :rtype: arango.aql.AQL 139 """ 140 return AQL(self._conn, self._executor) 141 142 @property 143 def wal(self) -> WAL: 144 """Return WAL (Write-Ahead Log) API wrapper. 145 146 :return: WAL API wrapper. 147 :rtype: arango.wal.WAL 148 """ 149 return WAL(self._conn, self._executor) 150 151 @property 152 def foxx(self) -> Foxx: 153 """Return Foxx API wrapper. 154 155 :return: Foxx API wrapper. 156 :rtype: arango.foxx.Foxx 157 """ 158 return Foxx(self._conn, self._executor) 159 160 @property 161 def pregel(self) -> Pregel: 162 """Return Pregel API wrapper. 163 164 :return: Pregel API wrapper. 165 :rtype: arango.pregel.Pregel 166 """ 167 return Pregel(self._conn, self._executor) 168 169 @property 170 def replication(self) -> Replication: 171 """Return Replication API wrapper. 172 173 :return: Replication API wrapper. 174 :rtype: arango.replication.Replication 175 """ 176 return Replication(self._conn, self._executor) 177 178 @property 179 def cluster(self) -> Cluster: # pragma: no cover 180 """Return Cluster API wrapper. 181 182 :return: Cluster API wrapper. 183 :rtype: arango.cluster.Cluster 184 """ 185 return Cluster(self._conn, self._executor) 186 187 @property 188 def backup(self) -> Backup: 189 """Return Backup API wrapper. 190 191 :return: Backup API wrapper. 192 :rtype: arango.backup.Backup 193 """ 194 return Backup(self._conn, self._executor) 195 196 def properties(self) -> Result[Json]: 197 """Return database properties. 198 199 :return: Database properties. 200 :rtype: dict 201 :raise arango.exceptions.DatabasePropertiesError: If retrieval fails. 202 """ 203 request = Request( 204 method="get", 205 endpoint="/_api/database/current", 206 ) 207 208 def response_handler(resp: Response) -> Json: 209 if not resp.is_success: 210 raise DatabasePropertiesError(resp, request) 211 return format_database(resp.body["result"]) 212 213 return self._execute(request, response_handler) 214 215 def execute_transaction( 216 self, 217 command: str, 218 params: Optional[Json] = None, 219 read: Optional[Sequence[str]] = None, 220 write: Optional[Sequence[str]] = None, 221 sync: Optional[bool] = None, 222 timeout: Optional[Number] = None, 223 max_size: Optional[int] = None, 224 allow_implicit: Optional[bool] = None, 225 intermediate_commit_count: Optional[int] = None, 226 intermediate_commit_size: Optional[int] = None, 227 ) -> Result[Any]: 228 """Execute raw Javascript command in transaction. 229 230 :param command: Javascript command to execute. 231 :type command: str 232 :param read: Names of collections read during transaction. If parameter 233 **allow_implicit** is set to True, any undeclared read collections 234 are loaded lazily. 235 :type read: [str] | None 236 :param write: Names of collections written to during transaction. 237 Transaction fails on undeclared write collections. 238 :type write: [str] | None 239 :param params: Optional parameters passed into the Javascript command. 240 :type params: dict | None 241 :param sync: Block until operation is synchronized to disk. 242 :type sync: bool | None 243 :param timeout: Timeout for waiting on collection locks. If set to 0, 244 ArangoDB server waits indefinitely. If not set, system default 245 value is used. 246 :type timeout: int | None 247 :param max_size: Max transaction size limit in bytes. 248 :type max_size: int | None 249 :param allow_implicit: If set to True, undeclared read collections are 250 loaded lazily. If set to False, transaction fails on any undeclared 251 collections. 252 :type allow_implicit: bool | None 253 :param intermediate_commit_count: Max number of operations after which 254 an intermediate commit is performed automatically. 255 :type intermediate_commit_count: int | None 256 :param intermediate_commit_size: Max size of operations in bytes after 257 which an intermediate commit is performed automatically. 258 :type intermediate_commit_size: int | None 259 :return: Return value of **command**. 260 :rtype: Any 261 :raise arango.exceptions.TransactionExecuteError: If execution fails. 262 """ 263 collections: Json = {"allowImplicit": allow_implicit} 264 if read is not None: 265 collections["read"] = read 266 if write is not None: 267 collections["write"] = write 268 269 data: Json = {"action": command} 270 if collections: 271 data["collections"] = collections 272 if params is not None: 273 data["params"] = params 274 if timeout is not None: 275 data["lockTimeout"] = timeout 276 if sync is not None: 277 data["waitForSync"] = sync 278 if max_size is not None: 279 data["maxTransactionSize"] = max_size 280 if intermediate_commit_count is not None: 281 data["intermediateCommitCount"] = intermediate_commit_count 282 if intermediate_commit_size is not None: 283 data["intermediateCommitSize"] = intermediate_commit_size 284 285 request = Request(method="post", endpoint="/_api/transaction", data=data) 286 287 def response_handler(resp: Response) -> Any: 288 if not resp.is_success: 289 raise TransactionExecuteError(resp, request) 290 291 return resp.body.get("result") 292 293 return self._execute(request, response_handler) 294 295 def version(self) -> Result[str]: 296 """Return ArangoDB server version. 297 298 :return: Server version. 299 :rtype: str 300 :raise arango.exceptions.ServerVersionError: If retrieval fails. 301 """ 302 request = Request( 303 method="get", endpoint="/_api/version", params={"details": False} 304 ) 305 306 def response_handler(resp: Response) -> str: 307 if not resp.is_success: 308 raise ServerVersionError(resp, request) 309 return str(resp.body["version"]) 310 311 return self._execute(request, response_handler) 312 313 def details(self) -> Result[Json]: 314 """Return ArangoDB server details. 315 316 :return: Server details. 317 :rtype: dict 318 :raise arango.exceptions.ServerDetailsError: If retrieval fails. 319 """ 320 request = Request( 321 method="get", endpoint="/_api/version", params={"details": True} 322 ) 323 324 def response_handler(resp: Response) -> Json: 325 if resp.is_success: 326 result: Json = resp.body["details"] 327 return result 328 raise ServerDetailsError(resp, request) 329 330 return self._execute(request, response_handler) 331 332 def status(self) -> Result[Json]: 333 """Return ArangoDB server status. 334 335 :return: Server status. 336 :rtype: dict 337 :raise arango.exceptions.ServerStatusError: If retrieval fails. 338 """ 339 request = Request( 340 method="get", 341 endpoint="/_admin/status", 342 ) 343 344 def response_handler(resp: Response) -> Json: 345 if not resp.is_success: 346 raise ServerStatusError(resp, request) 347 return format_server_status(resp.body) 348 349 return self._execute(request, response_handler) 350 351 def required_db_version(self) -> Result[str]: 352 """Return required version of target database. 353 354 :return: Required version of target database. 355 :rtype: str 356 :raise arango.exceptions.ServerRequiredDBVersionError: If retrieval fails. 357 """ 358 request = Request(method="get", endpoint="/_admin/database/target-version") 359 360 def response_handler(resp: Response) -> str: 361 if resp.is_success: 362 return str(resp.body["version"]) 363 raise ServerRequiredDBVersionError(resp, request) 364 365 return self._execute(request, response_handler) 366 367 def engine(self) -> Result[Json]: 368 """Return the database engine details. 369 370 :return: Database engine details. 371 :rtype: dict 372 :raise arango.exceptions.ServerEngineError: If retrieval fails. 373 """ 374 request = Request(method="get", endpoint="/_api/engine") 375 376 def response_handler(resp: Response) -> Json: 377 if resp.is_success: 378 return format_body(resp.body) 379 raise ServerEngineError(resp, request) 380 381 return self._execute(request, response_handler) 382 383 def statistics(self, description: bool = False) -> Result[Json]: 384 """Return server statistics. 385 386 :return: Server statistics. 387 :rtype: dict 388 :raise arango.exceptions.ServerStatisticsError: If retrieval fails. 389 """ 390 if description: 391 endpoint = "/_admin/statistics-description" 392 else: 393 endpoint = "/_admin/statistics" 394 395 request = Request(method="get", endpoint=endpoint) 396 397 def response_handler(resp: Response) -> Json: 398 if resp.is_success: 399 return format_body(resp.body) 400 raise ServerStatisticsError(resp, request) 401 402 return self._execute(request, response_handler) 403 404 def role(self) -> Result[str]: 405 """Return server role. 406 407 :return: Server role. Possible values are "SINGLE" (server which is not 408 in a cluster), "COORDINATOR" (cluster coordinator), "PRIMARY", 409 "SECONDARY", "AGENT" (Agency node in a cluster) or "UNDEFINED". 410 :rtype: str 411 :raise arango.exceptions.ServerRoleError: If retrieval fails. 412 """ 413 request = Request(method="get", endpoint="/_admin/server/role") 414 415 def response_handler(resp: Response) -> str: 416 if resp.is_success: 417 return str(resp.body["role"]) 418 raise ServerRoleError(resp, request) 419 420 return self._execute(request, response_handler) 421 422 def time(self) -> Result[datetime]: 423 """Return server system time. 424 425 :return: Server system time. 426 :rtype: datetime.datetime 427 :raise arango.exceptions.ServerTimeError: If retrieval fails. 428 """ 429 request = Request(method="get", endpoint="/_admin/time") 430 431 def response_handler(resp: Response) -> datetime: 432 if not resp.is_success: 433 raise ServerTimeError(resp, request) 434 return datetime.fromtimestamp(resp.body["time"]) 435 436 return self._execute(request, response_handler) 437 438 def echo(self) -> Result[Json]: 439 """Return details of the last request (e.g. headers, payload). 440 441 :return: Details of the last request. 442 :rtype: dict 443 :raise arango.exceptions.ServerEchoError: If retrieval fails. 444 """ 445 request = Request(method="get", endpoint="/_admin/echo") 446 447 def response_handler(resp: Response) -> Json: 448 if not resp.is_success: 449 raise ServerEchoError(resp, request) 450 result: Json = resp.body 451 return result 452 453 return self._execute(request, response_handler) 454 455 def shutdown(self) -> Result[bool]: # pragma: no cover 456 """Initiate server shutdown sequence. 457 458 :return: True if the server was shutdown successfully. 459 :rtype: bool 460 :raise arango.exceptions.ServerShutdownError: If shutdown fails. 461 """ 462 request = Request(method="delete", endpoint="/_admin/shutdown") 463 464 def response_handler(resp: Response) -> bool: 465 if not resp.is_success: 466 raise ServerShutdownError(resp, request) 467 return True 468 469 return self._execute(request, response_handler) 470 471 def run_tests(self, tests: Sequence[str]) -> Result[Json]: # pragma: no cover 472 """Run available unittests on the server. 473 474 :param tests: List of files containing the test suites. 475 :type tests: [str] 476 :return: Test results. 477 :rtype: dict 478 :raise arango.exceptions.ServerRunTestsError: If execution fails. 479 """ 480 request = Request(method="post", endpoint="/_admin/test", data={"tests": tests}) 481 482 def response_handler(resp: Response) -> Json: 483 if not resp.is_success: 484 raise ServerRunTestsError(resp, request) 485 result: Json = resp.body 486 return result 487 488 return self._execute(request, response_handler) 489 490 def read_log( 491 self, 492 upto: Optional[Union[int, str]] = None, 493 level: Optional[Union[int, str]] = None, 494 start: Optional[int] = None, 495 size: Optional[int] = None, 496 offset: Optional[int] = None, 497 search: Optional[str] = None, 498 sort: Optional[str] = None, 499 ) -> Result[Json]: 500 """Read the global log from server. 501 502 :param upto: Return the log entries up to the given level (mutually 503 exclusive with parameter **level**). Allowed values are "fatal", 504 "error", "warning", "info" (default) and "debug". 505 :type upto: int | str 506 :param level: Return the log entries of only the given level (mutually 507 exclusive with **upto**). Allowed values are "fatal", "error", 508 "warning", "info" (default) and "debug". 509 :type level: int | str 510 :param start: Return the log entries whose ID is greater or equal to 511 the given value. 512 :type start: int 513 :param size: Restrict the size of the result to the given value. This 514 can be used for pagination. 515 :type size: int 516 :param offset: Number of entries to skip (e.g. for pagination). 517 :type offset: int 518 :param search: Return only the log entries containing the given text. 519 :type search: str 520 :param sort: Sort the log entries according to the given fashion, which 521 can be "sort" or "desc". 522 :type sort: str 523 :return: Server log entries. 524 :rtype: dict 525 :raise arango.exceptions.ServerReadLogError: If read fails. 526 """ 527 params = dict() 528 if upto is not None: 529 params["upto"] = upto 530 if level is not None: 531 params["level"] = level 532 if start is not None: 533 params["start"] = start 534 if size is not None: 535 params["size"] = size 536 if offset is not None: 537 params["offset"] = offset 538 if search is not None: 539 params["search"] = search 540 if sort is not None: 541 params["sort"] = sort 542 543 request = Request(method="get", endpoint="/_admin/log", params=params) 544 545 def response_handler(resp: Response) -> Json: 546 if not resp.is_success: 547 raise ServerReadLogError(resp, request) 548 549 result: Json = resp.body 550 if "totalAmount" in result: 551 resp.body["total_amount"] = resp.body.pop("totalAmount") 552 return result 553 554 return self._execute(request, response_handler) 555 556 def log_levels(self) -> Result[Json]: 557 """Return current logging levels. 558 559 :return: Current logging levels. 560 :rtype: dict 561 """ 562 request = Request(method="get", endpoint="/_admin/log/level") 563 564 def response_handler(resp: Response) -> Json: 565 if not resp.is_success: 566 raise ServerLogLevelError(resp, request) 567 result: Json = resp.body 568 return result 569 570 return self._execute(request, response_handler) 571 572 def set_log_levels(self, **kwargs: str) -> Result[Json]: 573 """Set the logging levels. 574 575 This method takes arbitrary keyword arguments where the keys are the 576 logger names and the values are the logging levels. For example: 577 578 .. code-block:: python 579 580 arango.set_log_levels( 581 agency='DEBUG', 582 collector='INFO', 583 threads='WARNING' 584 ) 585 586 Keys that are not valid logger names are ignored. 587 588 :return: New logging levels. 589 :rtype: dict 590 """ 591 request = Request(method="put", endpoint="/_admin/log/level", data=kwargs) 592 593 def response_handler(resp: Response) -> Json: 594 if not resp.is_success: 595 raise ServerLogLevelSetError(resp, request) 596 result: Json = resp.body 597 return result 598 599 return self._execute(request, response_handler) 600 601 def reload_routing(self) -> Result[bool]: 602 """Reload the routing information. 603 604 :return: True if routing was reloaded successfully. 605 :rtype: bool 606 :raise arango.exceptions.ServerReloadRoutingError: If reload fails. 607 """ 608 request = Request(method="post", endpoint="/_admin/routing/reload") 609 610 def response_handler(resp: Response) -> bool: 611 if not resp.is_success: 612 raise ServerReloadRoutingError(resp, request) 613 return True 614 615 return self._execute(request, response_handler) 616 617 def metrics(self) -> Result[str]: 618 """Return server metrics in Prometheus format. 619 620 :return: Server metrics in Prometheus format. 621 :rtype: str 622 """ 623 request = Request(method="get", endpoint="/_admin/metrics") 624 625 def response_handler(resp: Response) -> str: 626 if resp.is_success: 627 return resp.raw_body 628 raise ServerMetricsError(resp, request) 629 630 return self._execute(request, response_handler) 631 632 def jwt_secrets(self) -> Result[Json]: # pragma: no cover 633 """Return information on currently loaded JWT secrets. 634 635 :return: Information on currently loaded JWT secrets. 636 :rtype: dict 637 """ 638 request = Request(method="get", endpoint="/_admin/server/jwt") 639 640 def response_handler(resp: Response) -> Json: 641 if not resp.is_success: 642 raise JWTSecretListError(resp, request) 643 result: Json = resp.body["result"] 644 return result 645 646 return self._execute(request, response_handler) 647 648 def reload_jwt_secrets(self) -> Result[Json]: # pragma: no cover 649 """Hot-reload JWT secrets. 650 651 Calling this without payload reloads JWT secrets from disk. Only files 652 specified via arangod startup option ``--server.jwt-secret-keyfile`` or 653 ``--server.jwt-secret-folder`` are used. It is not possible to change 654 the location where files are loaded from without restarting the server. 655 656 :return: Information on reloaded JWT secrets. 657 :rtype: dict 658 """ 659 request = Request(method="post", endpoint="/_admin/server/jwt") 660 661 def response_handler(resp: Response) -> Json: 662 if not resp.is_success: 663 raise JWTSecretReloadError(resp, request) 664 result: Json = resp.body["result"] 665 return result 666 667 return self._execute(request, response_handler) 668 669 def tls(self) -> Result[Json]: 670 """Return TLS data (server key, client-auth CA). 671 672 :return: TLS data. 673 :rtype: dict 674 """ 675 request = Request(method="get", endpoint="/_admin/server/tls") 676 677 def response_handler(resp: Response) -> Json: 678 if not resp.is_success: 679 raise ServerTLSError(resp, request) 680 return format_tls(resp.body["result"]) 681 682 return self._execute(request, response_handler) 683 684 def reload_tls(self) -> Result[Json]: 685 """Reload TLS data (server key, client-auth CA). 686 687 :return: New TLS data. 688 :rtype: dict 689 """ 690 request = Request(method="post", endpoint="/_admin/server/tls") 691 692 def response_handler(resp: Response) -> Json: 693 if not resp.is_success: 694 raise ServerTLSReloadError(resp, request) 695 return format_tls(resp.body["result"]) 696 697 return self._execute(request, response_handler) 698 699 def encryption(self) -> Result[Json]: 700 """Rotate the user-supplied keys for encryption. 701 702 This method is available only for enterprise edition of ArangoDB. 703 704 :return: New TLS data. 705 :rtype: dict 706 :raise arango.exceptions.ServerEncryptionError: If retrieval fails. 707 """ 708 request = Request(method="post", endpoint="/_admin/server/encryption") 709 710 def response_handler(resp: Response) -> Json: 711 if resp.is_success: # pragma: no cover 712 result: Json = resp.body["result"] 713 return result 714 raise ServerEncryptionError(resp, request) 715 716 return self._execute(request, response_handler) 717 718 ####################### 719 # Database Management # 720 ####################### 721 722 def databases(self) -> Result[List[str]]: 723 """Return the names all databases. 724 725 :return: Database names. 726 :rtype: [str] 727 :raise arango.exceptions.DatabaseListError: If retrieval fails. 728 """ 729 request = Request(method="get", endpoint="/_api/database") 730 731 def response_handler(resp: Response) -> List[str]: 732 if not resp.is_success: 733 raise DatabaseListError(resp, request) 734 result: List[str] = resp.body["result"] 735 return result 736 737 return self._execute(request, response_handler) 738 739 def has_database(self, name: str) -> Result[bool]: 740 """Check if a database exists. 741 742 :param name: Database name. 743 :type name: str 744 :return: True if database exists, False otherwise. 745 :rtype: bool 746 """ 747 request = Request(method="get", endpoint="/_api/database") 748 749 def response_handler(resp: Response) -> bool: 750 if not resp.is_success: 751 raise DatabaseListError(resp, request) 752 return name in resp.body["result"] 753 754 return self._execute(request, response_handler) 755 756 def create_database( 757 self, 758 name: str, 759 users: Optional[Sequence[Json]] = None, 760 replication_factor: Union[int, str, None] = None, 761 write_concern: Optional[int] = None, 762 sharding: Optional[str] = None, 763 ) -> Result[bool]: 764 """Create a new database. 765 766 :param name: Database name. 767 :type name: str 768 :param users: List of users with access to the new database, where each 769 user is a dictionary with fields "username", "password", "active" 770 and "extra" (see below for example). If not set, only the admin and 771 current user are granted access. 772 :type users: [dict] 773 :param replication_factor: Default replication factor for collections 774 created in this database. Special values include "satellite" which 775 replicates the collection to every DBServer, and 1 which disables 776 replication. Used for clusters only. 777 :type replication_factor: int | str 778 :param write_concern: Default write concern for collections created in 779 this database. Determines how many copies of each shard are 780 required to be in sync on different DBServers. If there are less 781 than these many copies in the cluster a shard will refuse to write. 782 Writes to shards with enough up-to-date copies will succeed at the 783 same time, however. Value of this parameter can not be larger than 784 the value of **replication_factor**. Used for clusters only. 785 :type write_concern: int 786 :param sharding: Sharding method used for new collections in this 787 database. Allowed values are: "", "flexible" and "single". The 788 first two are equivalent. Used for clusters only. 789 :type sharding: str 790 :return: True if database was created successfully. 791 :rtype: bool 792 :raise arango.exceptions.DatabaseCreateError: If create fails. 793 794 Here is an example entry for parameter **users**: 795 796 .. code-block:: python 797 798 { 799 'username': 'john', 800 'password': 'password', 801 'active': True, 802 'extra': {'Department': 'IT'} 803 } 804 """ 805 data: Json = {"name": name} 806 807 options: Json = {} 808 if replication_factor is not None: 809 options["replicationFactor"] = replication_factor 810 if write_concern is not None: 811 options["writeConcern"] = write_concern 812 if sharding is not None: 813 options["sharding"] = sharding 814 if options: 815 data["options"] = options 816 817 if users is not None: 818 data["users"] = [ 819 { 820 "username": user["username"], 821 "passwd": user["password"], 822 "active": user.get("active", True), 823 "extra": user.get("extra", {}), 824 } 825 for user in users 826 ] 827 828 request = Request(method="post", endpoint="/_api/database", data=data) 829 830 def response_handler(resp: Response) -> bool: 831 if not resp.is_success: 832 raise DatabaseCreateError(resp, request) 833 return True 834 835 return self._execute(request, response_handler) 836 837 def delete_database(self, name: str, ignore_missing: bool = False) -> Result[bool]: 838 """Delete the database. 839 840 :param name: Database name. 841 :type name: str 842 :param ignore_missing: Do not raise an exception on missing database. 843 :type ignore_missing: bool 844 :return: True if database was deleted successfully, False if database 845 was not found and **ignore_missing** was set to True. 846 :rtype: bool 847 :raise arango.exceptions.DatabaseDeleteError: If delete fails. 848 """ 849 request = Request(method="delete", endpoint=f"/_api/database/{name}") 850 851 def response_handler(resp: Response) -> bool: 852 if resp.error_code == 1228 and ignore_missing: 853 return False 854 if not resp.is_success: 855 raise DatabaseDeleteError(resp, request) 856 return True 857 858 return self._execute(request, response_handler) 859 860 ######################### 861 # Collection Management # 862 ######################### 863 864 def collection(self, name: str) -> StandardCollection: 865 """Return the standard collection API wrapper. 866 867 :param name: Collection name. 868 :type name: str 869 :return: Standard collection API wrapper. 870 :rtype: arango.collection.StandardCollection 871 """ 872 return StandardCollection(self._conn, self._executor, name) 873 874 def has_collection(self, name: str) -> Result[bool]: 875 """Check if collection exists in the database. 876 877 :param name: Collection name. 878 :type name: str 879 :return: True if collection exists, False otherwise. 880 :rtype: bool 881 """ 882 request = Request(method="get", endpoint="/_api/collection") 883 884 def response_handler(resp: Response) -> bool: 885 if not resp.is_success: 886 raise CollectionListError(resp, request) 887 return any(col["name"] == name for col in resp.body["result"]) 888 889 return self._execute(request, response_handler) 890 891 def collections(self) -> Result[Jsons]: 892 """Return the collections in the database. 893 894 :return: Collections in the database and their details. 895 :rtype: [dict] 896 :raise arango.exceptions.CollectionListError: If retrieval fails. 897 """ 898 request = Request(method="get", endpoint="/_api/collection") 899 900 def response_handler(resp: Response) -> Jsons: 901 if not resp.is_success: 902 raise CollectionListError(resp, request) 903 return [ 904 { 905 "id": col["id"], 906 "name": col["name"], 907 "system": col["isSystem"], 908 "type": StandardCollection.types[col["type"]], 909 "status": StandardCollection.statuses[col["status"]], 910 } 911 for col in resp.body["result"] 912 ] 913 914 return self._execute(request, response_handler) 915 916 def create_collection( 917 self, 918 name: str, 919 sync: bool = False, 920 system: bool = False, 921 edge: bool = False, 922 user_keys: bool = True, 923 key_increment: Optional[int] = None, 924 key_offset: Optional[int] = None, 925 key_generator: str = "traditional", 926 shard_fields: Optional[Sequence[str]] = None, 927 shard_count: Optional[int] = None, 928 replication_factor: Optional[int] = None, 929 shard_like: Optional[str] = None, 930 sync_replication: Optional[bool] = None, 931 enforce_replication_factor: Optional[bool] = None, 932 sharding_strategy: Optional[str] = None, 933 smart_join_attribute: Optional[str] = None, 934 write_concern: Optional[int] = None, 935 schema: Optional[Json] = None, 936 ) -> Result[StandardCollection]: 937 """Create a new collection. 938 939 :param name: Collection name. 940 :type name: str 941 :param sync: If set to True, document operations via the collection 942 will block until synchronized to disk by default. 943 :type sync: bool | None 944 :param system: If set to True, a system collection is created. The 945 collection name must have leading underscore "_" character. 946 :type system: bool 947 :param edge: If set to True, an edge collection is created. 948 :type edge: bool 949 :param key_generator: Used for generating document keys. Allowed values 950 are "traditional" or "autoincrement". 951 :type key_generator: str 952 :param user_keys: If set to True, users are allowed to supply document 953 keys. If set to False, the key generator is solely responsible for 954 supplying the key values. 955 :type user_keys: bool 956 :param key_increment: Key increment value. Applies only when value of 957 **key_generator** is set to "autoincrement". 958 :type key_increment: int 959 :param key_offset: Key offset value. Applies only when value of 960 **key_generator** is set to "autoincrement". 961 :type key_offset: int 962 :param shard_fields: Field(s) used to determine the target shard. 963 :type shard_fields: [str] 964 :param shard_count: Number of shards to create. 965 :type shard_count: int 966 :param replication_factor: Number of copies of each shard on different 967 servers in a cluster. Allowed values are 1 (only one copy is kept 968 and no synchronous replication), and n (n-1 replicas are kept and 969 any two copies are replicated across servers synchronously, meaning 970 every write to the master is copied to all slaves before operation 971 is reported successful). 972 :type replication_factor: int 973 :param shard_like: Name of prototype collection whose sharding 974 specifics are imitated. Prototype collections cannot be dropped 975 before imitating collections. Applies to enterprise version of 976 ArangoDB only. 977 :type shard_like: str 978 :param sync_replication: If set to True, server reports success only 979 when collection is created in all replicas. You can set this to 980 False for faster server response, and if full replication is not a 981 concern. 982 :type sync_replication: bool 983 :param enforce_replication_factor: Check if there are enough replicas 984 available at creation time, or halt the operation. 985 :type enforce_replication_factor: bool 986 :param sharding_strategy: Sharding strategy. Available for ArangoDB 987 version and up only. Possible values are "community-compat", 988 "enterprise-compat", "enterprise-smart-edge-compat", "hash" and 989 "enterprise-hash-smart-edge". Refer to ArangoDB documentation for 990 more details on each value. 991 :type sharding_strategy: str 992 :param smart_join_attribute: Attribute of the collection which must 993 contain the shard key value of the smart join collection. The shard 994 key for the documents must contain the value of this attribute, 995 followed by a colon ":" and the primary key of the document. 996 Requires parameter **shard_like** to be set to the name of another 997 collection, and parameter **shard_fields** to be set to a single 998 shard key attribute, with another colon ":" at the end. Available 999 only for enterprise version of ArangoDB. 1000 :type smart_join_attribute: str 1001 :param write_concern: Write concern for the collection. Determines how 1002 many copies of each shard are required to be in sync on different 1003 DBServers. If there are less than these many copies in the cluster 1004 a shard will refuse to write. Writes to shards with enough 1005 up-to-date copies will succeed at the same time. The value of this 1006 parameter cannot be larger than that of **replication_factor**. 1007 Default value is 1. Used for clusters only. 1008 :type write_concern: int 1009 :param schema: Optional dict specifying the collection level schema 1010 for documents. See ArangoDB documentation for more information on 1011 document schema validation. 1012 :type schema: dict 1013 :return: Standard collection API wrapper. 1014 :rtype: arango.collection.StandardCollection 1015 :raise arango.exceptions.CollectionCreateError: If create fails. 1016 """ 1017 key_options: Json = {"type": key_generator, "allowUserKeys": user_keys} 1018 if key_increment is not None: 1019 key_options["increment"] = key_increment 1020 if key_offset is not None: 1021 key_options["offset"] = key_offset 1022 1023 data: Json = { 1024 "name": name, 1025 "waitForSync": sync, 1026 "isSystem": system, 1027 "keyOptions": key_options, 1028 "type": 3 if edge else 2, 1029 } 1030 if shard_count is not None: 1031 data["numberOfShards"] = shard_count 1032 if shard_fields is not None: 1033 data["shardKeys"] = shard_fields 1034 if replication_factor is not None: 1035 data["replicationFactor"] = replication_factor 1036 if shard_like is not None: 1037 data["distributeShardsLike"] = shard_like 1038 if sharding_strategy is not None: 1039 data["shardingStrategy"] = sharding_strategy 1040 if smart_join_attribute is not None: 1041 data["smartJoinAttribute"] = smart_join_attribute 1042 if write_concern is not None: 1043 data["writeConcern"] = write_concern 1044 if schema is not None: 1045 data["schema"] = schema 1046 1047 params: Params = {} 1048 if sync_replication is not None: 1049 params["waitForSyncReplication"] = sync_replication 1050 if enforce_replication_factor is not None: 1051 params["enforceReplicationFactor"] = enforce_replication_factor 1052 1053 request = Request( 1054 method="post", endpoint="/_api/collection", params=params, data=data 1055 ) 1056 1057 def response_handler(resp: Response) -> StandardCollection: 1058 if resp.is_success: 1059 return self.collection(name) 1060 raise CollectionCreateError(resp, request) 1061 1062 return self._execute(request, response_handler) 1063 1064 def delete_collection( 1065 self, name: str, ignore_missing: bool = False, system: Optional[bool] = None 1066 ) -> Result[bool]: 1067 """Delete the collection. 1068 1069 :param name: Collection name. 1070 :type name: str 1071 :param ignore_missing: Do not raise an exception on missing collection. 1072 :type ignore_missing: bool 1073 :param system: Whether the collection is a system collection. 1074 :type system: bool 1075 :return: True if collection was deleted successfully, False if 1076 collection was not found and **ignore_missing** was set to True. 1077 :rtype: bool 1078 :raise arango.exceptions.CollectionDeleteError: If delete fails. 1079 """ 1080 params: Params = {} 1081 if system is not None: 1082 params["isSystem"] = system 1083 1084 request = Request( 1085 method="delete", endpoint=f"/_api/collection/{name}", params=params 1086 ) 1087 1088 def response_handler(resp: Response) -> bool: 1089 if resp.error_code == 1203 and ignore_missing: 1090 return False 1091 if not resp.is_success: 1092 raise CollectionDeleteError(resp, request) 1093 return True 1094 1095 return self._execute(request, response_handler) 1096 1097 #################### 1098 # Graph Management # 1099 #################### 1100 1101 def graph(self, name: str) -> Graph: 1102 """Return the graph API wrapper. 1103 1104 :param name: Graph name. 1105 :type name: str 1106 :return: Graph API wrapper. 1107 :rtype: arango.graph.Graph 1108 """ 1109 return Graph(self._conn, self._executor, name) 1110 1111 def has_graph(self, name: str) -> Result[bool]: 1112 """Check if a graph exists in the database. 1113 1114 :param name: Graph name. 1115 :type name: str 1116 :return: True if graph exists, False otherwise. 1117 :rtype: bool 1118 """ 1119 request = Request(method="get", endpoint="/_api/gharial") 1120 1121 def response_handler(resp: Response) -> bool: 1122 if not resp.is_success: 1123 raise GraphListError(resp, request) 1124 return any(name == graph["_key"] for graph in resp.body["graphs"]) 1125 1126 return self._execute(request, response_handler) 1127 1128 def graphs(self) -> Result[Jsons]: 1129 """List all graphs in the database. 1130 1131 :return: Graphs in the database. 1132 :rtype: [dict] 1133 :raise arango.exceptions.GraphListError: If retrieval fails. 1134 """ 1135 request = Request(method="get", endpoint="/_api/gharial") 1136 1137 def response_handler(resp: Response) -> Jsons: 1138 if not resp.is_success: 1139 raise GraphListError(resp, request) 1140 return [ 1141 { 1142 "id": body["_id"], 1143 "name": body["_key"], 1144 "revision": body["_rev"], 1145 "orphan_collections": body["orphanCollections"], 1146 "edge_definitions": [ 1147 { 1148 "edge_collection": definition["collection"], 1149 "from_vertex_collections": definition["from"], 1150 "to_vertex_collections": definition["to"], 1151 } 1152 for definition in body["edgeDefinitions"] 1153 ], 1154 "shard_count": body.get("numberOfShards"), 1155 "replication_factor": body.get("replicationFactor"), 1156 } 1157 for body in resp.body["graphs"] 1158 ] 1159 1160 return self._execute(request, response_handler) 1161 1162 def create_graph( 1163 self, 1164 name: str, 1165 edge_definitions: Optional[Sequence[Json]] = None, 1166 orphan_collections: Optional[Sequence[str]] = None, 1167 smart: Optional[bool] = None, 1168 smart_field: Optional[str] = None, 1169 shard_count: Optional[int] = None, 1170 ) -> Result[Graph]: 1171 """Create a new graph. 1172 1173 :param name: Graph name. 1174 :type name: str 1175 :param edge_definitions: List of edge definitions, where each edge 1176 definition entry is a dictionary with fields "edge_collection", 1177 "from_vertex_collections" and "to_vertex_collections" (see below 1178 for example). 1179 :type edge_definitions: [dict] | None 1180 :param orphan_collections: Names of additional vertex collections that 1181 are not in edge definitions. 1182 :type orphan_collections: [str] | None 1183 :param smart: If set to True, sharding is enabled (see parameter 1184 **smart_field** below). Applies only to enterprise version of 1185 ArangoDB. 1186 :type smart: bool | None 1187 :param smart_field: Document field used to shard the vertices of the 1188 graph. To use this, parameter **smart** must be set to True and 1189 every vertex in the graph must have the smart field. Applies only 1190 to enterprise version of ArangoDB. 1191 :type smart_field: str | None 1192 :param shard_count: Number of shards used for every collection in the 1193 graph. To use this, parameter **smart** must be set to True and 1194 every vertex in the graph must have the smart field. This number 1195 cannot be modified later once set. Applies only to enterprise 1196 version of ArangoDB. 1197 :type shard_count: int | None 1198 :return: Graph API wrapper. 1199 :rtype: arango.graph.Graph 1200 :raise arango.exceptions.GraphCreateError: If create fails. 1201 1202 Here is an example entry for parameter **edge_definitions**: 1203 1204 .. code-block:: python 1205 1206 { 1207 'edge_collection': 'teach', 1208 'from_vertex_collections': ['teachers'], 1209 'to_vertex_collections': ['lectures'] 1210 } 1211 """ 1212 data: Json = {"name": name, "options": dict()} 1213 if edge_definitions is not None: 1214 data["edgeDefinitions"] = [ 1215 { 1216 "collection": definition["edge_collection"], 1217 "from": definition["from_vertex_collections"], 1218 "to": definition["to_vertex_collections"], 1219 } 1220 for definition in edge_definitions 1221 ] 1222 if orphan_collections is not None: 1223 data["orphanCollections"] = orphan_collections 1224 if smart is not None: # pragma: no cover 1225 data["isSmart"] = smart 1226 if smart_field is not None: # pragma: no cover 1227 data["options"]["smartGraphAttribute"] = smart_field 1228 if shard_count is not None: # pragma: no cover 1229 data["options"]["numberOfShards"] = shard_count 1230 1231 request = Request(method="post", endpoint="/_api/gharial", data=data) 1232 1233 def response_handler(resp: Response) -> Graph: 1234 if resp.is_success: 1235 return Graph(self._conn, self._executor, name) 1236 raise GraphCreateError(resp, request) 1237 1238 return self._execute(request, response_handler) 1239 1240 def delete_graph( 1241 self, 1242 name: str, 1243 ignore_missing: bool = False, 1244 drop_collections: Optional[bool] = None, 1245 ) -> Result[bool]: 1246 """Drop the graph of the given name from the database. 1247 1248 :param name: Graph name. 1249 :type name: str 1250 :param ignore_missing: Do not raise an exception on missing graph. 1251 :type ignore_missing: bool 1252 :param drop_collections: Drop the collections of the graph also. This 1253 is only if they are not in use by other graphs. 1254 :type drop_collections: bool | None 1255 :return: True if graph was deleted successfully, False if graph was not 1256 found and **ignore_missing** was set to True. 1257 :rtype: bool 1258 :raise arango.exceptions.GraphDeleteError: If delete fails. 1259 """ 1260 params: Params = {} 1261 if drop_collections is not None: 1262 params["dropCollections"] = drop_collections 1263 1264 request = Request( 1265 method="delete", endpoint=f"/_api/gharial/{name}", params=params 1266 ) 1267 1268 def response_handler(resp: Response) -> bool: 1269 if resp.error_code == 1924 and ignore_missing: 1270 return False 1271 if not resp.is_success: 1272 raise GraphDeleteError(resp, request) 1273 return True 1274 1275 return self._execute(request, response_handler) 1276 1277 ####################### 1278 # Document Management # 1279 ####################### 1280 1281 def has_document( 1282 self, document: Json, rev: Optional[str] = None, check_rev: bool = True 1283 ) -> Result[bool]: 1284 """Check if a document exists. 1285 1286 :param document: Document ID or body with "_id" field. 1287 :type document: str | dict 1288 :param rev: Expected document revision. Overrides value of "_rev" field 1289 in **document** if present. 1290 :type rev: str | None 1291 :param check_rev: If set to True, revision of **document** (if given) 1292 is compared against the revision of target document. 1293 :type check_rev: bool 1294 :return: True if document exists, False otherwise. 1295 :rtype: bool 1296 :raise arango.exceptions.DocumentInError: If check fails. 1297 :raise arango.exceptions.DocumentRevisionError: If revisions mismatch. 1298 """ 1299 return self._get_col_by_doc(document).has( 1300 document=document, rev=rev, check_rev=check_rev 1301 ) 1302 1303 def document( 1304 self, document: Json, rev: Optional[str] = None, check_rev: bool = True 1305 ) -> Result[Optional[Json]]: 1306 """Return a document. 1307 1308 :param document: Document ID or body with "_id" field. 1309 :type document: str | dict 1310 :param rev: Expected document revision. Overrides the value of "_rev" 1311 field in **document** if present. 1312 :type rev: str | None 1313 :param check_rev: If set to True, revision of **document** (if given) 1314 is compared against the revision of target document. 1315 :type check_rev: bool 1316 :return: Document, or None if not found. 1317 :rtype: dict | None 1318 :raise arango.exceptions.DocumentGetError: If retrieval fails. 1319 :raise arango.exceptions.DocumentRevisionError: If revisions mismatch. 1320 """ 1321 return self._get_col_by_doc(document).get( 1322 document=document, rev=rev, check_rev=check_rev 1323 ) 1324 1325 def insert_document( 1326 self, 1327 collection: str, 1328 document: Json, 1329 return_new: bool = False, 1330 sync: Optional[bool] = None, 1331 silent: bool = False, 1332 overwrite: bool = False, 1333 return_old: bool = False, 1334 overwrite_mode: Optional[str] = None, 1335 keep_none: Optional[bool] = None, 1336 merge: Optional[bool] = None, 1337 ) -> Result[Union[bool, Json]]: 1338 """Insert a new document. 1339 1340 :param collection: Collection name. 1341 :type collection: str 1342 :param document: Document to insert. If it contains the "_key" or "_id" 1343 field, the value is used as the key of the new document (otherwise 1344 it is auto-generated). Any "_rev" field is ignored. 1345 :type document: dict 1346 :param return_new: Include body of the new document in the returned 1347 metadata. Ignored if parameter **silent** is set to True. 1348 :type return_new: bool 1349 :param sync: Block until operation is synchronized to disk. 1350 :type sync: bool | None 1351 :param silent: If set to True, no document metadata is returned. This 1352 can be used to save resources. 1353 :type silent: bool 1354 :param overwrite: If set to True, operation does not fail on duplicate 1355 key and the existing document is replaced. 1356 :type overwrite: bool 1357 :param return_old: Include body of the old document if replaced. 1358 Applies only when value of **overwrite** is set to True. 1359 :type return_old: bool 1360 :param overwrite_mode: Overwrite behavior used when the document key 1361 exists already. Allowed values are "replace" (replace-insert) or 1362 "update" (update-insert). Implicitly sets the value of parameter 1363 **overwrite**. 1364 :type overwrite_mode: str | None 1365 :param keep_none: If set to True, fields with value None are retained 1366 in the document. Otherwise, they are removed completely. Applies 1367 only when **overwrite_mode** is set to "update" (update-insert). 1368 :type keep_none: bool | None 1369 :param merge: If set to True (default), sub-dictionaries are merged 1370 instead of the new one overwriting the old one. Applies only when 1371 **overwrite_mode** is set to "update" (update-insert). 1372 :type merge: bool | None 1373 :return: Document metadata (e.g. document key, revision) or True if 1374 parameter **silent** was set to True. 1375 :rtype: bool | dict 1376 :raise arango.exceptions.DocumentInsertError: If insert fails. 1377 """ 1378 return self.collection(collection).insert( 1379 document=document, 1380 return_new=return_new, 1381 sync=sync, 1382 silent=silent, 1383 overwrite=overwrite, 1384 return_old=return_old, 1385 overwrite_mode=overwrite_mode, 1386 keep_none=keep_none, 1387 merge=merge, 1388 ) 1389 1390 def update_document( 1391 self, 1392 document: Json, 1393 check_rev: bool = True, 1394 merge: bool = True, 1395 keep_none: bool = True, 1396 return_new: bool = False, 1397 return_old: bool = False, 1398 sync: Optional[bool] = None, 1399 silent: bool = False, 1400 ) -> Result[Union[bool, Json]]: 1401 """Update a document. 1402 1403 :param document: Partial or full document with the updated values. It 1404 must contain the "_id" field. 1405 :type document: dict 1406 :param check_rev: If set to True, revision of **document** (if given) 1407 is compared against the revision of target document. 1408 :type check_rev: bool 1409 :param merge: If set to True, sub-dictionaries are merged instead of 1410 the new one overwriting the old one. 1411 :type merge: bool | None 1412 :param keep_none: If set to True, fields with value None are retained 1413 in the document. Otherwise, they are removed completely. 1414 :type keep_none: bool | None 1415 :param return_new: Include body of the new document in the result. 1416 :type return_new: bool 1417 :param return_old: Include body of the old document in the result. 1418 :type return_old: bool 1419 :param sync: Block until operation is synchronized to disk. 1420 :type sync: bool | None 1421 :param silent: If set to True, no document metadata is returned. This 1422 can be used to save resources. 1423 :type silent: bool 1424 :return: Document metadata (e.g. document key, revision) or True if 1425 parameter **silent** was set to True. 1426 :rtype: bool | dict 1427 :raise arango.exceptions.DocumentUpdateError: If update fails. 1428 :raise arango.exceptions.DocumentRevisionError: If revisions mismatch. 1429 """ 1430 return self._get_col_by_doc(document).update( 1431 document=document, 1432 check_rev=check_rev, 1433 merge=merge, 1434 keep_none=keep_none, 1435 return_new=return_new, 1436 return_old=return_old, 1437 sync=sync, 1438 silent=silent, 1439 ) 1440 1441 def replace_document( 1442 self, 1443 document: Json, 1444 check_rev: bool = True, 1445 return_new: bool = False, 1446 return_old: bool = False, 1447 sync: Optional[bool] = None, 1448 silent: bool = False, 1449 ) -> Result[Union[bool, Json]]: 1450 """Replace a document. 1451 1452 :param document: New document to replace the old one with. It must 1453 contain the "_id" field. Edge document must also have "_from" and 1454 "_to" fields. 1455 :type document: dict 1456 :param check_rev: If set to True, revision of **document** (if given) 1457 is compared against the revision of target document. 1458 :type check_rev: bool 1459 :param return_new: Include body of the new document in the result. 1460 :type return_new: bool 1461 :param return_old: Include body of the old document in the result. 1462 :type return_old: bool 1463 :param sync: Block until operation is synchronized to disk. 1464 :type sync: bool | None 1465 :param silent: If set to True, no document metadata is returned. This 1466 can be used to save resources. 1467 :type silent: bool 1468 :return: Document metadata (e.g. document key, revision) or True if 1469 parameter **silent** was set to True. 1470 :rtype: bool | dict 1471 :raise arango.exceptions.DocumentReplaceError: If replace fails. 1472 :raise arango.exceptions.DocumentRevisionError: If revisions mismatch. 1473 """ 1474 return self._get_col_by_doc(document).replace( 1475 document=document, 1476 check_rev=check_rev, 1477 return_new=return_new, 1478 return_old=return_old, 1479 sync=sync, 1480 silent=silent, 1481 ) 1482 1483 def delete_document( 1484 self, 1485 document: Union[str, Json], 1486 rev: Optional[str] = None, 1487 check_rev: bool = True, 1488 ignore_missing: bool = False, 1489 return_old: bool = False, 1490 sync: Optional[bool] = None, 1491 silent: bool = False, 1492 ) -> Result[Union[bool, Json]]: 1493 """Delete a document. 1494 1495 :param document: Document ID, key or body. Document body must contain 1496 the "_id" field. 1497 :type document: str | dict 1498 :param rev: Expected document revision. Overrides the value of "_rev" 1499 field in **document** if present. 1500 :type rev: str | None 1501 :param check_rev: If set to True, revision of **document** (if given) 1502 is compared against the revision of target document. 1503 :type check_rev: bool 1504 :param ignore_missing: Do not raise an exception on missing document. 1505 This parameter has no effect in transactions where an exception is 1506 always raised on failures. 1507 :type ignore_missing: bool 1508 :param return_old: Include body of the old document in the result. 1509 :type return_old: bool 1510 :param sync: Block until operation is synchronized to disk. 1511 :type sync: bool | None 1512 :param silent: If set to True, no document metadata is returned. This 1513 can be used to save resources. 1514 :type silent: bool 1515 :return: Document metadata (e.g. document key, revision), or True if 1516 parameter **silent** was set to True, or False if document was not 1517 found and **ignore_missing** was set to True (does not apply in 1518 transactions). 1519 :rtype: bool | dict 1520 :raise arango.exceptions.DocumentDeleteError: If delete fails. 1521 :raise arango.exceptions.DocumentRevisionError: If revisions mismatch. 1522 """ 1523 return self._get_col_by_doc(document).delete( 1524 document=document, 1525 rev=rev, 1526 check_rev=check_rev, 1527 ignore_missing=ignore_missing, 1528 return_old=return_old, 1529 sync=sync, 1530 silent=silent, 1531 ) 1532 1533 ################### 1534 # Task Management # 1535 ################### 1536 1537 def tasks(self) -> Result[Jsons]: 1538 """Return all currently active server tasks. 1539 1540 :return: Currently active server tasks. 1541 :rtype: [dict] 1542 :raise arango.exceptions.TaskListError: If retrieval fails. 1543 """ 1544 request = Request(method="get", endpoint="/_api/tasks") 1545 1546 def response_handler(resp: Response) -> Jsons: 1547 if not resp.is_success: 1548 raise TaskListError(resp, request) 1549 result: Jsons = resp.body 1550 return result 1551 1552 return self._execute(request, response_handler) 1553 1554 def task(self, task_id: str) -> Result[Json]: 1555 """Return the details of an active server task. 1556 1557 :param task_id: Server task ID. 1558 :type task_id: str 1559 :return: Server task details. 1560 :rtype: dict 1561 :raise arango.exceptions.TaskGetError: If retrieval fails. 1562 """ 1563 request = Request(method="get", endpoint=f"/_api/tasks/{task_id}") 1564 1565 def response_handler(resp: Response) -> Json: 1566 if resp.is_success: 1567 return format_body(resp.body) 1568 raise TaskGetError(resp, request) 1569 1570 return self._execute(request, response_handler) 1571 1572 def create_task( 1573 self, 1574 name: str, 1575 command: str, 1576 params: Optional[Json] = None, 1577 period: Optional[int] = None, 1578 offset: Optional[int] = None, 1579 task_id: Optional[str] = None, 1580 ) -> Result[Json]: 1581 """Create a new server task. 1582 1583 :param name: Name of the server task. 1584 :type name: str 1585 :param command: Javascript command to execute. 1586 :type command: str 1587 :param params: Optional parameters passed into the Javascript command. 1588 :type params: dict | None 1589 :param period: Number of seconds to wait between executions. If set 1590 to 0, the new task will be "timed", meaning it will execute only 1591 once and be deleted afterwards. 1592 :type period: int | None 1593 :param offset: Initial delay before execution in seconds. 1594 :type offset: int | None 1595 :param task_id: Pre-defined ID for the new server task. 1596 :type task_id: str | None 1597 :return: Details of the new task. 1598 :rtype: dict 1599 :raise arango.exceptions.TaskCreateError: If create fails. 1600 """ 1601 data: Json = {"name": name, "command": command} 1602 if params is not None: 1603 data["params"] = params 1604 if task_id is not None: 1605 data["id"] = task_id 1606 if period is not None: 1607 data["period"] = period 1608 if offset is not None: 1609 data["offset"] = offset 1610 1611 if task_id is None: 1612 task_id = "" 1613 1614 request = Request(method="post", endpoint=f"/_api/tasks/{task_id}", data=data) 1615 1616 def response_handler(resp: Response) -> Json: 1617 if resp.is_success: 1618 return format_body(resp.body) 1619 raise TaskCreateError(resp, request) 1620 1621 return self._execute(request, response_handler) 1622 1623 def delete_task(self, task_id: str, ignore_missing: bool = False) -> Result[bool]: 1624 """Delete a server task. 1625 1626 :param task_id: Server task ID. 1627 :type task_id: str 1628 :param ignore_missing: Do not raise an exception on missing task. 1629 :type ignore_missing: bool 1630 :return: True if task was successfully deleted, False if task was not 1631 found and **ignore_missing** was set to True. 1632 :rtype: bool 1633 :raise arango.exceptions.TaskDeleteError: If delete fails. 1634 """ 1635 request = Request(method="delete", endpoint=f"/_api/tasks/{task_id}") 1636 1637 def response_handler(resp: Response) -> bool: 1638 if resp.error_code == 1852 and ignore_missing: 1639 return False 1640 if not resp.is_success: 1641 raise TaskDeleteError(resp, request) 1642 return True 1643 1644 return self._execute(request, response_handler) 1645 1646 ################### 1647 # User Management # 1648 ################### 1649 1650 def has_user(self, username: str) -> Result[bool]: 1651 """Check if user exists. 1652 1653 :param username: Username. 1654 :type username: str 1655 :return: True if user exists, False otherwise. 1656 :rtype: bool 1657 """ 1658 request = Request(method="get", endpoint="/_api/user") 1659 1660 def response_handler(resp: Response) -> bool: 1661 if not resp.is_success: 1662 raise UserListError(resp, request) 1663 return any(user["user"] == username for user in resp.body["result"]) 1664 1665 return self._execute(request, response_handler) 1666 1667 def users(self) -> Result[Jsons]: 1668 """Return all user details. 1669 1670 :return: List of user details. 1671 :rtype: [dict] 1672 :raise arango.exceptions.UserListError: If retrieval fails. 1673 """ 1674 request = Request(method="get", endpoint="/_api/user") 1675 1676 def response_handler(resp: Response) -> Jsons: 1677 if not resp.is_success: 1678 raise UserListError(resp, request) 1679 return [ 1680 { 1681 "username": record["user"], 1682 "active": record["active"], 1683 "extra": record["extra"], 1684 } 1685 for record in resp.body["result"] 1686 ] 1687 1688 return self._execute(request, response_handler) 1689 1690 def user(self, username: str) -> Result[Json]: 1691 """Return user details. 1692 1693 :param username: Username. 1694 :type username: str 1695 :return: User details. 1696 :rtype: dict 1697 :raise arango.exceptions.UserGetError: If retrieval fails. 1698 """ 1699 request = Request(method="get", endpoint=f"/_api/user/{username}") 1700 1701 def response_handler(resp: Response) -> Json: 1702 if not resp.is_success: 1703 raise UserGetError(resp, request) 1704 return { 1705 "username": resp.body["user"], 1706 "active": resp.body["active"], 1707 "extra": resp.body["extra"], 1708 } 1709 1710 return self._execute(request, response_handler) 1711 1712 def create_user( 1713 self, 1714 username: str, 1715 password: Optional[str] = None, 1716 active: Optional[bool] = None, 1717 extra: Optional[Json] = None, 1718 ) -> Result[Json]: 1719 """Create a new user. 1720 1721 :param username: Username. 1722 :type username: str 1723 :param password: Password. 1724 :type password: str | None 1725 :param active: True if user is active, False otherwise. 1726 :type active: bool | None 1727 :param extra: Additional data for the user. 1728 :type extra: dict | None 1729 :return: New user details. 1730 :rtype: dict 1731 :raise arango.exceptions.UserCreateError: If create fails. 1732 """ 1733 data: Json = {"user": username, "passwd": password, "active": active} 1734 if extra is not None: 1735 data["extra"] = extra 1736 1737 request = Request(method="post", endpoint="/_api/user", data=data) 1738 1739 def response_handler(resp: Response) -> Json: 1740 if not resp.is_success: 1741 raise UserCreateError(resp, request) 1742 return { 1743 "username": resp.body["user"], 1744 "active": resp.body["active"], 1745 "extra": resp.body["extra"], 1746 } 1747 1748 return self._execute(request, response_handler) 1749 1750 def update_user( 1751 self, 1752 username: str, 1753 password: Optional[str] = None, 1754 active: Optional[bool] = None, 1755 extra: Optional[Json] = None, 1756 ) -> Result[Json]: 1757 """Update a user. 1758 1759 :param username: Username. 1760 :type username: str 1761 :param password: New password. 1762 :type password: str | None 1763 :param active: Whether the user is active. 1764 :type active: bool | None 1765 :param extra: Additional data for the user. 1766 :type extra: dict | None 1767 :return: New user details. 1768 :rtype: dict 1769 :raise arango.exceptions.UserUpdateError: If update fails. 1770 """ 1771 data: Json = {} 1772 if password is not None: 1773 data["passwd"] = password 1774 if active is not None: 1775 data["active"] = active 1776 if extra is not None: 1777 data["extra"] = extra 1778 1779 request = Request( 1780 method="patch", 1781 endpoint=f"/_api/user/{username}", 1782 data=data, 1783 ) 1784 1785 def response_handler(resp: Response) -> Json: 1786 if not resp.is_success: 1787 raise UserUpdateError(resp, request) 1788 return { 1789 "username": resp.body["user"], 1790 "active": resp.body["active"], 1791 "extra": resp.body["extra"], 1792 } 1793 1794 return self._execute(request, response_handler) 1795 1796 def replace_user( 1797 self, 1798 username: str, 1799 password: str, 1800 active: Optional[bool] = None, 1801 extra: Optional[Json] = None, 1802 ) -> Result[Json]: 1803 """Replace a user. 1804 1805 :param username: Username. 1806 :type username: str 1807 :param password: New password. 1808 :type password: str 1809 :param active: Whether the user is active. 1810 :type active: bool | None 1811 :param extra: Additional data for the user. 1812 :type extra: dict | None 1813 :return: New user details. 1814 :rtype: dict 1815 :raise arango.exceptions.UserReplaceError: If replace fails. 1816 """ 1817 data: Json = {"user": username, "passwd": password} 1818 if active is not None: 1819 data["active"] = active 1820 if extra is not None: 1821 data["extra"] = extra 1822 1823 request = Request(method="put", endpoint=f"/_api/user/{username}", data=data) 1824 1825 def response_handler(resp: Response) -> Json: 1826 if resp.is_success: 1827 return { 1828 "username": resp.body["user"], 1829 "active": resp.body["active"], 1830 "extra": resp.body["extra"], 1831 } 1832 raise UserReplaceError(resp, request) 1833 1834 return self._execute(request, response_handler) 1835 1836 def delete_user(self, username: str, ignore_missing: bool = False) -> Result[bool]: 1837 """Delete a user. 1838 1839 :param username: Username. 1840 :type username: str 1841 :param ignore_missing: Do not raise an exception on missing user. 1842 :type ignore_missing: bool 1843 :return: True if user was deleted successfully, False if user was not 1844 found and **ignore_missing** was set to True. 1845 :rtype: bool 1846 :raise arango.exceptions.UserDeleteError: If delete fails. 1847 """ 1848 request = Request(method="delete", endpoint=f"/_api/user/{username}") 1849 1850 def response_handler(resp: Response) -> bool: 1851 if resp.is_success: 1852 return True 1853 elif resp.status_code == 404 and ignore_missing: 1854 return False 1855 raise UserDeleteError(resp, request) 1856 1857 return self._execute(request, response_handler) 1858 1859 ######################### 1860 # Permission Management # 1861 ######################### 1862 1863 def permissions(self, username: str) -> Result[Json]: 1864 """Return user permissions for all databases and collections. 1865 1866 :param username: Username. 1867 :type username: str 1868 :return: User permissions for all databases and collections. 1869 :rtype: dict 1870 :raise arango.exceptions.PermissionListError: If retrieval fails. 1871 """ 1872 request = Request( 1873 method="get", 1874 endpoint=f"/_api/user/{username}/database", 1875 params={"full": True}, 1876 ) 1877 1878 def response_handler(resp: Response) -> Json: 1879 if resp.is_success: 1880 result: Json = resp.body["result"] 1881 return result 1882 raise PermissionListError(resp, request) 1883 1884 return self._execute(request, response_handler) 1885 1886 def permission( 1887 self, username: str, database: str, collection: Optional[str] = None 1888 ) -> Result[str]: 1889 """Return user permission for a specific database or collection. 1890 1891 :param username: Username. 1892 :type username: str 1893 :param database: Database name. 1894 :type database: str 1895 :param collection: Collection name. 1896 :type collection: str | None 1897 :return: Permission for given database or collection. 1898 :rtype: str 1899 :raise arango.exceptions.PermissionGetError: If retrieval fails. 1900 """ 1901 endpoint = f"/_api/user/{username}/database/{database}" 1902 if collection is not None: 1903 endpoint += "/" + collection 1904 request = Request(method="get", endpoint=endpoint) 1905 1906 def response_handler(resp: Response) -> str: 1907 if resp.is_success: 1908 return str(resp.body["result"]) 1909 raise PermissionGetError(resp, request) 1910 1911 return self._execute(request, response_handler) 1912 1913 def update_permission( 1914 self, 1915 username: str, 1916 permission: str, 1917 database: str, 1918 collection: Optional[str] = None, 1919 ) -> Result[bool]: 1920 """Update user permission for a specific database or collection. 1921 1922 :param username: Username. 1923 :type username: str 1924 :param permission: Allowed values are "rw" (read and write), "ro" 1925 (read only) or "none" (no access). 1926 :type permission: str 1927 :param database: Database name. 1928 :type database: str 1929 :param collection: Collection name. 1930 :type collection: str | None 1931 :return: True if access was granted successfully. 1932 :rtype: bool 1933 :raise arango.exceptions.PermissionUpdateError: If update fails. 1934 """ 1935 endpoint = f"/_api/user/{username}/database/{database}" 1936 if collection is not None: 1937 endpoint += "/" + collection 1938 1939 request = Request(method="put", endpoint=endpoint, data={"grant": permission}) 1940 1941 def response_handler(resp: Response) -> bool: 1942 if resp.is_success: 1943 return True 1944 raise PermissionUpdateError(resp, request) 1945 1946 return self._execute(request, response_handler) 1947 1948 def reset_permission( 1949 self, username: str, database: str, collection: Optional[str] = None 1950 ) -> Result[bool]: 1951 """Reset user permission for a specific database or collection. 1952 1953 :param username: Username. 1954 :type username: str 1955 :param database: Database name. 1956 :type database: str 1957 :param collection: Collection name. 1958 :type collection: str 1959 :return: True if permission was reset successfully. 1960 :rtype: bool 1961 :raise arango.exceptions.PermissionRestError: If reset fails. 1962 """ 1963 endpoint = f"/_api/user/{username}/database/{database}" 1964 if collection is not None: 1965 endpoint += "/" + collection 1966 1967 request = Request(method="delete", endpoint=endpoint) 1968 1969 def response_handler(resp: Response) -> bool: 1970 if resp.is_success: 1971 return True 1972 raise PermissionResetError(resp, request) 1973 1974 return self._execute(request, response_handler) 1975 1976 ######################## 1977 # Async Job Management # 1978 ######################## 1979 1980 def async_jobs(self, status: str, count: Optional[int] = None) -> Result[List[str]]: 1981 """Return IDs of async jobs with given status. 1982 1983 :param status: Job status (e.g. "pending", "done"). 1984 :type status: str 1985 :param count: Max number of job IDs to return. 1986 :type count: int 1987 :return: List of job IDs. 1988 :rtype: [str] 1989 :raise arango.exceptions.AsyncJobListError: If retrieval fails. 1990 """ 1991 params: Params = {} 1992 if count is not None: 1993 params["count"] = count 1994 1995 request = Request(method="get", endpoint=f"/_api/job/{status}", params=params) 1996 1997 def response_handler(resp: Response) -> List[str]: 1998 if resp.is_success: 1999 result: List[str] = resp.body 2000 return result 2001 raise AsyncJobListError(resp, request) 2002 2003 return self._execute(request, response_handler) 2004 2005 def clear_async_jobs(self, threshold: Optional[int] = None) -> Result[bool]: 2006 """Clear async job results from the server. 2007 2008 Async jobs that are still queued or running are not stopped. 2009 2010 :param threshold: If specified, only the job results created prior to 2011 the threshold (a unix timestamp) are deleted. Otherwise, all job 2012 results are deleted. 2013 :type threshold: int | None 2014 :return: True if job results were cleared successfully. 2015 :rtype: bool 2016 :raise arango.exceptions.AsyncJobClearError: If operation fails. 2017 """ 2018 if threshold is None: 2019 request = Request(method="delete", endpoint="/_api/job/all") 2020 else: 2021 request = Request( 2022 method="delete", 2023 endpoint="/_api/job/expired", 2024 params={"stamp": threshold}, 2025 ) 2026 2027 def response_handler(resp: Response) -> bool: 2028 if resp.is_success: 2029 return True 2030 raise AsyncJobClearError(resp, request) 2031 2032 return self._execute(request, response_handler) 2033 2034 ################### 2035 # View Management # 2036 ################### 2037 2038 def views(self) -> Result[Jsons]: 2039 """Return list of views and their summaries. 2040 2041 :return: List of views. 2042 :rtype: [dict] 2043 :raise arango.exceptions.ViewListError: If retrieval fails. 2044 """ 2045 request = Request(method="get", endpoint="/_api/view") 2046 2047 def response_handler(resp: Response) -> Jsons: 2048 if resp.is_success: 2049 return [format_view(view) for view in resp.body["result"]] 2050 raise ViewListError(resp, request) 2051 2052 return self._execute(request, response_handler) 2053 2054 def view(self, name: str) -> Result[Json]: 2055 """Return view details. 2056 2057 :return: View details. 2058 :rtype: dict 2059 :raise arango.exceptions.ViewGetError: If retrieval fails. 2060 """ 2061 request = Request(method="get", endpoint=f"/_api/view/{name}/properties") 2062 2063 def response_handler(resp: Response) -> Json: 2064 if resp.is_success: 2065 return format_view(resp.body) 2066 raise ViewGetError(resp, request) 2067 2068 return self._execute(request, response_handler) 2069 2070 def create_view( 2071 self, name: str, view_type: str, properties: Optional[Json] = None 2072 ) -> Result[Json]: 2073 """Create a view. 2074 2075 :param name: View name. 2076 :type name: str 2077 :param view_type: View type (e.g. "arangosearch"). 2078 :type view_type: str 2079 :param properties: View properties. For more information see 2080 https://www.arangodb.com/docs/stable/http/views-arangosearch.html 2081 :type properties: dict 2082 :return: View details. 2083 :rtype: dict 2084 :raise arango.exceptions.ViewCreateError: If create fails. 2085 """ 2086 data: Json = {"name": name, "type": view_type} 2087 2088 if properties is not None: 2089 data.update(properties) 2090 2091 request = Request(method="post", endpoint="/_api/view", data=data) 2092 2093 def response_handler(resp: Response) -> Json: 2094 if resp.is_success: 2095 return format_view(resp.body) 2096 raise ViewCreateError(resp, request) 2097 2098 return self._execute(request, response_handler) 2099 2100 def update_view(self, name: str, properties: Json) -> Result[Json]: 2101 """Update a view. 2102 2103 :param name: View name. 2104 :type name: str 2105 :param properties: View properties. For more information see 2106 https://www.arangodb.com/docs/stable/http/views-arangosearch.html 2107 :type properties: dict 2108 :return: View details. 2109 :rtype: dict 2110 :raise arango.exceptions.ViewUpdateError: If update fails. 2111 """ 2112 request = Request( 2113 method="patch", 2114 endpoint=f"/_api/view/{name}/properties", 2115 data=properties, 2116 ) 2117 2118 def response_handler(resp: Response) -> Json: 2119 if resp.is_success: 2120 return format_view(resp.body) 2121 raise ViewUpdateError(resp, request) 2122 2123 return self._execute(request, response_handler) 2124 2125 def replace_view(self, name: str, properties: Json) -> Result[Json]: 2126 """Replace a view. 2127 2128 :param name: View name. 2129 :type name: str 2130 :param properties: View properties. For more information see 2131 https://www.arangodb.com/docs/stable/http/views-arangosearch.html 2132 :type properties: dict 2133 :return: View details. 2134 :rtype: dict 2135 :raise arango.exceptions.ViewReplaceError: If replace fails. 2136 """ 2137 request = Request( 2138 method="put", 2139 endpoint=f"/_api/view/{name}/properties", 2140 data=properties, 2141 ) 2142 2143 def response_handler(resp: Response) -> Json: 2144 if resp.is_success: 2145 return format_view(resp.body) 2146 raise ViewReplaceError(resp, request) 2147 2148 return self._execute(request, response_handler) 2149 2150 def delete_view(self, name: str, ignore_missing: bool = False) -> Result[bool]: 2151 """Delete a view. 2152 2153 :param name: View name. 2154 :type name: str 2155 :param ignore_missing: Do not raise an exception on missing view. 2156 :type ignore_missing: bool 2157 :return: True if view was deleted successfully, False if view was not 2158 found and **ignore_missing** was set to True. 2159 :rtype: bool 2160 :raise arango.exceptions.ViewDeleteError: If delete fails. 2161 """ 2162 request = Request(method="delete", endpoint=f"/_api/view/{name}") 2163 2164 def response_handler(resp: Response) -> bool: 2165 if resp.error_code == 1203 and ignore_missing: 2166 return False 2167 if resp.is_success: 2168 return True 2169 raise ViewDeleteError(resp, request) 2170 2171 return self._execute(request, response_handler) 2172 2173 def rename_view(self, name: str, new_name: str) -> Result[bool]: 2174 """Rename a view. 2175 2176 :param name: View name. 2177 :type name: str 2178 :param new_name: New view name. 2179 :type new_name: str 2180 :return: True if view was renamed successfully. 2181 :rtype: bool 2182 :raise arango.exceptions.ViewRenameError: If delete fails. 2183 """ 2184 request = Request( 2185 method="put", 2186 endpoint=f"/_api/view/{name}/rename", 2187 data={"name": new_name}, 2188 ) 2189 2190 def response_handler(resp: Response) -> bool: 2191 if resp.is_success: 2192 return True 2193 raise ViewRenameError(resp, request) 2194 2195 return self._execute(request, response_handler) 2196 2197 ################################ 2198 # ArangoSearch View Management # 2199 ################################ 2200 2201 def create_arangosearch_view( 2202 self, name: str, properties: Optional[Json] = None 2203 ) -> Result[Json]: 2204 """Create an ArangoSearch view. 2205 2206 :param name: View name. 2207 :type name: str 2208 :param properties: View properties. For more information see 2209 https://www.arangodb.com/docs/stable/http/views-arangosearch.html 2210 :type properties: dict | None 2211 :return: View details. 2212 :rtype: dict 2213 :raise arango.exceptions.ViewCreateError: If create fails. 2214 """ 2215 data: Json = {"name": name, "type": "arangosearch"} 2216 2217 if properties is not None: 2218 data.update(properties) 2219 2220 request = Request(method="post", endpoint="/_api/view#ArangoSearch", data=data) 2221 2222 def response_handler(resp: Response) -> Json: 2223 if resp.is_success: 2224 return format_view(resp.body) 2225 raise ViewCreateError(resp, request) 2226 2227 return self._execute(request, response_handler) 2228 2229 def update_arangosearch_view(self, name: str, properties: Json) -> Result[Json]: 2230 """Update an ArangoSearch view. 2231 2232 :param name: View name. 2233 :type name: str 2234 :param properties: View properties. For more information see 2235 https://www.arangodb.com/docs/stable/http/views-arangosearch.html 2236 :type properties: dict 2237 :return: View details. 2238 :rtype: dict 2239 :raise arango.exceptions.ViewUpdateError: If update fails. 2240 """ 2241 request = Request( 2242 method="patch", 2243 endpoint=f"/_api/view/{name}/properties#ArangoSearch", 2244 data=properties, 2245 ) 2246 2247 def response_handler(resp: Response) -> Json: 2248 if resp.is_success: 2249 return format_view(resp.body) 2250 raise ViewUpdateError(resp, request) 2251 2252 return self._execute(request, response_handler) 2253 2254 def replace_arangosearch_view(self, name: str, properties: Json) -> Result[Json]: 2255 """Replace an ArangoSearch view. 2256 2257 :param name: View name. 2258 :type name: str 2259 :param properties: View properties. For more information see 2260 https://www.arangodb.com/docs/stable/http/views-arangosearch.html 2261 :type properties: dict 2262 :return: View details. 2263 :rtype: dict 2264 :raise arango.exceptions.ViewReplaceError: If replace fails. 2265 """ 2266 request = Request( 2267 method="put", 2268 endpoint=f"/_api/view/{name}/properties#ArangoSearch", 2269 data=properties, 2270 ) 2271 2272 def response_handler(resp: Response) -> Json: 2273 if resp.is_success: 2274 return format_view(resp.body) 2275 raise ViewReplaceError(resp, request) 2276 2277 return self._execute(request, response_handler) 2278 2279 ####################### 2280 # Analyzer Management # 2281 ####################### 2282 2283 def analyzers(self) -> Result[Jsons]: 2284 """Return list of analyzers. 2285 2286 :return: List of analyzers. 2287 :rtype: [dict] 2288 :raise arango.exceptions.AnalyzerListError: If retrieval fails. 2289 """ 2290 request = Request(method="get", endpoint="/_api/analyzer") 2291 2292 def response_handler(resp: Response) -> Jsons: 2293 if resp.is_success: 2294 result: Jsons = resp.body["result"] 2295 return result 2296 raise AnalyzerListError(resp, request) 2297 2298 return self._execute(request, response_handler) 2299 2300 def analyzer(self, name: str) -> Result[Json]: 2301 """Return analyzer details. 2302 2303 :param name: Analyzer name. 2304 :type name: str 2305 :return: Analyzer details. 2306 :rtype: dict 2307 :raise arango.exceptions.AnalyzerGetError: If retrieval fails. 2308 """ 2309 request = Request(method="get", endpoint=f"/_api/analyzer/{name}") 2310 2311 def response_handler(resp: Response) -> Json: 2312 if resp.is_success: 2313 return format_body(resp.body) 2314 raise AnalyzerGetError(resp, request) 2315 2316 return self._execute(request, response_handler) 2317 2318 def create_analyzer( 2319 self, 2320 name: str, 2321 analyzer_type: str, 2322 properties: Optional[Json] = None, 2323 features: Optional[Sequence[str]] = None, 2324 ) -> Result[Json]: 2325 """Create an analyzer. 2326 2327 :param name: Analyzer name. 2328 :type name: str 2329 :param analyzer_type: Analyzer type. 2330 :type analyzer_type: str 2331 :param properties: Analyzer properties. 2332 :type properties: dict | None 2333 :param features: Analyzer features. 2334 :type features: list | None 2335 :return: Analyzer details. 2336 :rtype: dict 2337 :raise arango.exceptions.AnalyzerCreateError: If create fails. 2338 """ 2339 data: Json = {"name": name, "type": analyzer_type} 2340 2341 if properties is not None: 2342 data["properties"] = properties 2343 2344 if features is not None: 2345 data["features"] = features 2346 2347 request = Request(method="post", endpoint="/_api/analyzer", data=data) 2348 2349 def response_handler(resp: Response) -> Json: 2350 if resp.is_success: 2351 result: Json = resp.body 2352 return result 2353 raise AnalyzerCreateError(resp, request) 2354 2355 return self._execute(request, response_handler) 2356 2357 def delete_analyzer( 2358 self, name: str, force: bool = False, ignore_missing: bool = False 2359 ) -> Result[bool]: 2360 """Delete an analyzer. 2361 2362 :param name: Analyzer name. 2363 :type name: str 2364 :param force: Remove the analyzer configuration even if in use. 2365 :type force: bool 2366 :param ignore_missing: Do not raise an exception on missing analyzer. 2367 :type ignore_missing: bool 2368 :return: True if analyzer was deleted successfully, False if analyzer 2369 was not found and **ignore_missing** was set to True. 2370 :rtype: bool 2371 :raise arango.exceptions.AnalyzerDeleteError: If delete fails. 2372 """ 2373 request = Request( 2374 method="delete", 2375 endpoint=f"/_api/analyzer/{name}", 2376 params={"force": force}, 2377 ) 2378 2379 def response_handler(resp: Response) -> bool: 2380 if resp.error_code in {1202, 404} and ignore_missing: 2381 return False 2382 if resp.is_success: 2383 return True 2384 raise AnalyzerDeleteError(resp, request) 2385 2386 return self._execute(request, response_handler) 2387 2388 2389class StandardDatabase(Database): 2390 """Standard database API wrapper.""" 2391 2392 def __init__(self, connection: Connection) -> None: 2393 super().__init__(connection=connection, executor=DefaultApiExecutor(connection)) 2394 2395 def __repr__(self) -> str: 2396 return f"<StandardDatabase {self.name}>" 2397 2398 def begin_async_execution(self, return_result: bool = True) -> "AsyncDatabase": 2399 """Begin async execution. 2400 2401 :param return_result: If set to True, API executions return instances 2402 of :class:`arango.job.AsyncJob`, which you can use to retrieve 2403 results from server once available. If set to False, API executions 2404 return None and no results are stored on server. 2405 :type return_result: bool 2406 :return: Database API wrapper object specifically for async execution. 2407 :rtype: arango.database.AsyncDatabase 2408 """ 2409 return AsyncDatabase(self._conn, return_result) 2410 2411 def begin_batch_execution(self, return_result: bool = True) -> "BatchDatabase": 2412 """Begin batch execution. 2413 2414 :param return_result: If set to True, API executions return instances 2415 of :class:`arango.job.BatchJob` that are populated with results on 2416 commit. If set to False, API executions return None and no results 2417 are tracked client-side. 2418 :type return_result: bool 2419 :return: Database API wrapper object specifically for batch execution. 2420 :rtype: arango.database.BatchDatabase 2421 """ 2422 return BatchDatabase(self._conn, return_result) 2423 2424 def begin_transaction( 2425 self, 2426 read: Union[str, Sequence[str], None] = None, 2427 write: Union[str, Sequence[str], None] = None, 2428 exclusive: Union[str, Sequence[str], None] = None, 2429 sync: Optional[bool] = None, 2430 allow_implicit: Optional[bool] = None, 2431 lock_timeout: Optional[int] = None, 2432 max_size: Optional[int] = None, 2433 ) -> "TransactionDatabase": 2434 """Begin a transaction. 2435 2436 :param read: Name(s) of collections read during transaction. Read-only 2437 collections are added lazily but should be declared if possible to 2438 avoid deadlocks. 2439 :type read: str | [str] | None 2440 :param write: Name(s) of collections written to during transaction with 2441 shared access. 2442 :type write: str | [str] | None 2443 :param exclusive: Name(s) of collections written to during transaction 2444 with exclusive access. 2445 :type exclusive: str | [str] | None 2446 :param sync: Block until operation is synchronized to disk. 2447 :type sync: bool | None 2448 :param allow_implicit: Allow reading from undeclared collections. 2449 :type allow_implicit: bool | None 2450 :param lock_timeout: Timeout for waiting on collection locks. If not 2451 given, a default value is used. Setting it to 0 disables the 2452 timeout. 2453 :type lock_timeout: int | None 2454 :param max_size: Max transaction size in bytes. 2455 :type max_size: int | None 2456 :return: Database API wrapper object specifically for transactions. 2457 :rtype: arango.database.TransactionDatabase 2458 """ 2459 return TransactionDatabase( 2460 connection=self._conn, 2461 read=read, 2462 write=write, 2463 exclusive=exclusive, 2464 sync=sync, 2465 allow_implicit=allow_implicit, 2466 lock_timeout=lock_timeout, 2467 max_size=max_size, 2468 ) 2469 2470 2471class AsyncDatabase(Database): 2472 """Database API wrapper tailored specifically for async execution. 2473 2474 See :func:`arango.database.StandardDatabase.begin_async_execution`. 2475 2476 :param connection: HTTP connection. 2477 :param return_result: If set to True, API executions return instances of 2478 :class:`arango.job.AsyncJob`, which you can use to retrieve results 2479 from server once available. If set to False, API executions return None 2480 and no results are stored on server. 2481 :type return_result: bool 2482 """ 2483 2484 def __init__(self, connection: Connection, return_result: bool) -> None: 2485 self._executor: AsyncApiExecutor 2486 super().__init__( 2487 connection=connection, executor=AsyncApiExecutor(connection, return_result) 2488 ) 2489 2490 def __repr__(self) -> str: 2491 return f"<AsyncDatabase {self.name}>" 2492 2493 2494class BatchDatabase(Database): 2495 """Database API wrapper tailored specifically for batch execution. 2496 2497 See :func:`arango.database.StandardDatabase.begin_batch_execution`. 2498 2499 :param connection: HTTP connection. 2500 :param return_result: If set to True, API executions return instances of 2501 :class:`arango.job.BatchJob` that are populated with results on commit. 2502 If set to False, API executions return None and no results are tracked 2503 client-side. 2504 :type return_result: bool 2505 """ 2506 2507 def __init__(self, connection: Connection, return_result: bool) -> None: 2508 self._executor: BatchApiExecutor 2509 super().__init__( 2510 connection=connection, executor=BatchApiExecutor(connection, return_result) 2511 ) 2512 2513 def __repr__(self) -> str: 2514 return f"<BatchDatabase {self.name}>" 2515 2516 def __enter__(self) -> "BatchDatabase": 2517 return self 2518 2519 def __exit__(self, exception: Exception, *_: Any) -> None: 2520 if exception is None: 2521 self._executor.commit() 2522 2523 def queued_jobs(self) -> Optional[Sequence[BatchJob[Any]]]: 2524 """Return the queued batch jobs. 2525 2526 :return: Queued batch jobs or None if **return_result** parameter was 2527 set to False during initialization. 2528 :rtype: [arango.job.BatchJob] | None 2529 """ 2530 return self._executor.jobs 2531 2532 def commit(self) -> Optional[Sequence[BatchJob[Any]]]: 2533 """Execute the queued requests in a single batch API request. 2534 2535 If **return_result** parameter was set to True during initialization, 2536 :class:`arango.job.BatchJob` instances are populated with results. 2537 2538 :return: Batch jobs, or None if **return_result** parameter was set to 2539 False during initialization. 2540 :rtype: [arango.job.BatchJob] | None 2541 :raise arango.exceptions.BatchStateError: If batch state is invalid 2542 (e.g. batch was already committed or the response size did not 2543 match expected). 2544 :raise arango.exceptions.BatchExecuteError: If commit fails. 2545 """ 2546 return self._executor.commit() 2547 2548 2549class TransactionDatabase(Database): 2550 """Database API wrapper tailored specifically for transactions. 2551 2552 See :func:`arango.database.StandardDatabase.begin_transaction`. 2553 2554 :param connection: HTTP connection. 2555 :param read: Name(s) of collections read during transaction. Read-only 2556 collections are added lazily but should be declared if possible to 2557 avoid deadlocks. 2558 :type read: str | [str] | None 2559 :param write: Name(s) of collections written to during transaction with 2560 shared access. 2561 :type write: str | [str] | None 2562 :param exclusive: Name(s) of collections written to during transaction 2563 with exclusive access. 2564 :type exclusive: str | [str] | None 2565 :param sync: Block until operation is synchronized to disk. 2566 :type sync: bool | None 2567 :param allow_implicit: Allow reading from undeclared collections. 2568 :type allow_implicit: bool | None 2569 :param lock_timeout: Timeout for waiting on collection locks. If not given, 2570 a default value is used. Setting it to 0 disables the timeout. 2571 :type lock_timeout: int | None 2572 :param max_size: Max transaction size in bytes. 2573 :type max_size: int | None 2574 """ 2575 2576 def __init__( 2577 self, 2578 connection: Connection, 2579 read: Union[str, Sequence[str], None] = None, 2580 write: Union[str, Sequence[str], None] = None, 2581 exclusive: Union[str, Sequence[str], None] = None, 2582 sync: Optional[bool] = None, 2583 allow_implicit: Optional[bool] = None, 2584 lock_timeout: Optional[int] = None, 2585 max_size: Optional[int] = None, 2586 ) -> None: 2587 self._executor: TransactionApiExecutor 2588 super().__init__( 2589 connection=connection, 2590 executor=TransactionApiExecutor( 2591 connection=connection, 2592 read=read, 2593 write=write, 2594 exclusive=exclusive, 2595 sync=sync, 2596 allow_implicit=allow_implicit, 2597 lock_timeout=lock_timeout, 2598 max_size=max_size, 2599 ), 2600 ) 2601 2602 def __repr__(self) -> str: 2603 return f"<TransactionDatabase {self.name}>" 2604 2605 @property 2606 def transaction_id(self) -> str: 2607 """Return the transaction ID. 2608 2609 :return: Transaction ID. 2610 :rtype: str 2611 """ 2612 return self._executor.id 2613 2614 def transaction_status(self) -> str: 2615 """Return the transaction status. 2616 2617 :return: Transaction status. 2618 :rtype: str 2619 :raise arango.exceptions.TransactionStatusError: If retrieval fails. 2620 """ 2621 return self._executor.status() 2622 2623 def commit_transaction(self) -> bool: 2624 """Commit the transaction. 2625 2626 :return: True if commit was successful. 2627 :rtype: bool 2628 :raise arango.exceptions.TransactionCommitError: If commit fails. 2629 """ 2630 return self._executor.commit() 2631 2632 def abort_transaction(self) -> bool: 2633 """Abort the transaction. 2634 2635 :return: True if the abort operation was successful. 2636 :rtype: bool 2637 :raise arango.exceptions.TransactionAbortError: If abort fails. 2638 """ 2639 return self._executor.abort() 2640