|
| 1 | +# TODO __all__ = [] |
| 2 | + |
| 3 | +from abc import ABC, abstractmethod |
| 4 | +from typing import Optional |
| 5 | + |
| 6 | +from aiohttp import BaseConnector, BasicAuth, ClientSession, ClientTimeout |
| 7 | +from request import Request |
| 8 | +from response import Response |
| 9 | + |
| 10 | + |
| 11 | +class Session(ABC): # pragma: no cover |
| 12 | + """Abstract base class for HTTP sessions.""" |
| 13 | + |
| 14 | + @abstractmethod |
| 15 | + async def request(self, request: Request) -> Response: |
| 16 | + """Send an HTTP request. |
| 17 | +
|
| 18 | + This method must be overridden by the user. |
| 19 | +
|
| 20 | + :param request: HTTP request. |
| 21 | + :type request: arangoasync.request.Request |
| 22 | + :returns: HTTP response. |
| 23 | + :rtype: arangoasync.response.Response |
| 24 | + """ |
| 25 | + raise NotImplementedError |
| 26 | + |
| 27 | + @abstractmethod |
| 28 | + async def close(self) -> None: |
| 29 | + """Close the session. |
| 30 | +
|
| 31 | + This method must be overridden by the user. |
| 32 | + """ |
| 33 | + raise NotImplementedError |
| 34 | + |
| 35 | + |
| 36 | +class HTTPClient(ABC): # pragma: no cover |
| 37 | + """Abstract base class for HTTP clients.""" |
| 38 | + |
| 39 | + @abstractmethod |
| 40 | + def create_session(self, host: str) -> Session: |
| 41 | + """Return a new requests session given the host URL. |
| 42 | +
|
| 43 | + This method must be overridden by the user. |
| 44 | +
|
| 45 | + :param host: ArangoDB host URL. |
| 46 | + :type host: str |
| 47 | + :returns: Requests session object. |
| 48 | + :rtype: arangoasync.http.Session |
| 49 | + """ |
| 50 | + raise NotImplementedError |
| 51 | + |
| 52 | + @abstractmethod |
| 53 | + async def send_request( |
| 54 | + self, |
| 55 | + session: Session, |
| 56 | + url: str, |
| 57 | + request: Request, |
| 58 | + ) -> Response: |
| 59 | + """Send an HTTP request. |
| 60 | +
|
| 61 | + This method must be overridden by the user. |
| 62 | +
|
| 63 | + :param session: Session object. |
| 64 | + :type session: arangoasync.http.Session |
| 65 | + :param url: Request URL. |
| 66 | + :type url: str |
| 67 | + :param request: HTTP request. |
| 68 | + :type request: arangoasync.request.Request |
| 69 | + :returns: HTTP response. |
| 70 | + :rtype: arango.response.Response |
| 71 | + """ |
| 72 | + raise NotImplementedError |
| 73 | + |
| 74 | + |
| 75 | +class DefaultSession(Session): |
| 76 | + """Wrapper on top of an aiohttp.ClientSession.""" |
| 77 | + |
| 78 | + def __init__( |
| 79 | + self, |
| 80 | + host: str, |
| 81 | + connector: BaseConnector, |
| 82 | + timeout: ClientTimeout, |
| 83 | + read_bufsize: int = 2**16, |
| 84 | + auth: Optional[BasicAuth] = None, |
| 85 | + ) -> None: |
| 86 | + """Initialize the session. |
| 87 | +
|
| 88 | + :param host: ArangoDB coordinator URL (eg http://localhost:8530). |
| 89 | + :type host: str |
| 90 | + :param connector: Supports connection pooling. |
| 91 | + :type connector: aiohttp.BaseConnector |
| 92 | + :param timeout: Request timeout settings. |
| 93 | + :type timeout: aiohttp.ClientTimeout |
| 94 | + :param read_bufsize: Size of read buffer. 64 Kib by default. |
| 95 | + :type read_bufsize: int |
| 96 | + :param auth: HTTP Authorization. |
| 97 | + :type auth: aiohttp.BasicAuth | None |
| 98 | + """ |
| 99 | + self._session = ClientSession( |
| 100 | + base_url=host, |
| 101 | + connector=connector, |
| 102 | + timeout=timeout, |
| 103 | + auth=auth, |
| 104 | + read_bufsize=read_bufsize, |
| 105 | + connector_owner=False, |
| 106 | + auto_decompress=True, |
| 107 | + ) |
| 108 | + |
| 109 | + async def request(self, request: Request) -> Response: |
| 110 | + """Send an HTTP request. |
| 111 | +
|
| 112 | + :param request: HTTP request. |
| 113 | + :type request: arangoasync.request.Request |
| 114 | + :returns: HTTP response. |
| 115 | + :rtype: arangoasync.response.Response |
| 116 | + """ |
| 117 | + method = request.method |
| 118 | + endpoint = request.endpoint |
| 119 | + headers = request.headers |
| 120 | + params = request.params |
| 121 | + data = request.data |
| 122 | + |
| 123 | + async with self._session.request( |
| 124 | + method.name, |
| 125 | + endpoint, |
| 126 | + headers=headers, |
| 127 | + params=params, |
| 128 | + data=data, |
| 129 | + ) as response: |
| 130 | + raw_body = await response.read() |
| 131 | + return Response( |
| 132 | + method=method, |
| 133 | + url=str(response.real_url), |
| 134 | + headers=response.headers, |
| 135 | + status_code=response.status, |
| 136 | + status_text=response.reason, |
| 137 | + raw_body=raw_body, |
| 138 | + ) |
| 139 | + |
| 140 | + async def close(self) -> None: |
| 141 | + """Close the session.""" |
| 142 | + await self._session.close() |
| 143 | + |
| 144 | + |
| 145 | +# TODO implement DefaultHTTPClient |
0 commit comments