Skip to content

Commit 1b308e0

Browse files
committed
AioHTTPClient refined
1 parent 8e3425e commit 1b308e0

File tree

9 files changed

+189
-24
lines changed

9 files changed

+189
-24
lines changed

arangoasync/http.py

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,14 @@
55
]
66

77
from abc import ABC, abstractmethod
8-
from typing import Any, Optional
8+
from typing import Any, Generic, Optional, TypeVar
99

10-
from aiohttp import BaseConnector, BasicAuth, ClientSession, ClientTimeout, TcpConnector
11-
from request import Request
12-
from response import Response
10+
from aiohttp import BaseConnector, BasicAuth, ClientSession, ClientTimeout, TCPConnector
11+
12+
from arangoasync.request import Request
13+
from arangoasync.response import Response
14+
15+
T = TypeVar("T")
1316

1417

1518
class HTTPClient(ABC): # pragma: no cover
@@ -50,7 +53,7 @@ async def send_request(
5053
raise NotImplementedError
5154

5255

53-
class AioHTTPClient(HTTPClient):
56+
class AioHTTPClient(HTTPClient, Generic[T]):
5457
"""HTTP client implemented on top of [aiohttp](https://docs.aiohttp.org/en/stable/).
5558
5659
:param connector: Supports connection pooling.
@@ -79,7 +82,7 @@ def __init__(
7982
auth: Optional[BasicAuth] = None,
8083
compression_threshold: int = 1024,
8184
) -> None:
82-
self._connector = connector or TcpConnector(
85+
self._connector = connector or TCPConnector(
8386
keepalive_timeout=60, # timeout for connection reusing after releasing
8487
limit=100, # total number simultaneous connections
8588
)
@@ -94,13 +97,14 @@ def __init__(
9497
def create_session(self, host: str) -> ClientSession:
9598
"""Return a new session given the base host URL.
9699
97-
:param host: ArangoDB host URL. Typically the address and port of a coordinator,
98-
for example "http://127.0.0.1:8529".
100+
:param host: ArangoDB host URL. Typically, the address and port of a coordinator
101+
(e.g. "http://127.0.0.1:8529").
99102
:type host: str
100103
:returns: Session object.
101104
:rtype: aiohttp.ClientSession
102105
"""
103106
return ClientSession(
107+
base_url=host,
104108
connector=self._connector,
105109
timeout=self._timeout,
106110
auth=self._auth,
@@ -126,7 +130,7 @@ async def send_request(
126130
headers = request.headers
127131
params = request.params
128132
data = request.data
129-
compress = len(data) >= self._compression_threshold
133+
compress = data is not None and len(data) >= self._compression_threshold
130134

131135
async with session.request(
132136
method.name,

arangoasync/request.py

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,10 @@
44
]
55

66
from enum import Enum, auto
7-
from typing import Generic, Optional, TypeVar
7+
from typing import Optional
88

9-
from typings import Headers, Params
10-
from version import __version__
11-
12-
T = TypeVar("T")
9+
from arangoasync.typings import Headers, Params
10+
from arangoasync.version import __version__
1311

1412

1513
class Method(Enum):
@@ -24,7 +22,7 @@ class Method(Enum):
2422
OPTIONS = auto()
2523

2624

27-
class Request(Generic[T]):
25+
class Request:
2826
"""HTTP request.
2927
3028
:param method: HTTP method.
@@ -69,14 +67,14 @@ def __init__(
6967
endpoint: str,
7068
headers: Optional[Headers] = None,
7169
params: Optional[Params] = None,
72-
data: Optional[T] = None,
70+
data: Optional[str] = None,
7371
deserialize: bool = True,
7472
) -> None:
7573
self.method: Method = method
7674
self.endpoint: str = endpoint
7775
self.headers: Headers = self._normalize_headers(headers)
7876
self.params: Params = self._normalize_params(params)
79-
self.data: Optional[T] = data
77+
self.data: Optional[str] = data
8078
self.deserialize: bool = deserialize
8179

8280
@staticmethod

arangoasync/response.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,13 @@
22
"Response",
33
]
44

5-
from typing import Generic, Optional, TypeVar
5+
from typing import Optional
66

7-
from request import Method
8-
from typings import Headers
7+
from arangoasync.request import Method
8+
from arangoasync.typings import Headers
99

10-
T = TypeVar("T")
1110

12-
13-
class Response(Generic[T]):
11+
class Response:
1412
"""HTTP response.
1513
1614
:param method: HTTP method.
@@ -78,7 +76,7 @@ def __init__(
7876
self.raw_body: bytes = raw_body
7977

8078
# Populated later
81-
self.body: Optional[T] = None
79+
self.body: Optional[str] = None
8280
self.error_code: Optional[int] = None
8381
self.error_message: Optional[str] = None
8482
self.is_success: Optional[bool] = None

docs/conf.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
import os
2+
import sys
3+
4+
# Required for autodoc
5+
sys.path.insert(0, os.path.abspath(".."))
6+
17
project = "python-arango-async"
28
copyright_notice = "ArangoDB"
39
author = "Alexandru Petenchea, Anthony Mahanna"
@@ -6,6 +12,14 @@
612
"sphinx.ext.autodoc",
713
"sphinx.ext.doctest",
814
"sphinx.ext.viewcode",
15+
"sphinx.ext.intersphinx",
916
]
1017
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
18+
html_theme = "sphinx_rtd_theme"
1119
master_doc = "index"
20+
21+
autodoc_member_order = "bysource"
22+
23+
intersphinx_mapping = {
24+
"aiohttp": ("https://docs.aiohttp.org/en/stable/", None),
25+
}

docs/index.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,11 @@ Welcome to the documentation for **python-arango-async**, a Python driver for Ar
55

66
**The driver is currently work in progress and not yet ready for use.**
77

8+
Development
9+
10+
.. toctree::
11+
:maxdepth: 1
12+
13+
specs
14+
815
.. _ArangoDB: https://www.arangodb.com

docs/specs.rst

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
API Specification
2+
-----------------
3+
4+
This page contains the specification for all classes and methods available in
5+
python-arango-async.
6+
7+
.. _AioHTTPClient:
8+
9+
AioHTTPClient
10+
=================
11+
12+
.. autoclass:: arangoasync.http.AioHTTPClient
13+
:members:
14+
15+
.. _DefaultHTTPClient:
16+
17+
DefaultHTTPClient
18+
=================
19+
20+
.. autoclass:: arangoasync.http.DefaultHTTPClient
21+
:members:
22+
23+
.. _HTTPClient:
24+
25+
HTTPClient
26+
==========
27+
28+
.. autoclass:: arangoasync.http.HTTPClient
29+
:members:
30+
31+
.. _Method:
32+
33+
Method
34+
=======
35+
36+
.. autoclass:: arangoasync.request.Method
37+
:members:
38+
39+
.. _Request:
40+
41+
Request
42+
=======
43+
44+
.. autoclass:: arangoasync.request.Request
45+
:members:
46+
47+
.. _Response:
48+
49+
Response
50+
========
51+
52+
.. autoclass:: arangoasync.response.Response
53+
:members:

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ dev = [
5454
"mypy>=1.10",
5555
"pre-commit>=3.7",
5656
"pytest>=8.2",
57+
"pytest-asyncio>=0.23.8",
5758
"pytest-cov>=5.0",
5859
"sphinx>=7.3",
5960
"sphinx_rtd_theme",

tests/conftest.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
from dataclasses import dataclass
2+
3+
import pytest
4+
5+
6+
@dataclass
7+
class GlobalData:
8+
url: str = None
9+
root: str = None
10+
password: str = None
11+
12+
13+
global_data = GlobalData()
14+
15+
16+
def pytest_addoption(parser):
17+
parser.addoption(
18+
"--host", action="store", default="127.0.0.1", help="ArangoDB host address"
19+
)
20+
parser.addoption(
21+
"--port", action="append", default=["8529"], help="ArangoDB coordinator ports"
22+
)
23+
parser.addoption(
24+
"--root", action="store", default="root", help="ArangoDB root user"
25+
)
26+
parser.addoption(
27+
"--password", action="store", default="passwd", help="ArangoDB password"
28+
)
29+
30+
31+
def pytest_configure(config):
32+
ports = config.getoption("port")
33+
hosts = [f"http://{config.getoption('host')}:{p}" for p in ports]
34+
url = hosts[0]
35+
36+
global_data.url = url
37+
global_data.root = config.getoption("root")
38+
global_data.password = config.getoption("password")
39+
40+
41+
@pytest.fixture(autouse=False)
42+
def url():
43+
return global_data.url
44+
45+
46+
@pytest.fixture(autouse=False)
47+
def root():
48+
return global_data.root
49+
50+
51+
@pytest.fixture(autouse=False)
52+
def password():
53+
return global_data.password

tests/test_http.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import pytest
2+
from aiohttp import BasicAuth
3+
4+
from arangoasync.http import AioHTTPClient
5+
from arangoasync.request import Method, Request
6+
7+
8+
@pytest.mark.asyncio
9+
async def test_AioHTTPClient_simple_request(url):
10+
client = AioHTTPClient()
11+
session = client.create_session(url)
12+
request = Request(
13+
method=Method.GET,
14+
endpoint="/_api/version",
15+
deserialize=False,
16+
)
17+
response = await client.send_request(session, request)
18+
assert response.method == Method.GET
19+
assert response.url == f"{url}/_api/version"
20+
assert response.status_code == 401
21+
assert response.status_text == "Unauthorized"
22+
23+
24+
@pytest.mark.asyncio
25+
async def test_AioHTTPClient_auth_pass(url, root, password):
26+
client = AioHTTPClient(auth=BasicAuth(root, password))
27+
session = client.create_session(url)
28+
request = Request(
29+
method=Method.GET,
30+
endpoint="/_api/version",
31+
deserialize=False,
32+
)
33+
response = await client.send_request(session, request)
34+
assert response.method == Method.GET
35+
assert response.url == f"{url}/_api/version"
36+
assert response.status_code == 200
37+
assert response.status_text == "OK"

0 commit comments

Comments
 (0)