From 7fc1037dcfbe4b65115f7ebd396f1f3dc8f6cfac Mon Sep 17 00:00:00 2001 From: Alex Petenchea Date: Wed, 9 Jul 2025 04:46:46 +0300 Subject: [PATCH 1/5] Skip _db prefix on /_open/auth (#374) * Updating example version * No longer use _db/... prefix on /_open/auth * Fixing test dependant on version --- arango/connection.py | 20 ++++++++++++++++---- starter.sh | 2 +- tests/test_database.py | 11 ++++++++--- 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/arango/connection.py b/arango/connection.py index 8de2643a..9384aef1 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/starter.sh b/starter.sh index b4e39f24..a5126f54 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 0b1d9752..d6595a4d 100644 --- a/tests/test_database.py +++ b/tests/test_database.py @@ -439,18 +439,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): From 3dfbe2584bfb5f5b512e87e83a7c19b6b0d4af58 Mon Sep 17 00:00:00 2001 From: Alex Petenchea Date: Wed, 9 Jul 2025 01:48:16 +0000 Subject: [PATCH 2/5] Bump driver version --- arango/request.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arango/request.py b/arango/request.py index 11ff91c6..8735afe6 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.1" driver_header = "python-arango/" + driver_version + " (" + flags + ")" normalized_headers: Headers = { "charset": "utf-8", From 0366a1e2a1d22269db887a4ff4d55959a67a44a1 Mon Sep 17 00:00:00 2001 From: Alex Petenchea Date: Tue, 15 Jul 2025 04:17:13 +0300 Subject: [PATCH 3/5] Adding support for /key-generators (#375) --- arango/database.py | 18 ++++++++++++++++++ arango/exceptions.py | 4 ++++ tests/test_database.py | 7 +++++++ 3 files changed, 29 insertions(+) diff --git a/arango/database.py b/arango/database.py index 8a145910..20e771d2 100644 --- a/arango/database.py +++ b/arango/database.py @@ -27,6 +27,7 @@ AsyncJobListError, CollectionCreateError, CollectionDeleteError, + CollectionKeyGeneratorsError, CollectionListError, DatabaseCompactError, DatabaseCreateError, @@ -1623,6 +1624,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 cdea90c5..789468ed 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.""" diff --git a/tests/test_database.py b/tests/test_database.py index d6595a4d..014f0235 100644 --- a/tests/test_database.py +++ b/tests/test_database.py @@ -13,6 +13,7 @@ USE_SYSTEM_DATABASE, ) from arango.exceptions import ( + CollectionKeyGeneratorsError, DatabaseCompactError, DatabaseCreateError, DatabaseDeleteError, @@ -348,6 +349,12 @@ 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() + def test_database_management(db, sys_db, bad_db): # Test list databases From 08a2e54a69d7b0603bed7552fe0dc715d8632ef0 Mon Sep 17 00:00:00 2001 From: Alex Petenchea Date: Mon, 4 Aug 2025 13:15:43 +0800 Subject: [PATCH 4/5] Deprecate load/unload methods (#376) * Deprecating load method * Deprecating unload method * Minor comment correction --- arango/backup.py | 2 +- arango/collection.py | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/arango/backup.py b/arango/backup.py index c06e4e15..d82e8e2c 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/collection.py b/arango/collection.py index a48bfd2a..2b13884a 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: From 453f74b667c4d634196bed15cba2531957dfa7dd Mon Sep 17 00:00:00 2001 From: Alex Petenchea Date: Mon, 18 Aug 2025 14:18:03 +0800 Subject: [PATCH 5/5] Getting the driver up-to-date (#377) * Adding missing parts * Bumping driver version --- arango/cluster.py | 2 +- arango/database.py | 23 ++++++++++++++++++++++- arango/exceptions.py | 4 ++++ arango/request.py | 2 +- docs/foxx.rst | 4 ++-- tests/test_database.py | 6 ++++++ 6 files changed, 36 insertions(+), 5 deletions(-) diff --git a/arango/cluster.py b/arango/cluster.py index ea13279d..78fd3ac9 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/database.py b/arango/database.py index 20e771d2..766161df 100644 --- a/arango/database.py +++ b/arango/database.py @@ -45,6 +45,7 @@ PermissionResetError, PermissionUpdateError, ServerAvailableOptionsGetError, + ServerCheckAvailabilityError, ServerCurrentOptionsGetError, ServerDetailsError, ServerEchoError, @@ -445,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", @@ -481,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, @@ -1069,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") diff --git a/arango/exceptions.py b/arango/exceptions.py index 789468ed..891c813e 100644 --- a/arango/exceptions.py +++ b/arango/exceptions.py @@ -654,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 8735afe6..4bb135a5 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.1" + 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 4f6ce35e..734a3168 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/tests/test_database.py b/tests/test_database.py index 014f0235..4e8a160e 100644 --- a/tests/test_database.py +++ b/tests/test_database.py @@ -20,6 +20,7 @@ DatabaseListError, DatabasePropertiesError, DatabaseSupportInfoError, + ServerCheckAvailabilityError, ServerDetailsError, ServerEchoError, ServerEngineError, @@ -355,6 +356,11 @@ def test_database_misc_methods(client, sys_db, db, bad_db, cluster, secret, db_v 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