diff --git a/arango/backup.py b/arango/backup.py index c06e4e1..d82e8e2 100644 --- a/arango/backup.py +++ b/arango/backup.py @@ -33,7 +33,7 @@ def get(self, backup_id: Optional[str] = None) -> Result[Json]: :type backup_id: str :return: Backup details. :rtype: dict - :raise arango.exceptions.BackupGetError: If delete fails. + :raise arango.exceptions.BackupGetError: If the operation fails. """ request = Request( method="post", diff --git a/arango/cluster.py b/arango/cluster.py index ea13279..78fd3ac 100644 --- a/arango/cluster.py +++ b/arango/cluster.py @@ -261,7 +261,7 @@ def endpoints(self) -> Result[List[str]]: :return: List of endpoints. :rtype: [str] - :raise arango.exceptions.ServerEndpointsError: If retrieval fails. + :raise arango.exceptions.ClusterEndpointsError: If retrieval fails. """ request = Request(method="get", endpoint="/_api/cluster/endpoints") diff --git a/arango/collection.py b/arango/collection.py index a48bfd2..2b13884 100644 --- a/arango/collection.py +++ b/arango/collection.py @@ -537,10 +537,18 @@ def response_handler(resp: Response) -> Json: def load(self) -> Result[bool]: """Load the collection into memory. + .. note:: + The load function is deprecated from version 3.8.0 onwards and is a + no-op from version 3.9.0 onwards. It should no longer be used, as it + may be removed in a future version of ArangoDB. + :return: True if collection was loaded successfully. :rtype: bool :raise arango.exceptions.CollectionLoadError: If operation fails. """ + m = "The load function is deprecated from version 3.8.0 onwards and is a no-op from version 3.9.0 onwards." # noqa: E501 + warn(m, DeprecationWarning, stacklevel=2) + request = Request(method="put", endpoint=f"/_api/collection/{self.name}/load") def response_handler(resp: Response) -> bool: @@ -553,10 +561,18 @@ def response_handler(resp: Response) -> bool: def unload(self) -> Result[bool]: """Unload the collection from memory. + .. note:: + The unload function is deprecated from version 3.8.0 onwards and is a + no-op from version 3.9.0 onwards. It should no longer be used, as it + may be removed in a future version of ArangoDB. + :return: True if collection was unloaded successfully. :rtype: bool :raise arango.exceptions.CollectionUnloadError: If operation fails. """ + m = "The unload function is deprecated from version 3.8.0 onwards and is a no-op from version 3.9.0 onwards." # noqa: E501 + warn(m, DeprecationWarning, stacklevel=2) + request = Request(method="put", endpoint=f"/_api/collection/{self.name}/unload") def response_handler(resp: Response) -> bool: diff --git a/arango/connection.py b/arango/connection.py index 8de2643..9384aef 100644 --- a/arango/connection.py +++ b/arango/connection.py @@ -125,7 +125,11 @@ def prep_response(self, resp: Response, deserialize: bool = True) -> Response: return resp def process_request( - self, host_index: int, request: Request, auth: Optional[Tuple[str, str]] = None + self, + host_index: int, + request: Request, + auth: Optional[Tuple[str, str]] = None, + skip_db_prefix: bool = False, ) -> Response: """Execute a request until a valid response has been returned. @@ -133,6 +137,10 @@ def process_request( :type host_index: int :param request: HTTP request. :type request: arango.request.Request + :param auth: HTTP basic authentication tuple (username, password). + :type auth: tuple[str, str] | None + :param skip_db_prefix: Skip the database prefix in the URL. + :type skip_db_prefix: bool :return: HTTP response. :rtype: arango.response.Response """ @@ -152,11 +160,16 @@ def process_request( request.headers["accept-encoding"] = self._response_compression while tries < self._host_resolver.max_tries: + if skip_db_prefix: + url = self._hosts[host_index] + request.endpoint + else: + url = self._url_prefixes[host_index] + request.endpoint + try: resp = self._http.send_request( session=self._sessions[host_index], method=request.method, - url=self._url_prefixes[host_index] + request.endpoint, + url=url, params=request.params, data=data, headers=request.headers, @@ -165,7 +178,6 @@ def process_request( return self.prep_response(resp, request.deserialize) except ConnectionError: - url = self._url_prefixes[host_index] + request.endpoint logging.debug(f"ConnectionError: {url}") if len(indexes_to_filter) == self._host_resolver.host_count - 1: @@ -425,7 +437,7 @@ def refresh_token(self) -> None: host_index = self._host_resolver.get_host_index() - resp = self.process_request(host_index, request) + resp = self.process_request(host_index, request, skip_db_prefix=True) if not resp.is_success: raise JWTAuthError(resp, request) diff --git a/arango/database.py b/arango/database.py index 8a14591..766161d 100644 --- a/arango/database.py +++ b/arango/database.py @@ -27,6 +27,7 @@ AsyncJobListError, CollectionCreateError, CollectionDeleteError, + CollectionKeyGeneratorsError, CollectionListError, DatabaseCompactError, DatabaseCreateError, @@ -44,6 +45,7 @@ PermissionResetError, PermissionUpdateError, ServerAvailableOptionsGetError, + ServerCheckAvailabilityError, ServerCurrentOptionsGetError, ServerDetailsError, ServerEchoError, @@ -444,7 +446,7 @@ def set_license(self, license: str, force: bool = False) -> Result[Json]: :type force: bool :return: Server license. :rtype: dict - :raise arango.exceptions.ServerLicenseError: If retrieval fails. + :raise arango.exceptions.ServerLicenseSetError: If retrieval fails. """ request = Request( method="put", @@ -480,6 +482,25 @@ def response_handler(resp: Response) -> Json: return self._execute(request, response_handler) + def check_availability(self) -> Result[str]: + """Return ArangoDB server availability mode. + + :return: Server availability mode ("readonly" or "default"). + :rtype: str + :raise arango.exceptions.ServerCheckAvailabilityError: If retrieval fails. + """ + request = Request( + method="get", + endpoint="/_admin/server/availability", + ) + + def response_handler(resp: Response) -> str: + if not resp.is_success: + raise ServerCheckAvailabilityError(resp, request) + return str(resp.body["mode"]) + + return self._execute(request, response_handler) + def compact( self, change_level: Optional[bool] = None, @@ -1068,6 +1089,7 @@ def metrics(self) -> Result[str]: :return: Server metrics in Prometheus format. :rtype: str + :raise arango.exceptions.ServerMetricsError: If operation fails. """ request = Request(method="get", endpoint="/_admin/metrics/v2") @@ -1623,6 +1645,23 @@ def response_handler(resp: Response) -> bool: return self._execute(request, response_handler) + def key_generators(self) -> Result[List[str]]: + """Returns the available key generators for collections. + + :return: List of available key generators. + :rtype: [str] + :raise arango.exceptions.CollectionKeyGeneratorsError: If retrieval fails. + """ # noqa: E501 + request = Request(method="get", endpoint="/_api/key-generators") + + def response_handler(resp: Response) -> List[str]: + if not resp.is_success: + raise CollectionKeyGeneratorsError(resp, request) + result: List[str] = resp.body["keyGenerators"] + return result + + return self._execute(request, response_handler) + #################### # Graph Management # #################### diff --git a/arango/exceptions.py b/arango/exceptions.py index cdea90c..891c813 100644 --- a/arango/exceptions.py +++ b/arango/exceptions.py @@ -298,6 +298,10 @@ class CollectionTruncateError(ArangoServerError): """Failed to truncate collection.""" +class CollectionKeyGeneratorsError(ArangoServerError): + """Failed to retrieve key generators.""" + + class CollectionLoadError(ArangoServerError): """Failed to load collection.""" @@ -650,6 +654,10 @@ class ServerTimeError(ArangoServerError): """Failed to retrieve server system time.""" +class ServerCheckAvailabilityError(ArangoServerError): + """Failed to retrieve server availability mode.""" + + class ServerEchoError(ArangoServerError): """Failed to retrieve details on last request.""" diff --git a/arango/request.py b/arango/request.py index 11ff91c..4bb135a 100644 --- a/arango/request.py +++ b/arango/request.py @@ -12,7 +12,7 @@ def normalize_headers( if driver_flags is not None: for flag in driver_flags: flags = flags + flag + ";" - driver_version = "8.2.0" + driver_version = "8.2.2" driver_header = "python-arango/" + driver_version + " (" + flags + ")" normalized_headers: Headers = { "charset": "utf-8", diff --git a/docs/foxx.rst b/docs/foxx.rst index 4f6ce35..734a316 100644 --- a/docs/foxx.rst +++ b/docs/foxx.rst @@ -83,9 +83,9 @@ information, refer to `ArangoDB manual`_. foxx.readme(service_mount) foxx.swagger(service_mount) foxx.download(service_mount) - foxx.commit(service_mount) + foxx.commit() foxx.scripts(service_mount) - foxx.run_script(service_mount, 'setup', []) + foxx.run_script(service_mount, 'setup', {}) foxx.run_tests(service_mount, reporter='xunit', output_format='xml') # Delete a service. diff --git a/starter.sh b/starter.sh index b4e39f2..a5126f5 100755 --- a/starter.sh +++ b/starter.sh @@ -6,7 +6,7 @@ # Usage: # ./starter.sh [single|cluster] [community|enterprise] [version] # Example: -# ./starter.sh cluster enterprise 3.12.1 +# ./starter.sh cluster enterprise 3.12.5 setup="${1:-single}" license="${2:-community}" diff --git a/tests/test_database.py b/tests/test_database.py index 0b1d975..4e8a160 100644 --- a/tests/test_database.py +++ b/tests/test_database.py @@ -13,12 +13,14 @@ USE_SYSTEM_DATABASE, ) from arango.exceptions import ( + CollectionKeyGeneratorsError, DatabaseCompactError, DatabaseCreateError, DatabaseDeleteError, DatabaseListError, DatabasePropertiesError, DatabaseSupportInfoError, + ServerCheckAvailabilityError, ServerDetailsError, ServerEchoError, ServerEngineError, @@ -348,6 +350,17 @@ def test_database_misc_methods(client, sys_db, db, bad_db, cluster, secret, db_v result = db_superuser.compact() assert result == {} + if db_version >= version.parse("3.12.0"): + key_generators = db.key_generators() + assert isinstance(key_generators, list) + with pytest.raises(CollectionKeyGeneratorsError): + bad_db.key_generators() + + with pytest.raises(ServerCheckAvailabilityError): + bad_db.check_availability() + availability = db.check_availability() + assert isinstance(availability, str) + def test_database_management(db, sys_db, bad_db): # Test list databases @@ -439,18 +452,23 @@ def test_database_utf8(sys_db, special_db_names): assert sys_db.delete_database(name) -def test_license(sys_db, enterprise): +def test_license(sys_db, enterprise, db_version): license = sys_db.license() assert isinstance(license, dict) - if enterprise: - assert set(license.keys()) == { + if db_version >= version.parse("3.12.5"): + expected_keys = {"diskUsage", "upgrading"} + else: + expected_keys = { "upgrading", "features", "license", "version", "status", } + + if enterprise: + assert set(license.keys()) == expected_keys else: assert license == {"license": "none"} with pytest.raises(ServerLicenseSetError):