Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
os: [ubuntu-22.04]
python-version: ['pypy2.7', '3.7', 'pypy3.8']
# os: [ubuntu-latest, windows-latest, macos-latest]
# python-version: ['2.7', '3.7', '3.8', '3.9', '3.10', 'pypy-2.7', 'pypy-3.8']
Expand Down
61 changes: 61 additions & 0 deletions datadog/api/http_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@
except ImportError:
urlfetch, urlfetch_errors = None, None

try:
import urllib3 # type: ignore
except ImportError:
urllib3 = None

# datadog
from datadog.api.exceptions import ProxyError, ClientError, HTTPError, HttpTimeout

Expand Down Expand Up @@ -178,6 +183,58 @@ def raise_on_status(cls, result):
raise HTTPError(status_code)


class Urllib3Client(HTTPClient):
"""
HTTP client based on 3rd party `urllib3` module.
"""

_pool = None
_pool_lock = Lock()

@classmethod
def request(cls, method, url, headers, params, data, timeout, proxies, verify, max_retries):
"""
Wrapper around `urllib3.PoolManager.request` method. This method will raise
exceptions for HTTP status codes that are not 2xx.
"""
try:
with cls._pool_lock:
if cls._pool is None:
cls._pool = urllib3.PoolManager(
retries=max_retries,
timeout=timeout,
cert_reqs="CERT_REQUIRED" if verify else "CERT_NONE",
)

newheaders = copy.deepcopy(headers)
newheaders["User-Agent"] = _get_user_agent_header()
response = cls._pool.request(
method, url, body=data, fields=params, headers=newheaders
)
cls.raise_on_status(response)

except urllib3.exceptions.ProxyError as e:
raise _remove_context(ProxyError(method, url, e))
except urllib3.exceptions.MaxRetryError as e:
raise _remove_context(ClientError(method, url, e))
except urllib3.exceptions.TimeoutError as e:
raise _remove_context(HttpTimeout(method, url, e))
except urllib3.exceptions.HTTPError as e:
raise _remove_context(HTTPError(e))

return response

@classmethod
def raise_on_status(cls, response):
"""
Raise on HTTP status code errors.
"""
status_code = response.status
if status_code < 200 or status_code >= 300:
if status_code not in (400, 401, 403, 404, 409, 429):
raise HTTPError(status_code, response.reason)


def resolve_http_client():
"""
Resolve an appropriate HTTP client based the defined priority and user environment.
Expand All @@ -190,6 +247,10 @@ def resolve_http_client():
log.debug(u"Use `urlfetch` based HTTP client.")
return URLFetchClient

if urllib3:
log.debug(u"Use `urllib3` based HTTP client.")
return Urllib3Client

raise ImportError(
u"Datadog API client was unable to resolve a HTTP client. " u" Please install `requests` library."
)
Loading