From 8d07827f19264acd8a29121100d97f50d65433aa Mon Sep 17 00:00:00 2001 From: Alex Petenchea Date: Sun, 22 Sep 2024 19:53:23 +0300 Subject: [PATCH] Introducing Database class --- arangoasync/client.py | 8 +++--- arangoasync/database.py | 52 +++++++++++++++++++++++++++++++++++---- arangoasync/exceptions.py | 4 +++ arangoasync/executor.py | 43 ++++++++++++++++++++++++++++++++ tests/test_client.py | 4 +-- tests/test_database.py | 15 +++++++++++ 6 files changed, 115 insertions(+), 11 deletions(-) create mode 100644 arangoasync/executor.py create mode 100644 tests/test_database.py diff --git a/arangoasync/client.py b/arangoasync/client.py index 83b2f67..464501e 100644 --- a/arangoasync/client.py +++ b/arangoasync/client.py @@ -11,7 +11,7 @@ JwtConnection, JwtSuperuserConnection, ) -from arangoasync.database import Database +from arangoasync.database import StandardDatabase from arangoasync.http import DefaultHTTPClient, HTTPClient from arangoasync.resolver import HostResolver, get_resolver from arangoasync.version import __version__ @@ -124,7 +124,7 @@ async def db( token: Optional[JwtToken] = None, verify: bool = False, compression: Optional[CompressionManager] = None, - ) -> Database: + ) -> StandardDatabase: """Connects to a database and returns and API wrapper. Args: @@ -147,7 +147,7 @@ async def db( client-level compression settings. Returns: - Database: Database API wrapper. + StandardDatabase: Database API wrapper. Raises: ValueError: If the authentication is invalid. @@ -198,4 +198,4 @@ async def db( if verify: await connection.ping() - return Database(connection) + return StandardDatabase(connection) diff --git a/arangoasync/database.py b/arangoasync/database.py index 8a6e52a..51ac136 100644 --- a/arangoasync/database.py +++ b/arangoasync/database.py @@ -1,17 +1,59 @@ __all__ = [ "Database", + "StandardDatabase", ] -from arangoasync.connection import BaseConnection +import json +from typing import Any + +from arangoasync.connection import Connection +from arangoasync.exceptions import ServerStatusError +from arangoasync.executor import ApiExecutor, DefaultApiExecutor +from arangoasync.request import Method, Request +from arangoasync.response import Response class Database: """Database API.""" - def __init__(self, connection: BaseConnection) -> None: - self._conn = connection + def __init__(self, executor: ApiExecutor) -> None: + self._executor = executor @property - def conn(self) -> BaseConnection: + def connection(self) -> Connection: """Return the HTTP connection.""" - return self._conn + return self._executor.connection + + @property + def name(self) -> str: + """Return the name of the current database.""" + return self.connection.db_name + + # TODO - user real return type + async def status(self) -> Any: + """Query the server status. + + Returns: + Json: Server status. + + Raises: + ServerSatusError: If retrieval fails. + """ + request = Request(method=Method.GET, endpoint="/_admin/status") + + # TODO + # - introduce specific return type for response_handler + # - introduce specific serializer and deserializer + def response_handler(resp: Response) -> Any: + if not resp.is_success: + raise ServerStatusError(resp, request) + return json.loads(resp.raw_body) + + return await self._executor.execute(request, response_handler) + + +class StandardDatabase(Database): + """Standard database API wrapper.""" + + def __init__(self, connection: Connection) -> None: + super().__init__(DefaultApiExecutor(connection)) diff --git a/arangoasync/exceptions.py b/arangoasync/exceptions.py index b0cd62c..e2f0100 100644 --- a/arangoasync/exceptions.py +++ b/arangoasync/exceptions.py @@ -86,3 +86,7 @@ class JWTRefreshError(ArangoClientError): class ServerConnectionError(ArangoServerError): """Failed to connect to ArangoDB server.""" + + +class ServerStatusError(ArangoServerError): + """Failed to retrieve server status.""" diff --git a/arangoasync/executor.py b/arangoasync/executor.py new file mode 100644 index 0000000..a07479d --- /dev/null +++ b/arangoasync/executor.py @@ -0,0 +1,43 @@ +from typing import Callable, TypeVar + +from arangoasync.connection import Connection +from arangoasync.request import Request +from arangoasync.response import Response + +T = TypeVar("T") + + +class DefaultApiExecutor: + """Default API executor. + + Responsible for executing requests and handling responses. + + Args: + connection: HTTP connection. + """ + + def __init__(self, connection: Connection) -> None: + self._conn = connection + + @property + def connection(self) -> Connection: + return self._conn + + @property + def context(self) -> str: + return "default" + + async def execute( + self, request: Request, response_handler: Callable[[Response], T] + ) -> T: + """Execute the request and handle the response. + + Args: + request: HTTP request. + response_handler: HTTP response handler. + """ + response = await self._conn.send_request(request) + return response_handler(response) + + +ApiExecutor = DefaultApiExecutor diff --git a/tests/test_client.py b/tests/test_client.py index f8fc7a7..580a61b 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -55,7 +55,7 @@ def mock_method(*args, **kwargs): @pytest.mark.asyncio -async def test_client_bad_auth_method(url, sys_db_name, root, password): +async def test_client_bad_auth_method(url, sys_db_name): async with ArangoClient(hosts=url) as client: with pytest.raises(ValueError): await client.db(sys_db_name, auth_method="invalid") @@ -89,7 +89,7 @@ async def test_client_jwt_auth(url, sys_db_name, root, password): # successful authentication with auth only async with ArangoClient(hosts=url) as client: db = await client.db(sys_db_name, auth_method="jwt", auth=auth, verify=True) - token = db.conn.token + token = db.connection.token # successful authentication with token only async with ArangoClient(hosts=url) as client: diff --git a/tests/test_database.py b/tests/test_database.py new file mode 100644 index 0000000..d25c42b --- /dev/null +++ b/tests/test_database.py @@ -0,0 +1,15 @@ +import pytest + +from arangoasync.auth import Auth +from arangoasync.client import ArangoClient + + +@pytest.mark.asyncio +async def test_client_basic_auth(url, sys_db_name, root, password): + auth = Auth(username=root, password=password) + + # TODO create a test database and user + async with ArangoClient(hosts=url) as client: + db = await client.db(sys_db_name, auth_method="basic", auth=auth, verify=True) + status = await db.status() + assert status["server"] == "arango"