Skip to content

Commit 616d97b

Browse files
committed
Finishing foxx API
1 parent e9731a5 commit 616d97b

File tree

3 files changed

+260
-1
lines changed

3 files changed

+260
-1
lines changed

arangoasync/exceptions.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,10 @@ class FoxxConfigUpdateError(ArangoServerError):
407407
"""Failed to update Foxx service configuration."""
408408

409409

410+
class FoxxCommitError(ArangoServerError):
411+
"""Failed to commit local Foxx service state."""
412+
413+
410414
class FoxxDependencyGetError(ArangoServerError):
411415
"""Failed to retrieve Foxx service dependencies."""
412416

@@ -423,6 +427,22 @@ class FoxxScriptListError(ArangoServerError):
423427
"""Failed to retrieve Foxx service scripts."""
424428

425429

430+
class FoxxDevModeEnableError(ArangoServerError):
431+
"""Failed to enable development mode for Foxx service."""
432+
433+
434+
class FoxxDevModeDisableError(ArangoServerError):
435+
"""Failed to disable development mode for Foxx service."""
436+
437+
438+
class FoxxDownloadError(ArangoServerError):
439+
"""Failed to download Foxx service bundle."""
440+
441+
442+
class FoxxReadmeGetError(ArangoServerError):
443+
"""Failed to retrieve Foxx service readme."""
444+
445+
426446
class FoxxScriptRunError(ArangoServerError):
427447
"""Failed to run Foxx service script."""
428448

@@ -451,6 +471,10 @@ class FoxxServiceUpdateError(ArangoServerError):
451471
"""Failed to update Foxx service."""
452472

453473

474+
class FoxxSwaggerGetError(ArangoServerError):
475+
"""Failed to retrieve Foxx service swagger."""
476+
477+
454478
class FoxxTestRunError(ArangoServerError):
455479
"""Failed to run Foxx service tests."""
456480

arangoasync/foxx.py

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,17 @@
33
from typing import Any, Optional
44

55
from arangoasync.exceptions import (
6+
FoxxCommitError,
67
FoxxConfigGetError,
78
FoxxConfigReplaceError,
89
FoxxConfigUpdateError,
910
FoxxDependencyGetError,
1011
FoxxDependencyReplaceError,
1112
FoxxDependencyUpdateError,
13+
FoxxDevModeDisableError,
14+
FoxxDevModeEnableError,
15+
FoxxDownloadError,
16+
FoxxReadmeGetError,
1217
FoxxScriptListError,
1318
FoxxScriptRunError,
1419
FoxxServiceCreateError,
@@ -17,6 +22,7 @@
1722
FoxxServiceListError,
1823
FoxxServiceReplaceError,
1924
FoxxServiceUpdateError,
25+
FoxxSwaggerGetError,
2026
FoxxTestRunError,
2127
)
2228
from arangoasync.executor import ApiExecutor
@@ -638,3 +644,186 @@ def response_handler(resp: Response) -> str:
638644
return resp.raw_body.decode("utf-8")
639645

640646
return await self._executor.execute(request, response_handler)
647+
648+
async def enable_development(self, mount: str) -> Result[Json]:
649+
"""Puts the service into development mode.
650+
651+
While the service is running in development mode, it is reloaded from
652+
the file system, and its setup script (if any) is re-executed every
653+
time the service handles a request.
654+
655+
In a cluster with multiple coordinators, changes to the filesystem on
656+
one coordinator is not reflected across other coordinators.
657+
658+
Args:
659+
mount (str): Service mount path.
660+
661+
Returns:
662+
dict: Service metadata.
663+
664+
Raises:
665+
FoxxDevModeEnableError: If the operation fails.
666+
667+
References:
668+
- `enable-the-development-mode <https://docs.arangodb.com/stable/develop/http-api/foxx/#enable-the-development-mode>`__
669+
""" # noqa: E501
670+
request = Request(
671+
method=Method.POST,
672+
endpoint="/_api/foxx/development",
673+
params={"mount": mount},
674+
)
675+
676+
def response_handler(resp: Response) -> Json:
677+
if not resp.is_success:
678+
raise FoxxDevModeEnableError(resp, request)
679+
result: Json = self.deserializer.loads(resp.raw_body)
680+
return result
681+
682+
return await self._executor.execute(request, response_handler)
683+
684+
async def disable_development(self, mount: str) -> Result[Json]:
685+
"""Puts the service into production mode.
686+
687+
In a cluster with multiple coordinators, the services on all other
688+
coordinators are replaced with the version on the calling coordinator.
689+
690+
Args:
691+
mount (str): Service mount path.
692+
693+
Returns:
694+
dict: Service metadata.
695+
696+
Raises:
697+
FoxxDevModeDisableError: If the operation fails.
698+
699+
References:
700+
- `disable-the-development-mode <https://docs.arangodb.com/stable/develop/http-api/foxx/#disable-the-development-mode>`__
701+
""" # noqa: E501
702+
request = Request(
703+
method=Method.DELETE,
704+
endpoint="/_api/foxx/development",
705+
params={"mount": mount},
706+
)
707+
708+
def response_handler(resp: Response) -> Json:
709+
if not resp.is_success:
710+
raise FoxxDevModeDisableError(resp, request)
711+
result: Json = self.deserializer.loads(resp.raw_body)
712+
return result
713+
714+
return await self._executor.execute(request, response_handler)
715+
716+
async def readme(self, mount: str) -> Result[str]:
717+
"""Return the service readme.
718+
719+
Args:
720+
mount (str): Service mount path.
721+
722+
Returns:
723+
str: Service readme content.
724+
725+
Raises:
726+
FoxxReadmeGetError: If retrieval fails.
727+
728+
References:
729+
- `get-the-service-readme <https://docs.arangodb.com/stable/develop/http-api/foxx/#get-the-service-readme>`__
730+
""" # noqa: E501
731+
request = Request(
732+
method=Method.GET,
733+
endpoint="/_api/foxx/readme",
734+
params={"mount": mount},
735+
)
736+
737+
def response_handler(resp: Response) -> str:
738+
if not resp.is_success:
739+
raise FoxxReadmeGetError(resp, request)
740+
return resp.raw_body.decode("utf-8")
741+
742+
return await self._executor.execute(request, response_handler)
743+
744+
async def swagger(self, mount: str) -> Result[Json]:
745+
"""Return the Swagger API description for the given service.
746+
747+
Args:
748+
mount (str): Service mount path.
749+
750+
Returns:
751+
dict: Swagger API description.
752+
753+
Raises:
754+
FoxxSwaggerGetError: If retrieval fails.
755+
756+
References:
757+
- `get-the-swagger-description <https://docs.arangodb.com/stable/develop/http-api/foxx/#get-the-swagger-description>`__
758+
""" # noqa: E501
759+
request = Request(
760+
method=Method.GET, endpoint="/_api/foxx/swagger", params={"mount": mount}
761+
)
762+
763+
def response_handler(resp: Response) -> Json:
764+
if not resp.is_success:
765+
raise FoxxSwaggerGetError(resp, request)
766+
result: Json = self.deserializer.loads(resp.raw_body)
767+
return result
768+
769+
return await self._executor.execute(request, response_handler)
770+
771+
async def download(self, mount: str) -> Result[bytes]:
772+
"""Downloads a zip bundle of the service directory.
773+
774+
When development mode is enabled, this always creates a new bundle.
775+
Otherwise, the bundle will represent the version of a service that is
776+
installed on that ArangoDB instance.
777+
778+
Args:
779+
mount (str): Service mount path.
780+
781+
Returns:
782+
bytes: Service bundle zip in raw bytes form.
783+
784+
Raises:
785+
FoxxDownloadError: If download fails.
786+
787+
References:
788+
- `download-a-service-bundle <https://docs.arangodb.com/stable/develop/http-api/foxx/#download-a-service-bundle>`__
789+
""" # noqa: E501
790+
request = Request(
791+
method=Method.POST, endpoint="/_api/foxx/download", params={"mount": mount}
792+
)
793+
794+
def response_handler(resp: Response) -> bytes:
795+
if not resp.is_success:
796+
raise FoxxDownloadError(resp, request)
797+
return resp.raw_body
798+
799+
return await self._executor.execute(request, response_handler)
800+
801+
async def commit(self, replace: Optional[bool] = None) -> None:
802+
"""Commit local service state of the coordinator to the database.
803+
804+
This can be used to resolve service conflicts between coordinators
805+
that cannot be fixed automatically due to missing data.
806+
807+
Args:
808+
replace (bool | None): If set to `True`, any existing service files in the database
809+
will be overwritten.
810+
811+
Raises:
812+
FoxxCommitError: If commit fails.
813+
814+
References:
815+
- `commit-the-local-service-state <https://docs.arangodb.com/stable/develop/http-api/foxx/#commit-the-local-service-state>`__
816+
""" # noqa: E501
817+
params: Params = {}
818+
if replace is not None:
819+
params["replace"] = replace
820+
821+
request = Request(
822+
method=Method.POST, endpoint="/_api/foxx/commit", params=params
823+
)
824+
825+
def response_handler(resp: Response) -> None:
826+
if not resp.is_success:
827+
raise FoxxCommitError(resp, request)
828+
829+
await self._executor.execute(request, response_handler)

tests/test_foxx.py

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,22 @@
1+
import asyncio
12
import json
23

34
import aiofiles
45
import aiohttp
56
import pytest
67

78
from arangoasync.exceptions import (
9+
FoxxCommitError,
810
FoxxConfigGetError,
911
FoxxConfigReplaceError,
1012
FoxxConfigUpdateError,
1113
FoxxDependencyGetError,
1214
FoxxDependencyReplaceError,
1315
FoxxDependencyUpdateError,
16+
FoxxDevModeDisableError,
17+
FoxxDevModeEnableError,
18+
FoxxDownloadError,
19+
FoxxReadmeGetError,
1420
FoxxScriptListError,
1521
FoxxScriptRunError,
1622
FoxxServiceCreateError,
@@ -19,6 +25,7 @@
1925
FoxxServiceListError,
2026
FoxxServiceReplaceError,
2127
FoxxServiceUpdateError,
28+
FoxxSwaggerGetError,
2229
FoxxTestRunError,
2330
)
2431
from tests.helpers import generate_service_mount
@@ -28,7 +35,7 @@
2835

2936

3037
@pytest.mark.asyncio
31-
async def test_foxx(db, bad_db, cluster):
38+
async def test_foxx(db, bad_db):
3239
# Test errors
3340
with pytest.raises(FoxxServiceGetError):
3441
await bad_db.foxx.service(service_name)
@@ -61,6 +68,18 @@ async def test_foxx(db, bad_db, cluster):
6168
await bad_db.foxx.replace_dependencies(mount="foo", options={})
6269
with pytest.raises(FoxxDependencyUpdateError):
6370
await bad_db.foxx.update_dependencies(mount="foo", options={})
71+
with pytest.raises(FoxxDevModeEnableError):
72+
await bad_db.foxx.enable_development("foo")
73+
with pytest.raises(FoxxDevModeDisableError):
74+
await bad_db.foxx.disable_development("foo")
75+
with pytest.raises(FoxxReadmeGetError):
76+
await bad_db.foxx.readme("foo")
77+
with pytest.raises(FoxxSwaggerGetError):
78+
await bad_db.foxx.swagger("foo")
79+
with pytest.raises(FoxxDownloadError):
80+
await bad_db.foxx.download("foo")
81+
with pytest.raises(FoxxCommitError):
82+
await bad_db.foxx.commit()
6483

6584
services = await db.foxx.services()
6685
assert isinstance(services, list)
@@ -197,3 +216,30 @@ async def test_foxx(db, bad_db, cluster):
197216
# Run tests on missing service
198217
with pytest.raises(FoxxTestRunError):
199218
await db.foxx.run_tests("foo")
219+
220+
# Development mode
221+
result = await db.foxx.enable_development(mount1)
222+
assert result["mount"] == mount1
223+
result = await db.foxx.disable_development(mount1)
224+
assert result["mount"] == mount1
225+
226+
# Readme
227+
result = await db.foxx.readme(mount1)
228+
assert isinstance(result, str)
229+
230+
# Swagger
231+
result = await db.foxx.swagger(mount1)
232+
assert isinstance(result, dict)
233+
234+
# Download service
235+
result = await db.foxx.download(mount1)
236+
assert isinstance(result, bytes)
237+
238+
# Commit
239+
await db.foxx.commit(replace=True)
240+
241+
# Delete remaining services
242+
await asyncio.gather(
243+
db.foxx.delete_service(mount1),
244+
db.foxx.delete_service(mount2),
245+
)

0 commit comments

Comments
 (0)