Skip to content

Commit

Permalink
Increase default request timeout to 60 seconds and add ArangoClient.c…
Browse files Browse the repository at this point in the history
…lose method
  • Loading branch information
joowani committed Feb 25, 2021
1 parent 71285e0 commit bc65da7
Show file tree
Hide file tree
Showing 8 changed files with 41 additions and 29 deletions.
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,5 +109,4 @@ result = graph.traverse(
)
```

Please see the [documentation](http://python-driver-for-arangodb.readthedocs.io/en/master/index.html)
for more details.
Please see the [documentation](https://docs.python-arango.com) for more details.
5 changes: 5 additions & 0 deletions arango/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@ def __init__(
def __repr__(self) -> str:
return f"<ArangoClient {','.join(self._hosts)}>"

def close(self) -> None: # pragma: no cover
"""Close HTTP sessions."""
for session in self._sessions:
session.close()

@property
def hosts(self) -> Sequence[str]:
"""Return the list of ArangoDB host URLs.
Expand Down
5 changes: 2 additions & 3 deletions arango/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@
]

import sys
import time
from abc import abstractmethod
from calendar import timegm
from datetime import datetime
from typing import Any, Callable, Optional, Sequence, Union

import jwt
Expand Down Expand Up @@ -317,7 +316,7 @@ def send_request(self, request: Request) -> Response:
if resp.error_code != 11 or resp.status_code != 401:
return resp

now = timegm(datetime.utcnow().utctimetuple())
now = int(time.time())
if self._token_exp < now - self.exp_leeway: # pragma: no cover
return resp

Expand Down
14 changes: 9 additions & 5 deletions arango/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@

from requests import Session
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.retry import Retry
from requests_toolbelt import MultipartEncoder
from urllib3.util.retry import Retry

from arango.response import Response
from arango.typings import Headers
Expand Down Expand Up @@ -66,6 +66,10 @@ def send_request(
class DefaultHTTPClient(HTTPClient):
"""Default HTTP client implementation."""

REQUEST_TIMEOUT = 60
RETRY_ATTEMPTS = 3
BACKOFF_FACTOR = 1

def create_session(self, host: str) -> Session:
"""Create and return a new session/connection.
Expand All @@ -75,10 +79,10 @@ def create_session(self, host: str) -> Session:
:rtype: requests.Session
"""
retry_strategy = Retry(
total=3,
backoff_factor=1,
total=self.RETRY_ATTEMPTS,
backoff_factor=self.BACKOFF_FACTOR,
status_forcelist=[429, 500, 502, 503, 504],
method_whitelist=["HEAD", "GET", "OPTIONS"],
allowed_methods=["HEAD", "GET", "OPTIONS"],
)
http_adapter = HTTPAdapter(max_retries=retry_strategy)

Expand Down Expand Up @@ -124,7 +128,7 @@ def send_request(
data=data,
headers=headers,
auth=auth,
timeout=5,
timeout=self.REQUEST_TIMEOUT,
)
return Response(
method=method,
Expand Down
5 changes: 3 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
from setuptools import find_packages, setup

with open("./README.md") as fp:
description = fp.read()
long_description = fp.read()

setup(
name="python-arango",
description="Python Driver for ArangoDB",
long_description=description,
long_description=long_description,
long_description_content_type="text/markdown",
author="Joohwan Oh",
author_email="[email protected]",
Expand All @@ -19,6 +19,7 @@
use_scm_version=True,
setup_requires=["setuptools_scm"],
install_requires=[
"urllib3>=1.26.0",
"dataclasses>=0.6; python_version < '3.7'",
"requests",
"requests_toolbelt",
Expand Down
2 changes: 2 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,8 @@ def pytest_unconfigure(*_): # pragma: no cover
for backup_id in sys_db.backup.get()["list"].keys():
sys_db.backup.delete(backup_id)

global_data.client.close()


# noinspection PyProtectedMember
def pytest_generate_tests(metafunc):
Expand Down
5 changes: 2 additions & 3 deletions tests/helpers.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from calendar import timegm
import time
from collections import deque
from datetime import datetime
from uuid import uuid4

import jwt
Expand Down Expand Up @@ -119,7 +118,7 @@ def generate_jwt(secret, exp=3600):
:return: JWT
:rtype: str
"""
now = timegm(datetime.utcnow().utctimetuple())
now = int(time.time())
return jwt.encode(
payload={
"iat": now,
Expand Down
31 changes: 17 additions & 14 deletions tests/test_auth.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from arango.connection import BasicConnection, JwtConnection, JwtSuperuserConnection
from arango.errno import FORBIDDEN, HTTP_UNAUTHORIZED
from arango.exceptions import ( # JWTSecretListError,; JWTSecretReloadError,
from arango.exceptions import (
JWTAuthError,
JWTSecretListError,
JWTSecretReloadError,
ServerEncryptionError,
ServerTLSError,
ServerTLSReloadError,
Expand Down Expand Up @@ -53,6 +55,7 @@ def test_auth_jwt(client, db_name, username, password):
assert err.value.error_code == HTTP_UNAUTHORIZED


# TODO re-examine commented out code
def test_auth_superuser_token(client, db_name, root_password, secret):
token = generate_jwt(secret)
db = client.db("_system", superuser_token=token)
Expand All @@ -66,21 +69,21 @@ def test_auth_superuser_token(client, db_name, root_password, secret):
# secrets = db.jwt_secrets()
# assert 'active' in secrets
# assert 'passive' in secrets
#
# # Test get JWT secrets with bad database
# with assert_raises(JWTSecretListError) as err:
# bad_db.jwt_secrets()
# assert err.value.error_code == FORBIDDEN
#

# Test get JWT secrets with bad database
with assert_raises(JWTSecretListError) as err:
bad_db.jwt_secrets()
assert err.value.error_code == FORBIDDEN

# # Test reload JWT secrets
# secrets = db.reload_jwt_secrets()
# assert 'active' in secrets
# assert 'passive' in secrets
#
# # Test reload JWT secrets with bad database
# with assert_raises(JWTSecretReloadError) as err:
# bad_db.reload_jwt_secrets()
# assert err.value.error_code == FORBIDDEN

# Test reload JWT secrets with bad database
with assert_raises(JWTSecretReloadError) as err:
bad_db.reload_jwt_secrets()
assert err.value.error_code == FORBIDDEN

# Test get TLS data
result = db.tls()
Expand All @@ -100,7 +103,7 @@ def test_auth_superuser_token(client, db_name, root_password, secret):
bad_db.reload_tls()
assert err.value.error_code == FORBIDDEN

# # Test reload TLS
# # Test get encryption
# result = db.encryption()
# assert isinstance(result, dict)

Expand All @@ -113,7 +116,7 @@ def test_auth_superuser_token(client, db_name, root_password, secret):
def test_auth_jwt_expiry(client, db_name, root_password, secret):
# Test automatic token refresh on expired token.
db = client.db("_system", "root", root_password, auth_method="jwt")
expired_token = generate_jwt(secret, exp=0)
expired_token = generate_jwt(secret, exp=-1000)
db.conn._token = expired_token
db.conn._auth_header = f"bearer {expired_token}"
assert isinstance(db.version(), str)
Expand Down

0 comments on commit bc65da7

Please sign in to comment.