Skip to content

Commit 350c36b

Browse files
authored
[BTS-1515] Pass PoolTimeout as argument (#257)
* Adding extra parameters to the DefaultHTTPClient * Keeping default behavior the same as before * Corrections * Updating test * Adding request_timeout together with pool_timeout
1 parent a2f856a commit 350c36b

File tree

3 files changed

+127
-24
lines changed

3 files changed

+127
-24
lines changed

arango/client.py

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
)
1414
from arango.database import StandardDatabase
1515
from arango.exceptions import ServerConnectionError
16-
from arango.http import DefaultHTTPClient, HTTPClient
16+
from arango.http import DEFAULT_REQUEST_TIMEOUT, DefaultHTTPClient, HTTPClient
1717
from arango.resolver import (
1818
HostResolver,
1919
RandomHostResolver,
@@ -55,10 +55,10 @@ class ArangoClient:
5555
:type verify_override: Union[bool, str, None]
5656
:param request_timeout: This is the default request timeout (in seconds)
5757
for http requests issued by the client if the parameter http_client is
58-
not secified. The default value is 60.
58+
not specified. The default value is 60.
5959
None: No timeout.
6060
int: Timeout value in seconds.
61-
:type request_timeout: Any
61+
:type request_timeout: int | float
6262
"""
6363

6464
def __init__(
@@ -70,7 +70,7 @@ def __init__(
7070
serializer: Callable[..., str] = lambda x: dumps(x),
7171
deserializer: Callable[[str], Any] = lambda x: loads(x),
7272
verify_override: Union[bool, str, None] = None,
73-
request_timeout: Any = 60,
73+
request_timeout: Union[int, float] = DEFAULT_REQUEST_TIMEOUT,
7474
) -> None:
7575
if isinstance(hosts, str):
7676
self._hosts = [host.strip("/") for host in hosts.split(",")]
@@ -88,11 +88,7 @@ def __init__(
8888
self._host_resolver = RoundRobinHostResolver(host_count, resolver_max_tries)
8989

9090
# Initializes the http client
91-
self._http = http_client or DefaultHTTPClient()
92-
# Sets the request timeout.
93-
# This call can only happen AFTER initializing the http client.
94-
if http_client is None:
95-
self.request_timeout = request_timeout
91+
self._http = http_client or DefaultHTTPClient(request_timeout=request_timeout)
9692

9793
self._serializer = serializer
9894
self._deserializer = deserializer
@@ -137,12 +133,12 @@ def request_timeout(self) -> Any:
137133
:return: Request timeout.
138134
:rtype: Any
139135
"""
140-
return self._http.REQUEST_TIMEOUT # type: ignore
136+
return self._http.request_timeout # type: ignore
141137

142138
# Setter for request_timeout
143139
@request_timeout.setter
144140
def request_timeout(self, value: Any) -> None:
145-
self._http.REQUEST_TIMEOUT = value # type: ignore
141+
self._http.request_timeout = value # type: ignore
146142

147143
def db(
148144
self,

arango/http.py

Lines changed: 102 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
1-
__all__ = ["HTTPClient", "DefaultHTTPClient"]
1+
__all__ = ["HTTPClient", "DefaultHTTPClient", "DEFAULT_REQUEST_TIMEOUT"]
22

3+
import typing
34
from abc import ABC, abstractmethod
4-
from typing import MutableMapping, Optional, Tuple, Union
5+
from typing import Any, MutableMapping, Optional, Tuple, Union
56

67
from requests import Session
7-
from requests.adapters import HTTPAdapter
8+
from requests.adapters import DEFAULT_POOLBLOCK, DEFAULT_POOLSIZE, HTTPAdapter
89
from requests_toolbelt import MultipartEncoder
10+
from urllib3.poolmanager import PoolManager
911
from urllib3.util.retry import Retry
1012

1113
from arango.response import Response
1214
from arango.typings import Headers
1315

16+
DEFAULT_REQUEST_TIMEOUT = 60
17+
1418

1519
class HTTPClient(ABC): # pragma: no cover
1620
"""Abstract base class for HTTP clients."""
@@ -63,12 +67,92 @@ def send_request(
6367
raise NotImplementedError
6468

6569

66-
class DefaultHTTPClient(HTTPClient):
67-
"""Default HTTP client implementation."""
70+
class DefaultHTTPAdapter(HTTPAdapter):
71+
"""Default transport adapter implementation
72+
73+
:param connection_timeout: Socket timeout in seconds for each individual connection.
74+
:type connection_timeout: int | float
75+
:param pool_connections: The number of urllib3 connection pools to cache.
76+
:type pool_connections: int
77+
:param pool_maxsize: The maximum number of connections to save in the pool.
78+
:type pool_maxsize: int
79+
:param pool_timeout: If set, then the pool will be set to block=True,
80+
and requests will block for pool_timeout seconds and raise
81+
EmptyPoolError if no connection is available within the time period.
82+
:type pool_timeout: int | float | None
83+
:param kwargs: Additional keyword arguments passed to the HTTPAdapter constructor.
84+
:type kwargs: Any
85+
"""
86+
87+
def __init__(
88+
self,
89+
connection_timeout: Union[int, float] = DEFAULT_REQUEST_TIMEOUT,
90+
pool_connections: int = DEFAULT_POOLSIZE,
91+
pool_maxsize: int = DEFAULT_POOLSIZE,
92+
pool_timeout: Union[int, float, None] = None,
93+
**kwargs: Any
94+
) -> None:
95+
self._connection_timeout = connection_timeout
96+
self._pool_timeout = pool_timeout
97+
super().__init__(
98+
pool_connections=pool_connections, pool_maxsize=pool_maxsize, **kwargs
99+
)
100+
101+
@typing.no_type_check
102+
def init_poolmanager(
103+
self, connections, maxsize, block=DEFAULT_POOLBLOCK, **pool_kwargs
104+
) -> None:
105+
kwargs = pool_kwargs
106+
kwargs.update(
107+
dict(
108+
num_pools=connections,
109+
maxsize=maxsize,
110+
strict=True,
111+
timeout=self._connection_timeout,
112+
)
113+
)
114+
if self._pool_timeout is not None:
115+
kwargs["block"] = True
116+
kwargs["timeout"] = self._pool_timeout
117+
else:
118+
kwargs["block"] = False
119+
self.poolmanager = PoolManager(**kwargs)
68120

69-
REQUEST_TIMEOUT = 60
70-
RETRY_ATTEMPTS = 3
71-
BACKOFF_FACTOR = 1
121+
122+
class DefaultHTTPClient(HTTPClient):
123+
"""Default HTTP client implementation.
124+
125+
:param request_timeout: Timeout in seconds for each individual connection.
126+
:type request_timeout: int | float
127+
:param retry_attempts: Number of retry attempts.
128+
:type retry_attempts: int
129+
:param backoff_factor: Backoff factor for retry attempts.
130+
:type backoff_factor: float
131+
:param pool_connections: The number of urllib3 connection pools to cache.
132+
:type pool_connections: int
133+
:param pool_maxsize: The maximum number of connections to save in the pool.
134+
:type pool_maxsize: int
135+
:param pool_timeout: If set, then the pool will be set to block=True,
136+
and requests will block for pool_timeout seconds and raise
137+
EmptyPoolError if no connection is available within the time period.
138+
:type pool_timeout: int | float | None
139+
"""
140+
141+
def __init__(
142+
self,
143+
request_timeout: Union[int, float] = DEFAULT_REQUEST_TIMEOUT,
144+
retry_attempts: int = 3,
145+
backoff_factor: float = 1.0,
146+
pool_connections: int = 10,
147+
pool_maxsize: int = 10,
148+
pool_timeout: Union[int, float, None] = None,
149+
) -> None:
150+
self.request_timeout = request_timeout
151+
self._retry_attempts = retry_attempts
152+
self._backoff_factor = backoff_factor
153+
self._pool_connections = pool_connections
154+
self._pool_maxsize = pool_maxsize
155+
self._pool_timeout = pool_timeout
72156

73157
def create_session(self, host: str) -> Session:
74158
"""Create and return a new session/connection.
@@ -79,12 +163,18 @@ def create_session(self, host: str) -> Session:
79163
:rtype: requests.Session
80164
"""
81165
retry_strategy = Retry(
82-
total=self.RETRY_ATTEMPTS,
83-
backoff_factor=self.BACKOFF_FACTOR,
166+
total=self._retry_attempts,
167+
backoff_factor=self._backoff_factor,
84168
status_forcelist=[429, 500, 502, 503, 504],
85169
allowed_methods=["HEAD", "GET", "OPTIONS"],
86170
)
87-
http_adapter = HTTPAdapter(max_retries=retry_strategy)
171+
http_adapter = DefaultHTTPAdapter(
172+
connection_timeout=self.request_timeout,
173+
pool_connections=self._pool_connections,
174+
pool_maxsize=self._pool_maxsize,
175+
pool_timeout=self._pool_timeout,
176+
max_retries=retry_strategy,
177+
)
88178

89179
session = Session()
90180
session.mount("https://", http_adapter)
@@ -128,7 +218,7 @@ def send_request(
128218
data=data,
129219
headers=headers,
130220
auth=auth,
131-
timeout=self.REQUEST_TIMEOUT,
221+
timeout=self.request_timeout,
132222
)
133223
return Response(
134224
method=method,

tests/test_client.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ def test_client_attributes():
5454
assert isinstance(client._host_resolver, RandomHostResolver)
5555

5656
client = ArangoClient(hosts=client_hosts, request_timeout=120)
57-
assert client.request_timeout == client._http.REQUEST_TIMEOUT == 120
57+
assert client.request_timeout == client._http.request_timeout == 120
5858

5959

6060
def test_client_good_connection(db, username, password):
@@ -92,6 +92,23 @@ def test_client_bad_connection(db, username, password, cluster):
9292
assert "bad connection" in str(err.value)
9393

9494

95+
def test_client_http_client_attributes(db, username, password):
96+
http_client = DefaultHTTPClient(
97+
request_timeout=80,
98+
retry_attempts=5,
99+
backoff_factor=1.0,
100+
pool_connections=16,
101+
pool_maxsize=12,
102+
pool_timeout=120,
103+
)
104+
client = ArangoClient(
105+
hosts="http://127.0.0.1:8529", http_client=http_client, request_timeout=30
106+
)
107+
client.db(db.name, username, password, verify=True)
108+
assert http_client.request_timeout == 80
109+
assert client.request_timeout == http_client.request_timeout
110+
111+
95112
def test_client_custom_http_client(db, username, password):
96113
# Define custom HTTP client which increments the counter on any API call.
97114
class MyHTTPClient(DefaultHTTPClient):

0 commit comments

Comments
 (0)