Skip to content

Commit 3ca5d5d

Browse files
committed
Finishing up Administration API
1 parent e7fa7a9 commit 3ca5d5d

File tree

4 files changed

+246
-9
lines changed

4 files changed

+246
-9
lines changed

arangoasync/database.py

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
CollectionDeleteError,
2828
CollectionKeyGeneratorsError,
2929
CollectionListError,
30+
DatabaseCompactError,
3031
DatabaseCreateError,
3132
DatabaseDeleteError,
3233
DatabaseListError,
@@ -44,12 +45,17 @@
4445
ServerAvailableOptionsGetError,
4546
ServerCheckAvailabilityError,
4647
ServerCurrentOptionsGetError,
48+
ServerEchoError,
4749
ServerEncryptionError,
4850
ServerEngineError,
51+
ServerExecuteError,
4952
ServerLicenseGetError,
5053
ServerLicenseSetError,
5154
ServerModeError,
5255
ServerModeSetError,
56+
ServerReloadRoutingError,
57+
ServerShutdownError,
58+
ServerShutdownProgressError,
5359
ServerStatusError,
5460
ServerTimeError,
5561
ServerTLSError,
@@ -2691,6 +2697,170 @@ def response_handler(resp: Response) -> None:
26912697

26922698
await self._executor.execute(request, response_handler)
26932699

2700+
async def shutdown(self, soft: Optional[bool] = None) -> None:
2701+
"""Initiate server shutdown sequence.
2702+
2703+
Args:
2704+
soft (bool | None): If set to `True`, this initiates a soft shutdown.
2705+
2706+
Raises:
2707+
ServerShutdownError: If the operation fails.
2708+
2709+
References:
2710+
- `start-the-shutdown-sequence <https://docs.arangodb.com/stable/develop/http-api/administration/#start-the-shutdown-sequence>`__
2711+
""" # noqa: E501
2712+
params: Params = {}
2713+
if soft is not None:
2714+
params["soft"] = soft
2715+
2716+
request = Request(
2717+
method=Method.DELETE,
2718+
endpoint="/_admin/shutdown",
2719+
params=params,
2720+
)
2721+
2722+
def response_handler(resp: Response) -> None:
2723+
if not resp.is_success:
2724+
raise ServerShutdownError(resp, request)
2725+
2726+
await self._executor.execute(request, response_handler)
2727+
2728+
async def shutdown_progress(self) -> Result[Json]:
2729+
"""Query the soft shutdown progress.
2730+
2731+
Returns:
2732+
dict: Information about the shutdown progress.
2733+
2734+
Raises:
2735+
ServerShutdownProgressError: If the operation fails.
2736+
2737+
References:
2738+
- `query-the-soft-shutdown-progress <https://docs.arangodb.com/stable/develop/http-api/administration/#query-the-soft-shutdown-progress>`__
2739+
""" # noqa: E501
2740+
request = Request(method=Method.GET, endpoint="/_admin/shutdown")
2741+
2742+
def response_handler(resp: Response) -> Json:
2743+
if not resp.is_success:
2744+
raise ServerShutdownProgressError(resp, request)
2745+
2746+
result: Json = self.deserializer.loads(resp.raw_body)
2747+
return result
2748+
2749+
return await self._executor.execute(request, response_handler)
2750+
2751+
async def compact(
2752+
self,
2753+
change_level: Optional[bool] = None,
2754+
compact_bottom_most_level: Optional[bool] = None,
2755+
) -> None:
2756+
"""Compact all databases. This method requires superuser access.
2757+
2758+
Note:
2759+
This command can cause a full rewrite of all data in all databases,
2760+
which may take very long for large databases.
2761+
2762+
Args:
2763+
change_level (bool | None): Whether or not compacted data should be
2764+
moved to the minimum possible level. Default value is `False`.
2765+
compact_bottom_most_level (bool | None): Whether or not to compact the bottom-most level of data.
2766+
Default value is `False`.
2767+
2768+
Returns:
2769+
dict: Information about the compaction process.
2770+
2771+
Raises:
2772+
DatabaseCompactError: If the operation fails.
2773+
2774+
References:
2775+
- `compact-all-databases <https://docs.arangodb.com/stable/develop/http-api/administration/#compact-all-databases>`__
2776+
""" # noqa: E501
2777+
data = {}
2778+
if change_level is not None:
2779+
data["changeLevel"] = change_level
2780+
if compact_bottom_most_level is not None:
2781+
data["compactBottomMostLevel"] = compact_bottom_most_level
2782+
2783+
request = Request(
2784+
method=Method.PUT,
2785+
endpoint="/_admin/compact",
2786+
data=self.serializer.dumps(data),
2787+
)
2788+
2789+
def response_handler(resp: Response) -> None:
2790+
if not resp.is_success:
2791+
raise DatabaseCompactError(resp, request)
2792+
2793+
await self._executor.execute(request, response_handler)
2794+
2795+
async def reload_routing(self) -> None:
2796+
"""Reload the routing information.
2797+
2798+
Raises:
2799+
ServerReloadRoutingError: If the operation fails.
2800+
2801+
References:
2802+
- `reload-the-routing-table <https://docs.arangodb.com/stable/develop/http-api/administration/#reload-the-routing-table>`__
2803+
""" # noqa: E501
2804+
request = Request(method=Method.POST, endpoint="/_admin/routing/reload")
2805+
2806+
def response_handler(resp: Response) -> None:
2807+
if not resp.is_success:
2808+
raise ServerReloadRoutingError(resp, request)
2809+
2810+
await self._executor.execute(request, response_handler)
2811+
2812+
async def echo(self, body: Optional[Json] = None) -> Result[Json]:
2813+
"""Return an object with the servers request information.
2814+
2815+
Args:
2816+
body (dict | None): Optional body of the request.
2817+
2818+
Returns:
2819+
dict: Details of the request.
2820+
2821+
Raises:
2822+
ServerEchoError: If the operation fails.
2823+
2824+
References:
2825+
- `echo-a-request <https://docs.arangodb.com/stable/develop/http-api/administration/#echo-a-request>`__
2826+
""" # noqa: E501
2827+
data = body if body is not None else {}
2828+
request = Request(method=Method.POST, endpoint="/_admin/echo", data=data)
2829+
2830+
def response_handler(resp: Response) -> Json:
2831+
if not resp.is_success:
2832+
raise ServerEchoError(resp, request)
2833+
result: Json = self.deserializer.loads(resp.raw_body)
2834+
return result
2835+
2836+
return await self._executor.execute(request, response_handler)
2837+
2838+
async def execute(self, command: str) -> Result[Any]:
2839+
"""Execute raw Javascript command on the server.
2840+
2841+
Args:
2842+
command (str): Javascript command to execute.
2843+
2844+
Returns:
2845+
Return value of **command**, if any.
2846+
2847+
Raises:
2848+
ServerExecuteError: If the execution fails.
2849+
2850+
References:
2851+
- `execute-a-script <https://docs.arangodb.com/stable/develop/http-api/administration/#execute-a-script>`__
2852+
""" # noqa: E501
2853+
request = Request(
2854+
method=Method.POST, endpoint="/_admin/execute", data=command.encode("utf-8")
2855+
)
2856+
2857+
def response_handler(resp: Response) -> Any:
2858+
if not resp.is_success:
2859+
raise ServerExecuteError(resp, request)
2860+
return self.deserializer.loads(resp.raw_body)
2861+
2862+
return await self._executor.execute(request, response_handler)
2863+
26942864

26952865
class StandardDatabase(Database):
26962866
"""Standard database API wrapper.

arangoasync/exceptions.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,10 @@ class CursorStateError(ArangoClientError):
319319
"""The cursor object was in a bad state."""
320320

321321

322+
class DatabaseCompactError(ArangoServerError):
323+
"""Failed to compact databases."""
324+
325+
322326
class DatabaseCreateError(ArangoServerError):
323327
"""Failed to create database."""
324328

@@ -567,6 +571,10 @@ class ServerCurrentOptionsGetError(ArangoServerError):
567571
"""Failed to retrieve currently-set server options."""
568572

569573

574+
class ServerEchoError(ArangoServerError):
575+
"""Failed to retrieve details on last request."""
576+
577+
570578
class ServerEncryptionError(ArangoServerError):
571579
"""Failed to reload user-defined encryption keys."""
572580

@@ -575,6 +583,10 @@ class ServerEngineError(ArangoServerError):
575583
"""Failed to retrieve database engine."""
576584

577585

586+
class ServerExecuteError(ArangoServerError):
587+
"""Failed to execute raw JavaScript command."""
588+
589+
578590
class ServerModeError(ArangoServerError):
579591
"""Failed to retrieve server mode."""
580592

@@ -591,6 +603,18 @@ class ServerLicenseSetError(ArangoServerError):
591603
"""Failed to set server license."""
592604

593605

606+
class ServerReloadRoutingError(ArangoServerError):
607+
"""Failed to reload routing details."""
608+
609+
610+
class ServerShutdownError(ArangoServerError):
611+
"""Failed to initiate shutdown sequence."""
612+
613+
614+
class ServerShutdownProgressError(ArangoServerError):
615+
"""Failed to retrieve soft shutdown progress."""
616+
617+
594618
class ServerStatusError(ArangoServerError):
595619
"""Failed to retrieve server status."""
596620

docs/admin.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,9 @@ Most of these operations can only be performed by admin users via the
3939
4040
# Return whether or not a server is in read-only mode
4141
mode = await sys_db.mode()
42+
43+
# Get license information
44+
license = await sys_db.license()
45+
46+
# Execute Javascript on the server
47+
result = await sys_db.execute("return 1")

tests/test_database.py

Lines changed: 46 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@
44
import pytest
55
from packaging import version
66

7+
from arangoasync.client import ArangoClient
78
from arangoasync.collection import StandardCollection
89
from arangoasync.exceptions import (
910
CollectionCreateError,
1011
CollectionDeleteError,
1112
CollectionKeyGeneratorsError,
1213
CollectionListError,
14+
DatabaseCompactError,
1315
DatabaseCreateError,
1416
DatabaseDeleteError,
1517
DatabaseListError,
@@ -20,11 +22,16 @@
2022
ServerAvailableOptionsGetError,
2123
ServerCheckAvailabilityError,
2224
ServerCurrentOptionsGetError,
25+
ServerEchoError,
2326
ServerEngineError,
27+
ServerExecuteError,
2428
ServerLicenseGetError,
2529
ServerLicenseSetError,
2630
ServerModeError,
2731
ServerModeSetError,
32+
ServerReloadRoutingError,
33+
ServerShutdownError,
34+
ServerShutdownProgressError,
2835
ServerStatusError,
2936
ServerTimeError,
3037
ServerVersionError,
@@ -34,7 +41,9 @@
3441

3542

3643
@pytest.mark.asyncio
37-
async def test_database_misc_methods(sys_db, db, bad_db, cluster, db_version):
44+
async def test_database_misc_methods(
45+
sys_db, db, bad_db, cluster, db_version, url, sys_db_name, token
46+
):
3847
# Status
3948
status = await sys_db.status()
4049
assert status["server"] == "arango"
@@ -95,14 +104,15 @@ async def test_database_misc_methods(sys_db, db, bad_db, cluster, db_version):
95104
info = await sys_db.support_info()
96105
assert isinstance(info, dict)
97106

98-
with pytest.raises(ServerCurrentOptionsGetError):
99-
await bad_db.options()
100-
options = await sys_db.options()
101-
assert isinstance(options, dict)
102-
with pytest.raises(ServerAvailableOptionsGetError):
103-
await bad_db.options_available()
104-
options_available = await sys_db.options_available()
105-
assert isinstance(options_available, dict)
107+
if db_version >= version.parse("3.12.0"):
108+
with pytest.raises(ServerCurrentOptionsGetError):
109+
await bad_db.options()
110+
options = await sys_db.options()
111+
assert isinstance(options, dict)
112+
with pytest.raises(ServerAvailableOptionsGetError):
113+
await bad_db.options_available()
114+
options_available = await sys_db.options_available()
115+
assert isinstance(options_available, dict)
106116

107117
with pytest.raises(ServerModeError):
108118
await bad_db.mode()
@@ -120,6 +130,33 @@ async def test_database_misc_methods(sys_db, db, bad_db, cluster, db_version):
120130
with pytest.raises(ServerLicenseSetError):
121131
await sys_db.set_license('"abc"')
122132

133+
with pytest.raises(ServerShutdownError):
134+
await bad_db.shutdown()
135+
with pytest.raises(ServerShutdownProgressError):
136+
await bad_db.shutdown_progress()
137+
138+
with pytest.raises(ServerReloadRoutingError):
139+
await bad_db.reload_routing()
140+
await sys_db.reload_routing()
141+
142+
with pytest.raises(ServerEchoError):
143+
await bad_db.echo()
144+
result = await sys_db.echo()
145+
assert isinstance(result, dict)
146+
147+
with pytest.raises(ServerExecuteError):
148+
await bad_db.execute("return 1")
149+
result = await sys_db.execute("return 1")
150+
assert result == 1
151+
152+
with pytest.raises(DatabaseCompactError):
153+
await bad_db.compact()
154+
async with ArangoClient(hosts=url) as client:
155+
db = await client.db(
156+
sys_db_name, auth_method="superuser", token=token, verify=True
157+
)
158+
await db.compact()
159+
123160

124161
@pytest.mark.asyncio
125162
async def test_create_drop_database(

0 commit comments

Comments
 (0)