|
2 | 2 |
|
3 | 3 | HTTP
|
4 | 4 | ----
|
| 5 | + |
| 6 | +You can define your own HTTP client for sending requests to |
| 7 | +ArangoDB server. The default implementation uses the aiohttp_ library. |
| 8 | + |
| 9 | +Your HTTP client must inherit :class:`arangoasync.http.HTTPClient` and implement the |
| 10 | +following abstract methods: |
| 11 | + |
| 12 | +* :func:`arangoasync.http.HTTPClient.create_session` |
| 13 | +* :func:`arangoasync.http.HTTPClient.close_session` |
| 14 | +* :func:`arangoasync.http.HTTPClient.send_request` |
| 15 | + |
| 16 | +Let's take for example, the default implementation of :class:`arangoasync.http.AioHTTPClient`: |
| 17 | + |
| 18 | +* The **create_session** method returns a :class:`aiohttp.ClientSession` instance per |
| 19 | + connected host (coordinator). The session objects are stored in the client. |
| 20 | +* The **close_session** method performs the necessary cleanup for a :class:`aiohttp.ClientSession` instance. |
| 21 | + This is usually called only by the client. |
| 22 | +* The **send_request** method must uses the session to send an HTTP request, and |
| 23 | + returns a fully populated instance of :class:`arangoasync.response.Response`. |
| 24 | + |
| 25 | +**Example:** |
| 26 | + |
| 27 | +Suppose you're working on a project that uses httpx_ as a dependency and you want your |
| 28 | +own HTTP client implementation on top of :class:`httpx.AsyncClient`. Your ``HttpxHTTPClient`` |
| 29 | +class might look something like this: |
| 30 | + |
| 31 | +.. code-block:: python |
| 32 | +
|
| 33 | + import httpx |
| 34 | + import ssl |
| 35 | + from typing import Any, Optional |
| 36 | + from arangoasync.exceptions import ClientConnectionError |
| 37 | + from arangoasync.http import HTTPClient |
| 38 | + from arangoasync.request import Request |
| 39 | + from arangoasync.response import Response |
| 40 | +
|
| 41 | + class HttpxHTTPClient(HTTPClient): |
| 42 | + """HTTP client implementation on top of httpx.AsyncClient. |
| 43 | +
|
| 44 | + Args: |
| 45 | + limits (httpx.Limits | None): Connection pool limits.n |
| 46 | + timeout (httpx.Timeout | float | None): Request timeout settings. |
| 47 | + ssl_context (ssl.SSLContext | bool): SSL validation mode. |
| 48 | + `True` (default) uses httpx’s default validation (system CAs). |
| 49 | + `False` disables SSL checks. |
| 50 | + Or pass a custom `ssl.SSLContext`. |
| 51 | + """ |
| 52 | +
|
| 53 | + def __init__( |
| 54 | + self, |
| 55 | + limits: Optional[httpx.Limits] = None, |
| 56 | + timeout: Optional[httpx.Timeout | float] = None, |
| 57 | + ssl_context: bool | ssl.SSLContext = True, |
| 58 | + ) -> None: |
| 59 | + self._limits = limits or httpx.Limits( |
| 60 | + max_connections=100, |
| 61 | + max_keepalive_connections=20 |
| 62 | + ) |
| 63 | + self._timeout = timeout or httpx.Timeout(300.0, connect=60.0) |
| 64 | + if ssl_context is True: |
| 65 | + self._verify: bool | ssl.SSLContext = True |
| 66 | + elif ssl_context is False: |
| 67 | + self._verify = False |
| 68 | + else: |
| 69 | + self._verify = ssl_context |
| 70 | +
|
| 71 | + def create_session(self, host: str) -> httpx.AsyncClient: |
| 72 | + return httpx.AsyncClient( |
| 73 | + base_url=host, |
| 74 | + limits=self._limits, |
| 75 | + timeout=self._timeout, |
| 76 | + verify=self._verify, |
| 77 | + ) |
| 78 | +
|
| 79 | + async def close_session(self, session: httpx.AsyncClient) -> None: |
| 80 | + await session.aclose() |
| 81 | +
|
| 82 | + async def send_request( |
| 83 | + self, |
| 84 | + session: httpx.AsyncClient, |
| 85 | + request: Request, |
| 86 | + ) -> Response: |
| 87 | + auth: Any = None |
| 88 | + if request.auth is not None: |
| 89 | + auth = httpx.BasicAuth( |
| 90 | + username=request.auth.username, |
| 91 | + password=request.auth.password, |
| 92 | + ) |
| 93 | +
|
| 94 | + try: |
| 95 | + resp = await session.request( |
| 96 | + method=request.method.name, |
| 97 | + url=request.endpoint, |
| 98 | + headers=request.normalized_headers(), |
| 99 | + params=request.normalized_params(), |
| 100 | + content=request.data, |
| 101 | + auth=auth, |
| 102 | + ) |
| 103 | + raw_body = resp.content |
| 104 | + return Response( |
| 105 | + method=request.method, |
| 106 | + url=str(resp.url), |
| 107 | + headers=resp.headers, |
| 108 | + status_code=resp.status_code, |
| 109 | + status_text=resp.reason_phrase, |
| 110 | + raw_body=raw_body, |
| 111 | + ) |
| 112 | + except httpx.HTTPError as e: |
| 113 | + raise ClientConnectionError(str(e)) from e |
| 114 | +
|
| 115 | +Then you would inject your client as follows: |
| 116 | + |
| 117 | +.. code-block:: python |
| 118 | +
|
| 119 | + from arangoasync import ArangoClient |
| 120 | + from arangoasync.auth import Auth |
| 121 | +
|
| 122 | + # Initialize the client for ArangoDB. |
| 123 | + async with ArangoClient( |
| 124 | + hosts="http://localhost:8529", |
| 125 | + http_client=HttpxHTTPClient(), |
| 126 | + ) as client: |
| 127 | + auth = Auth(username="root", password="passwd") |
| 128 | +
|
| 129 | + # Connect to "test" database as root user. |
| 130 | + db = await client.db("test", auth=auth, verify=True) |
| 131 | +
|
| 132 | + # List all collections. |
| 133 | + cols = await db.collections() |
| 134 | +
|
| 135 | +.. _aiohttp: https://docs.aiohttp.org/en/stable/ |
| 136 | +.. _httpx: https://www.python-httpx.org/ |
0 commit comments