diff --git a/README.md b/README.md index 03b5a0b..df598e9 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,12 @@ Check the [Python Connect SDK Example](example/README.md) to see an example of i export OP_CONNECT_HOST= && \ export OP_CONNECT_TOKEN= ``` + + 2.1 If you need a higher timeout on the client requests you can export `OP_CLIENT_REQUEST_TIMEOUT` environment variable: + ```sh + # set the timeout to 90 seconds + export OP_CLIENT_REQUEST_TIMEOUT=90 + ``` 3. Use the SDK: diff --git a/src/onepasswordconnectsdk/async_client.py b/src/onepasswordconnectsdk/async_client.py index f7e357b..23c1bfe 100644 --- a/src/onepasswordconnectsdk/async_client.py +++ b/src/onepasswordconnectsdk/async_client.py @@ -5,7 +5,7 @@ import os from onepasswordconnectsdk.serializer import Serializer -from onepasswordconnectsdk.utils import build_headers, is_valid_uuid, PathBuilder +from onepasswordconnectsdk.utils import build_headers, is_valid_uuid, PathBuilder, get_timeout from onepasswordconnectsdk.errors import ( FailedToRetrieveItemException, FailedToRetrieveVaultException, @@ -24,7 +24,8 @@ def __init__(self, url: str, token: str) -> None: self.serializer = Serializer() def create_session(self, url: str, token: str) -> httpx.AsyncClient: - return httpx.AsyncClient(base_url=url, headers=self.build_headers(token)) + # import here to avoid circular import + return httpx.AsyncClient(base_url=url, headers=self.build_headers(token), timeout=get_timeout()) def build_headers(self, token: str) -> Dict[str, str]: return build_headers(token) diff --git a/src/onepasswordconnectsdk/client.py b/src/onepasswordconnectsdk/client.py index 2b40327..a6d5060 100644 --- a/src/onepasswordconnectsdk/client.py +++ b/src/onepasswordconnectsdk/client.py @@ -1,13 +1,13 @@ """Python Client for connecting to 1Password Connect""" import httpx -from httpx import HTTPError +from httpx import HTTPError, USE_CLIENT_DEFAULT import json from typing import Dict, List, Union import os from onepasswordconnectsdk.async_client import AsyncClient from onepasswordconnectsdk.serializer import Serializer -from onepasswordconnectsdk.utils import build_headers, is_valid_uuid, PathBuilder +from onepasswordconnectsdk.utils import build_headers, is_valid_uuid, PathBuilder, get_timeout from onepasswordconnectsdk.errors import ( FailedToRetrieveItemException, FailedToRetrieveVaultException, @@ -32,7 +32,7 @@ def __init__(self, url: str, token: str) -> None: self.serializer = Serializer() def create_session(self, url: str, token: str) -> httpx.Client: - return httpx.Client(base_url=url, headers=self.build_headers(token)) + return httpx.Client(base_url=url, headers=self.build_headers(token), timeout=get_timeout()) def build_headers(self, token: str) -> Dict[str, str]: return build_headers(token) diff --git a/src/onepasswordconnectsdk/utils.py b/src/onepasswordconnectsdk/utils.py index da35d50..6699e42 100644 --- a/src/onepasswordconnectsdk/utils.py +++ b/src/onepasswordconnectsdk/utils.py @@ -1,4 +1,11 @@ +import os +from typing import Union + +from httpx import USE_CLIENT_DEFAULT +from httpx._client import UseClientDefault + UUIDLength = 26 +ENV_CLIENT_REQUEST_TIMEOUT = "OP_CONNECT_CLIENT_REQ_TIMEOUT" def is_valid_uuid(uuid): @@ -59,3 +66,9 @@ def _append_path(self, path_chunk: str = None, query: str = None) -> 'PathBuilde self.path += f"/{path_chunk}" if query is not None: self.path += f"?{query}" + + +def get_timeout() -> Union[int, UseClientDefault]: + """Get the timeout to be used in the HTTP Client""" + timeout = int(os.getenv(ENV_CLIENT_REQUEST_TIMEOUT, 0)) + return timeout if timeout else USE_CLIENT_DEFAULT diff --git a/src/tests/test_client_items.py b/src/tests/test_client_items.py index 1f5f74d..d9e23c9 100644 --- a/src/tests/test_client_items.py +++ b/src/tests/test_client_items.py @@ -1,6 +1,10 @@ +import os import pytest +from unittest import mock + from httpx import Response from onepasswordconnectsdk import client, models +from onepasswordconnectsdk.utils import ENV_CLIENT_REQUEST_TIMEOUT VAULT_ID = "hfnjvi6aymbsnfc2xeeoheizda" VAULT_TITLE = "VaultA" @@ -440,3 +444,16 @@ def generate_full_item(): id="Section_47DC4DDBF26640AB8B8618DA36D5A499"))], sections=[models.Section(id="id", label="label")]) return item + + +def test_set_timeout_using_env_variable(): + with mock.patch.dict(os.environ, {ENV_CLIENT_REQUEST_TIMEOUT: '120'}): + client_instance = client.new_client(HOST, TOKEN) + assert client_instance.session.timeout.read == 120 + + +@pytest.mark.asyncio +def test_set_timeout_using_env_variable_async(): + with mock.patch.dict(os.environ, {ENV_CLIENT_REQUEST_TIMEOUT: '120'}): + client_instance = client.new_client(HOST, TOKEN, is_async=True) + assert client_instance.session.timeout.read == 120 \ No newline at end of file