Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Debian packages RPM packages NuGet packages

Repository URL to install this package:

Details    
datadog / api / http_client.py
Size: Mime:
"""
Available HTTP Client for Datadog API client.

Priority:
1. `requests` 3p module
2. `urlfetch` 3p module - Google App Engine only
"""
# stdlib
import logging
import urllib

# 3p
try:
    import requests
except ImportError:
    requests = None

try:
    from google.appengine.api import urlfetch, urlfetch_errors
except ImportError:
    urlfetch, urlfetch_errors = None, None

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


log = logging.getLogger('datadog.api')


class HTTPClient(object):
    """
    An abstract generic HTTP client. Subclasses must implement the `request` methods.
    """
    @classmethod
    def request(cls, method, url, headers, params, data, timeout, proxies, verify, max_retries):
        """
        Main method to be implemented by HTTP clients.

        The returned data structure has the following fields:
        * `content`: string containing the response from the server
        * `status_code`: HTTP status code returned by the server

        Can raise the following exceptions:
        * `ClientError`: server cannot be contacted
        * `HttpTimeout`: connection timed out
        * `HTTPError`: unexpected HTTP response code
        """
        raise NotImplementedError(
            u"Must be implemented by HTTPClient subclasses."
        )


class RequestClient(HTTPClient):
    """
    HTTP client based on 3rd party `requests` module.
    """
    @classmethod
    def request(cls, method, url, headers, params, data, timeout, proxies, verify, max_retries):
        """
        """
        # Use a session to set a max_retries parameters
        s = requests.Session()
        http_adapter = requests.adapters.HTTPAdapter(max_retries=max_retries)
        s.mount('https://', http_adapter)

        try:
            result = s.request(
                method, url,
                headers=headers, params=params, data=data,
                timeout=timeout,
                proxies=proxies, verify=verify)

            result.raise_for_status()

        except requests.ConnectionError as e:
            raise ClientError(method, url, e)
        except requests.exceptions.Timeout:
            raise HttpTimeout(method, url, timeout)
        except requests.exceptions.HTTPError as e:
            if e.response.status_code in (400, 403, 404, 409):
                # This gets caught afterwards and raises an ApiError exception
                pass
            else:
                raise HTTPError(e.response.status_code, result.reason)
        except TypeError as e:
            raise TypeError(
                u"Your installed version of `requests` library seems not compatible with"
                u"Datadog's usage. We recommand upgrading it ('pip install -U requests')."
                u"If you need help or have any question, please contact support@datadoghq.com"
            )

        return result


class URLFetchClient(HTTPClient):
    """
    HTTP client based on Google App Engine `urlfetch` module.
    """
    @classmethod
    def request(cls, method, url, headers, params, data, timeout, proxies, verify, max_retries):
        """
        Wrapper around `urlfetch.fetch` method.

        TO IMPLEMENT:
        * `max_retries`
        """
        # No local certificate file can be used on Google App Engine
        validate_certificate = True if verify else False

        # Encode parameters in the url
        url_with_params = "{url}?{params}".format(
            url=url,
            params=urllib.urlencode(params)
        )

        try:
            result = urlfetch.fetch(
                url=url_with_params,
                method=method,
                headers=headers,
                validate_certificate=validate_certificate,
                deadline=timeout,
                payload=data,
                # setting follow_redirects=False may be slightly faster:
                # https://cloud.google.com/appengine/docs/python/microservice-performance#use_the_shortest_route
                follow_redirects=False
            )

            cls.raise_on_status(result)

        except urlfetch.DownloadError as e:
            raise ClientError(method, url, e)
        except urlfetch_errors.DeadlineExceededError:
            raise HttpTimeout(method, url, timeout)

        return result

    @classmethod
    def raise_on_status(cls, result):
        """
        Raise on HTTP status code errors.
        """
        status_code = result.status_code

        if (status_code / 100) != 2:
            if status_code in (400, 403, 404, 409):
                pass
            else:
                raise HTTPError(status_code)


def resolve_http_client():
    """
    Resolve an appropriate HTTP client based the defined priority and user environment.
    """
    if requests:
        log.debug(u"Use `requests` based HTTP client.")
        return RequestClient

    if urlfetch and urlfetch_errors:
        log.debug(u"Use `urlfetch` based HTTP client.")
        return URLFetchClient

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