-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
322390a
commit ba3d617
Showing
4 changed files
with
360 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
# TODO __all__ = [] | ||
|
||
from abc import ABC, abstractmethod | ||
from typing import Optional | ||
|
||
from aiohttp import BaseConnector, BasicAuth, ClientSession, ClientTimeout | ||
from request import Request | ||
from response import Response | ||
|
||
|
||
class Session(ABC): # pragma: no cover | ||
"""Abstract base class for HTTP sessions.""" | ||
|
||
@abstractmethod | ||
async def request(self, request: Request) -> Response: | ||
"""Send an HTTP request. | ||
This method must be overridden by the user. | ||
:param request: HTTP request. | ||
:type request: arangoasync.request.Request | ||
:returns: HTTP response. | ||
:rtype: arangoasync.response.Response | ||
""" | ||
raise NotImplementedError | ||
|
||
@abstractmethod | ||
async def close(self) -> None: | ||
"""Close the session. | ||
This method must be overridden by the user. | ||
""" | ||
raise NotImplementedError | ||
|
||
|
||
class HTTPClient(ABC): # pragma: no cover | ||
"""Abstract base class for HTTP clients.""" | ||
|
||
@abstractmethod | ||
def create_session(self, host: str) -> Session: | ||
"""Return a new requests session given the host URL. | ||
This method must be overridden by the user. | ||
:param host: ArangoDB host URL. | ||
:type host: str | ||
:returns: Requests session object. | ||
:rtype: arangoasync.http.Session | ||
""" | ||
raise NotImplementedError | ||
|
||
@abstractmethod | ||
async def send_request( | ||
self, | ||
session: Session, | ||
url: str, | ||
request: Request, | ||
) -> Response: | ||
"""Send an HTTP request. | ||
This method must be overridden by the user. | ||
:param session: Session object. | ||
:type session: arangoasync.http.Session | ||
:param url: Request URL. | ||
:type url: str | ||
:param request: HTTP request. | ||
:type request: arangoasync.request.Request | ||
:returns: HTTP response. | ||
:rtype: arango.response.Response | ||
""" | ||
raise NotImplementedError | ||
|
||
|
||
class DefaultSession(Session): | ||
"""Wrapper on top of an aiohttp.ClientSession.""" | ||
|
||
def __init__( | ||
self, | ||
host: str, | ||
connector: BaseConnector, | ||
timeout: ClientTimeout, | ||
read_bufsize: int = 2**16, | ||
auth: Optional[BasicAuth] = None, | ||
) -> None: | ||
"""Initialize the session. | ||
:param host: ArangoDB coordinator URL (eg http://localhost:8530). | ||
:type host: str | ||
:param connector: Supports connection pooling. | ||
:type connector: aiohttp.BaseConnector | ||
:param timeout: Request timeout settings. | ||
:type timeout: aiohttp.ClientTimeout | ||
:param read_bufsize: Size of read buffer. 64 Kib by default. | ||
:type read_bufsize: int | ||
:param auth: HTTP Authorization. | ||
:type auth: aiohttp.BasicAuth | None | ||
""" | ||
self._session = ClientSession( | ||
base_url=host, | ||
connector=connector, | ||
timeout=timeout, | ||
auth=auth, | ||
read_bufsize=read_bufsize, | ||
connector_owner=False, | ||
auto_decompress=True, | ||
) | ||
|
||
async def request(self, request: Request) -> Response: | ||
"""Send an HTTP request. | ||
:param request: HTTP request. | ||
:type request: arangoasync.request.Request | ||
:returns: HTTP response. | ||
:rtype: arangoasync.response.Response | ||
""" | ||
method = request.method | ||
endpoint = request.endpoint | ||
headers = request.headers | ||
params = request.params | ||
data = request.data | ||
|
||
async with self._session.request( | ||
method.name, | ||
endpoint, | ||
headers=headers, | ||
params=params, | ||
data=data, | ||
) as response: | ||
raw_body = await response.read() | ||
return Response( | ||
method=method, | ||
url=str(response.real_url), | ||
headers=response.headers, | ||
status_code=response.status, | ||
status_text=response.reason, | ||
raw_body=raw_body, | ||
) | ||
|
||
async def close(self) -> None: | ||
"""Close the session.""" | ||
await self._session.close() | ||
|
||
|
||
# TODO implement DefaultHTTPClient |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
__all__ = [ | ||
"Method", | ||
"Request", | ||
] | ||
|
||
from enum import Enum, auto | ||
from typing import Generic, Optional, TypeVar | ||
|
||
from typings import Headers, Params | ||
from version import __version__ as driver_version | ||
|
||
T = TypeVar("T") | ||
|
||
|
||
class Method(Enum): | ||
"""HTTP methods.""" | ||
|
||
GET = auto() | ||
POST = auto() | ||
PUT = auto() | ||
PATCH = auto() | ||
DELETE = auto() | ||
HEAD = auto() | ||
OPTIONS = auto() | ||
|
||
|
||
class Request(Generic[T]): | ||
"""HTTP request. | ||
:param method: HTTP method. | ||
:type method: request.Method | ||
:param endpoint: API endpoint. | ||
:type endpoint: str | ||
:param headers: Request headers. | ||
:type headers: dict | None | ||
:param params: URL (query) parameters. | ||
:type params: dict | None | ||
:param data: Request payload. | ||
:type data: Any | ||
:param deserialize: Whether the response body should be deserialized. | ||
:type deserialize: bool | ||
:ivar method: HTTP method. | ||
:vartype method: request.Method | ||
:ivar endpoint: API endpoint. | ||
:vartype endpoint: str | ||
:ivar headers: Request headers. | ||
:vartype headers: dict | None | ||
:ivar params: URL (query) parameters. | ||
:vartype params: dict | None | ||
:ivar data: Request payload. | ||
:vartype data: Any | ||
:ivar deserialize: Whether the response body should be deserialized. | ||
:vartype deserialize: bool | ||
""" | ||
|
||
__slots__ = ( | ||
"method", | ||
"endpoint", | ||
"headers", | ||
"params", | ||
"data", | ||
"deserialize", | ||
) | ||
|
||
def __init__( | ||
self, | ||
method: Method, | ||
endpoint: str, | ||
headers: Optional[Headers] = None, | ||
params: Optional[Params] = None, | ||
data: Optional[T] = None, | ||
deserialize: bool = True, | ||
) -> None: | ||
self.method: Method = method | ||
self.endpoint: str = endpoint | ||
self.headers: Headers = self._normalize_headers(headers) | ||
self.params: Params = self._normalize_params(params) | ||
self.data: Optional[T] = data | ||
self.deserialize: bool = deserialize | ||
|
||
@staticmethod | ||
def _normalize_headers(headers: Optional[Headers]) -> Headers: | ||
"""Normalize request headers. | ||
:param headers: Request headers. | ||
:type headers: dict | None | ||
:returns: Normalized request headers. | ||
:rtype: dict | ||
""" | ||
driver_header = f"arangoasync/{driver_version}" | ||
normalized_headers: Headers = { | ||
"charset": "utf-8", | ||
"content-type": "application/json", | ||
"x-arango-driver": driver_header, | ||
} | ||
|
||
if headers is not None: | ||
for key, value in headers.items(): | ||
normalized_headers[key.lower()] = value | ||
|
||
return normalized_headers | ||
|
||
@staticmethod | ||
def _normalize_params(params: Optional[Params]) -> Params: | ||
"""Normalize URL (query) parameters. | ||
:param params: URL (query) parameters. | ||
:type params: dict | None | ||
:returns: Normalized URL (query) parameters. | ||
:rtype: dict | ||
""" | ||
normalized_params: Params = {} | ||
|
||
if params is not None: | ||
for key, value in params.items(): | ||
if isinstance(value, bool): | ||
value = int(value) | ||
normalized_params[key] = str(value) | ||
|
||
return normalized_params |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
from typing import Generic, Optional, TypeVar | ||
|
||
from request import Method | ||
from typings import Headers | ||
|
||
T = TypeVar("T") | ||
|
||
|
||
class Response(Generic[T]): | ||
"""HTTP response. | ||
:param method: HTTP method. | ||
:type method: request.Method | ||
:param url: API URL. | ||
:type url: str | ||
:param headers: Response headers. | ||
:type headers: dict | None | ||
:param status_code: Response status code. | ||
:type status_code: int | ||
:param status_text: Response status text. | ||
:type status_text: str | ||
:param raw_body: Raw response body. | ||
:type raw_body: str | ||
:ivar method: HTTP method. | ||
:vartype method: request.Method | ||
:ivar url: API URL. | ||
:vartype url: str | ||
:ivar headers: Response headers. | ||
:vartype headers: dict | None | ||
:ivar status_code: Response status code. | ||
:vartype status_code: int | ||
:ivar status_text: Response status text. | ||
:vartype status_text: str | ||
:ivar raw_body: Raw response body. | ||
:vartype raw_body: str | ||
:ivar body: Response body after processing. | ||
:vartype body: Any | ||
:ivar error_code: Error code from ArangoDB server. | ||
:vartype error_code: int | ||
:ivar error_message: Error message from ArangoDB server. | ||
:vartype error_message: str | ||
:ivar is_success: True if response status code was 2XX. | ||
:vartype is_success: bool | ||
""" | ||
|
||
__slots__ = ( | ||
"method", | ||
"url", | ||
"headers", | ||
"status_code", | ||
"status_text", | ||
"body", | ||
"raw_body", | ||
"error_code", | ||
"error_message", | ||
"is_success", | ||
) | ||
|
||
def __init__( | ||
self, | ||
method: Method, | ||
url: str, | ||
headers: Headers, | ||
status_code: int, | ||
status_text: str, | ||
raw_body: bytes, | ||
) -> None: | ||
self.method: Method = method | ||
self.url: str = url | ||
self.headers: Headers = headers | ||
self.status_code: int = status_code | ||
self.status_text: str = status_text | ||
self.raw_body: bytes = raw_body | ||
|
||
# Populated later | ||
self.body: Optional[T] = None | ||
self.error_code: Optional[int] = None | ||
self.error_message: Optional[str] = None | ||
self.is_success: Optional[bool] = None |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
__all__ = [ | ||
"Headers", | ||
"Params", | ||
] | ||
|
||
from typing import MutableMapping | ||
|
||
from multidict import MultiDict | ||
|
||
Headers = MutableMapping[str, str] | MultiDict[str] | ||
Headers.__doc__ = """Type definition for HTTP headers""" | ||
|
||
Params = MutableMapping[str, bool | int | str] | ||
Params.__doc__ = """Type definition for URL (query) parameters""" |