Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions packages/gooddata-sdk/src/gooddata_sdk/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ def __init__(
extra_user_agent: str | None = None,
executions_cancellable: bool = False,
ssl_ca_cert: str | None = None,
proxy: str | None = None,
) -> None:
"""Take url, token for connecting to GoodData.CN.

Expand All @@ -38,6 +39,10 @@ def __init__(
`executions_cancellable` is a flag that sets all executions computed through this client as cancellable.
In case a request for a result is interrupted, the GD server will try to free resources like killing sql queries
related to the given execution.

`proxy` is optional URL of an HTTP(S) proxy (e.g. ``http://proxy:8080``).
When not set, the standard ``HTTPS_PROXY`` / ``https_proxy`` / ``HTTP_PROXY`` /
``http_proxy`` environment variables are checked automatically.
"""
self._hostname = host
self._token = token
Expand All @@ -53,7 +58,20 @@ def __init__(
f"ssl_ca_cert file path specified but the file does not exist. Path: {ssl_ca_cert_path}."
)

if proxy is None:
import os

proxy = (
os.environ.get("HTTPS_PROXY")
or os.environ.get("https_proxy")
or os.environ.get("HTTP_PROXY")
or os.environ.get("http_proxy")
or None
)

self._api_config = api_client.Configuration(host=host, ssl_ca_cert=ssl_ca_cert)
if proxy:
self._api_config.proxy = proxy
self._api_client = api_client.ApiClient(
configuration=self._api_config,
header_name="Authorization",
Expand Down
6 changes: 6 additions & 0 deletions packages/gooddata-sdk/src/gooddata_sdk/sdk.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,18 @@ def create(
*,
ssl_ca_cert: str | None = None,
executions_cancellable: bool = False,
proxy: str | None = None,
**custom_headers_: str | None,
) -> GoodDataSdk:
"""
Create common GoodDataApiClient and return new GoodDataSdk instance.
Custom headers are filtered. Headers with None value are removed. It simplifies usage because headers
can be created directly from optional values.

``proxy`` is an optional HTTP(S) proxy URL (e.g. ``http://proxy:8080``).
When not set, the standard ``HTTPS_PROXY`` / ``https_proxy`` / ``HTTP_PROXY``
/ ``http_proxy`` environment variables are checked automatically.

This is preferred way of creating GoodDataSdk, when no tweaks are needed.
"""
filtered_headers = {key: value for key, value in custom_headers_.items() if value is not None}
Expand All @@ -66,6 +71,7 @@ def create(
extra_user_agent=extra_user_agent_,
executions_cancellable=executions_cancellable,
ssl_ca_cert=ssl_ca_cert,
proxy=proxy,
)
return cls(client)

Expand Down
64 changes: 63 additions & 1 deletion packages/gooddata-sdk/tests/test_client.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# (C) 2021 GoodData Corporation
from gooddata_sdk import GoodDataApiClient
import os
from unittest import mock

from gooddata_sdk import GoodDataApiClient, GoodDataSdk


def test_http_headers_precedence():
Expand All @@ -12,3 +15,62 @@ def test_http_headers_precedence():
agent = c._api_client.default_headers["User-Agent"]
assert agent.startswith("gooddata")
assert agent.endswith("yes")


class TestProxy:
"""Proxy configuration for the underlying urllib3 pool manager."""

@mock.patch.dict(os.environ, {}, clear=True)
def test_no_proxy_when_env_empty(self):
c = GoodDataApiClient("https://example.com", "token")
assert c._api_config.proxy is None

def test_explicit_proxy(self):
c = GoodDataApiClient("https://example.com", "token", proxy="http://myproxy:8080")
assert c._api_config.proxy == "http://myproxy:8080"

@mock.patch.dict(os.environ, {"HTTPS_PROXY": "http://envproxy:3128"}, clear=True)
def test_proxy_from_https_proxy_env(self):
c = GoodDataApiClient("https://example.com", "token")
assert c._api_config.proxy == "http://envproxy:3128"

@mock.patch.dict(os.environ, {"https_proxy": "http://lower:3128"}, clear=True)
def test_proxy_from_lowercase_https_proxy_env(self):
c = GoodDataApiClient("https://example.com", "token")
assert c._api_config.proxy == "http://lower:3128"

@mock.patch.dict(os.environ, {"HTTP_PROXY": "http://httpproxy:3128"}, clear=True)
def test_proxy_from_http_proxy_env(self):
c = GoodDataApiClient("https://example.com", "token")
assert c._api_config.proxy == "http://httpproxy:3128"

@mock.patch.dict(
os.environ,
{
"HTTPS_PROXY": "http://preferred:3128",
"HTTP_PROXY": "http://fallback:3128",
},
clear=True,
)
def test_https_proxy_takes_precedence_over_http_proxy(self):
c = GoodDataApiClient("https://example.com", "token")
assert c._api_config.proxy == "http://preferred:3128"

@mock.patch.dict(os.environ, {"HTTPS_PROXY": "http://envproxy:3128"}, clear=True)
def test_explicit_proxy_overrides_env(self):
c = GoodDataApiClient("https://example.com", "token", proxy="http://explicit:8080")
assert c._api_config.proxy == "http://explicit:8080"

def test_sdk_create_with_explicit_proxy(self):
sdk = GoodDataSdk.create("https://example.com", "token", proxy="http://myproxy:8080")
assert sdk.client._api_config.proxy == "http://myproxy:8080"

@mock.patch.dict(os.environ, {"HTTPS_PROXY": "http://envproxy:3128"}, clear=True)
def test_sdk_create_picks_up_env_proxy(self):
sdk = GoodDataSdk.create("https://example.com", "token")
assert sdk.client._api_config.proxy == "http://envproxy:3128"

@mock.patch.dict(os.environ, {}, clear=True)
def test_sdk_create_no_proxy_when_env_empty(self):
sdk = GoodDataSdk.create("https://example.com", "token")
assert sdk.client._api_config.proxy is None