def DXHTTPRequest(resource, data, method='POST', headers=None, auth=True,
                  timeout=DEFAULT_TIMEOUT,
                  use_compression=None, jsonify_data=True, want_full_response=False,
                  decode_response_body=True, prepend_srv=True, session_handler=None,
                  max_retries=DEFAULT_RETRIES, always_retry=False,
                  **kwargs):
    '''
    :param resource: API server route, e.g. "/record/new". If *prepend_srv* is False, a fully qualified URL is expected. If this argument is a callable, it will be called just before each request attempt, and expected to return a tuple (URL, headers). Headers returned by the callback are updated with *headers* (including headers set by this method).
    :type resource: string
    :param data: Content of the request body
    :type data: list or dict, if *jsonify_data* is True; or string or file-like object, otherwise
    :param headers: Names and values of HTTP headers to submit with the request (in addition to those needed for authentication, compression, or other options specified with the call).
    :type headers: dict
    :param auth:
        Controls the ``Authentication`` header or other means of authentication supplied with the request. If ``True``
        (default), a token is obtained from the ``DX_SECURITY_CONTEXT``. If the value evaluates to false, no action is
        taken to prepare authentication for the request. Otherwise, the value is assumed to be callable, and called with
        three arguments (method, url, headers) and expected to prepare the authentication headers by reference.
    :type auth: tuple, object, True (default), or None
    :param timeout: HTTP request timeout, in seconds
    :type timeout: float
    :param config: *config* value to pass through to :meth:`requests.request`
    :type config: dict
    :param use_compression: Deprecated
    :type use_compression: string or None
    :param jsonify_data: If True, *data* is converted from a Python list or dict to a JSON string
    :type jsonify_data: boolean
    :param want_full_response: If True, the full :class:`requests.Response` object is returned (otherwise, only the content of the response body is returned)
    :type want_full_response: boolean
    :param decode_response_body: If True (and *want_full_response* is False), the response body is decoded and, if it is a JSON string, deserialized. Otherwise, the response body is uncompressed if transport compression is on, and returned raw.
    :type decode_response_body: boolean
    :param prepend_srv: If True, prepends the API server location to the URL
    :type prepend_srv: boolean
    :param session_handler: Deprecated.
    :param max_retries: Maximum number of retries to perform for a request. A "failed" request is retried if any of the following is true:

                        - A response is received from the server, and the content length received does not match the "Content-Length" header.
                        - A response is received from the server, and the response has an HTTP status code in 5xx range.
                        - A response is received from the server, the "Content-Length" header is not set, and the response JSON cannot be parsed.
                        - No response is received from the server, and either *always_retry* is True or the request *method* is "GET".

    :type max_retries: int
    :param always_retry: If True, indicates that it is safe to retry a request on failure

                        - Note: It is not guaranteed that the request will *always* be retried on failure; rather, this is an indication to the function that it would be safe to do so.

    :type always_retry: boolean
    :returns: Response from API server in the format indicated by *want_full_response* and *decode_response_body*.
    :raises: :exc:`exceptions.DXAPIError` or a subclass if the server returned a non-200 status code; :exc:`requests.exceptions.HTTPError` if an invalid response was received from the server; or :exc:`requests.exceptions.ConnectionError` if a connection cannot be established.

    Wrapper around :meth:`requests.request()` that makes an HTTP
    request, inserting authentication headers and (by default)
    converting *data* to JSON.

    .. note:: Bindings methods that make API calls make the underlying
       HTTP request(s) using :func:`DXHTTPRequest`, and most of them
       will pass any unrecognized keyword arguments you have supplied
       through to :func:`DXHTTPRequest`.

    '''
    if headers is None:
        headers = {}

    global _UPGRADE_NOTIFY

    url = APISERVER + resource if prepend_srv else resource
    method = method.upper()  # Convert method name to uppercase, to ease string comparisons later

    if auth is True:
        auth = AUTH_HELPER

    if auth:
        auth(_RequestForAuth(method, url, headers))

    pool_args = {arg: kwargs.pop(arg, None) for arg in ("verify", "cert_file", "key_file")}

    if _DEBUG >= 2:
        if isinstance(data, basestring) or isinstance(data, mmap.mmap):
            if len(data) == 0:
                formatted_data = '""'
            else:
                formatted_data = "<file data>"
        else:
            try:
                if _DEBUG >= 3:
                    formatted_data = json.dumps(data, indent=2)
                else:
                    formatted_data = json.dumps(data)
            except (__HOLE__, TypeError):
                formatted_data = "<binary data>"

    if jsonify_data:
        data = json.dumps(data)
        if 'Content-Type' not in headers and method == 'POST':
            headers['Content-Type'] = 'application/json'

    # If the input is a buffer, its data gets consumed by
    # requests.request (moving the read position). Record the initial
    # buffer position so that we can return to it if the request fails
    # and needs to be retried.
    rewind_input_buffer_offset = None
    if hasattr(data, 'seek') and hasattr(data, 'tell'):
        rewind_input_buffer_offset = data.tell()

    try_index = 0
    while True:
        success, time_started = True, None
        response = None
        try:
            if _DEBUG > 0:
                time_started = time.time()
            _method, _url, _headers = _process_method_url_headers(method, url, headers)

            if _DEBUG >= 2:
                maybe_headers = ''
                if 'Range' in _headers:
                    maybe_headers = " " + json.dumps({"Range": _headers["Range"]})
                print("%s %s%s => %s\n" % (method, _url, maybe_headers, formatted_data), file=sys.stderr, end="")
            elif _DEBUG > 0:
                from repr import Repr
                print("%s %s => %s\n" % (method, _url, Repr().repr(data)), file=sys.stderr, end="")

            body = _maybe_trucate_request(_url, try_index, data)

            # throws BadStatusLine if the server returns nothing
            response = _get_pool_manager(**pool_args).request(_method, _url, headers=_headers, body=body,
                                                              timeout=timeout, retries=False, **kwargs)
            req_id = response.headers.get("x-request-id", "unavailable")

            if _UPGRADE_NOTIFY and response.headers.get('x-upgrade-info', '').startswith('A recommended update is available') and '_ARGCOMPLETE' not in os.environ:
                logger.info(response.headers['x-upgrade-info'])
                try:
                    with file(_UPGRADE_NOTIFY, 'a'):
                        os.utime(_UPGRADE_NOTIFY, None)
                except:
                    pass
                _UPGRADE_NOTIFY = False

            # If an HTTP code that is not in the 200 series is received and the content is JSON, parse it and throw the
            # appropriate error.  Otherwise, raise the usual exception.
            if response.status // 100 != 2:
                # response.headers key lookup is case-insensitive
                if response.headers.get('content-type', '').startswith('application/json'):
                    content = response.data.decode('utf-8')
                    try:
                        content = json.loads(content)
                    except ValueError:
                        # The JSON is not parsable, but we should be able to retry.
                        raise exceptions.BadJSONInReply("Invalid JSON received from server", response.status)
                    try:
                        error_class = getattr(exceptions, content["error"]["type"], exceptions.DXAPIError)
                    except (KeyError, AttributeError, TypeError):
                        error_class = exceptions.HTTPError
                    raise error_class(content, response.status)
                else:
                    content = response.data.decode('utf-8')
                    raise exceptions.HTTPError("{} {} [RequestID={}]\n{}".format(response.status,
                                                                                 response.reason,
                                                                                 req_id,
                                                                                 content))

            if want_full_response:
                return response
            else:
                if 'content-length' in response.headers:
                    if int(response.headers['content-length']) != len(response.data):
                        range_str = (' (%s)' % (headers['Range'],)) if 'Range' in headers else ''
                        raise exceptions.ContentLengthError(
                            "Received response with content-length header set to %s but content length is %d%s. " +
                            "[RequestID=%s]" %
                            (response.headers['content-length'], len(response.data), range_str, req_id)
                        )

                content = response.data

                if decode_response_body:
                    content = content.decode('utf-8')
                    if response.headers.get('content-type', '').startswith('application/json'):
                        try:
                            content = json.loads(content)
                        except ValueError:
                            # The JSON is not parsable, but we should be able to retry.
                            raise exceptions.BadJSONInReply("Invalid JSON received from server", response.status)
                        if _DEBUG > 0:
                            t = int((time.time() - time_started) * 1000)
                            req_id = response.headers.get('x-request-id')
                        if _DEBUG >= 3:
                            print(method, req_id, url, "<=", response.status, "(%dms)" % t,
                                  "\n" + json.dumps(content, indent=2), file=sys.stderr)
                        elif _DEBUG == 2:
                            print(method, req_id, url, "<=", response.status, "(%dms)" % t, json.dumps(content),
                                  file=sys.stderr)
                        elif _DEBUG > 0:
                            print(method, req_id, url, "<=", response.status, "(%dms)" % t, Repr().repr(content),
                                  file=sys.stderr)
                return content
            raise AssertionError('Should never reach this line: expected a result to have been returned by now')
        except Exception as e:
            # Avoid reusing connections in the pool, since they may be
            # in an inconsistent state (observed as "ResponseNotReady"
            # errors).
            _get_pool_manager(**pool_args).clear()
            success = False
            exception_msg = _extract_msg_from_last_exception()
            if isinstance(e, _expected_exceptions):
                if response is not None and response.status == 503:
                    seconds_to_wait = _extract_retry_after_timeout(response)
                    logger.warn("%s %s: %s. Waiting %d seconds due to server unavailability...",
                                method, url, exception_msg, seconds_to_wait)
                    time.sleep(seconds_to_wait)
                    # Note, we escape the "except" block here without
                    # incrementing try_index because 503 responses with
                    # Retry-After should not count against the number of
                    # permitted retries.
                    continue

                # Total number of allowed tries is the initial try + up to
                # (max_retries) subsequent retries.
                total_allowed_tries = max_retries + 1
                ok_to_retry = False
                is_retryable = always_retry or (method == 'GET') or _is_retryable_exception(e)
                # Because try_index is not incremented until we escape this
                # iteration of the loop, try_index is equal to the number of
                # tries that have failed so far, minus one. Test whether we
                # have exhausted all retries.
                #
                # BadStatusLine ---  server did not return anything
                # BadJSONInReply --- server returned JSON that didn't parse properly
                if try_index + 1 < total_allowed_tries:
                    if response is None or \
                       isinstance(e, (exceptions.ContentLengthError, BadStatusLine, exceptions.BadJSONInReply, \
                                      urllib3.exceptions.ProtocolError)):
                        ok_to_retry = is_retryable
                    else:
                        ok_to_retry = 500 <= response.status < 600

                    # The server has closed the connection prematurely
                    if response is not None and \
                       response.status == 400 and is_retryable and method == 'PUT' and \
                       isinstance(e, requests.exceptions.HTTPError):
                        if '<Code>RequestTimeout</Code>' in exception_msg:
                            logger.info("Retrying 400 HTTP error, due to slow data transfer")
                        else:
                            logger.info("400 HTTP error, of unknown origin, exception_msg=[%s]", exception_msg)
                        ok_to_retry = True

                if ok_to_retry:
                    if rewind_input_buffer_offset is not None:
                        data.seek(rewind_input_buffer_offset)
                    delay = min(2 ** try_index, DEFAULT_TIMEOUT)
                    range_str = (' (range=%s)' % (headers['Range'],)) if 'Range' in headers else ''
                    logger.warn("%s %s: %s. Waiting %d seconds before retry %d of %d... %s",
                                method, url, exception_msg, delay, try_index + 1, max_retries, range_str)
                    time.sleep(delay)
                    try_index += 1
                    continue

            # All retries have been exhausted OR the error is deemed not
            # retryable. Print the latest error and propagate it back to the caller.
            if not isinstance(e, exceptions.DXAPIError):
                logger.error("%s %s: %s", method, url, exception_msg)

            # Retries have been exhausted, and we are unable to get a full
            # buffer from the data source. Raise a special exception.
            if isinstance(e, urllib3.exceptions.ProtocolError) and \
               'Connection broken: IncompleteRead' in exception_msg:
                raise exceptions.DXIncompleteReadsError(exception_msg)
            raise
        finally:
            if success and try_index > 0:
                logger.info("%s %s: Recovered after %d retries", method, url, try_index)

        raise AssertionError('Should never reach this line: should have attempted a retry or reraised by now')
    raise AssertionError('Should never reach this line: should never break out of loop')
dataset/ETHPy150Open dnanexus/dx-toolkit/src/python/dxpy/__init__.py/DXHTTPRequest