def make_request(
        url, method='GET', query=None, body=None, auth=None, timeout=10,
        client=None, macaroons=None):
    headers = {}
    kwargs = {'timeout': timeout, 'headers': headers}
    if body is not None:
        if isinstance(body, collections.Mapping):
            body = json.dumps(body)
        kwargs['data'] = body
    if method in ('GET', 'HEAD'):
        if query:
            url = '{}?{}'.format(url, urlencode(query, True))
    elif method in ('DELETE', 'PATCH', 'POST', 'PUT'):
        headers['Content-Type'] = 'application/json'
    else:
        raise ValueError('invalid method {}'.format(method))
    if macaroons is not None:
        headers['Macaroons'] = macaroons
    kwargs['auth'] = auth if client is None else client.auth()
    api_method = getattr(requests, method.lower())
    try:
        response = api_method(url, **kwargs)
    except requests.exceptions.Timeout:
        raise timeout_error(url, timeout)
    except Exception as err:
        msg = _server_error_message(url, err)
        raise ServerError(msg)
    try:
        response.raise_for_status()
    except HTTPError as err:
        msg = _server_error_message(url, err.response.text)
        raise ServerError(err.response.status_code, msg)
    except requests.exceptions.RequestException as err:
        msg = _server_error_message(url, err.message)
        raise ServerError(msg)
    if not response.content:
        return {}
    try:
        return response.json()
    except Exception as err:
        msg = 'Error decoding JSON response: {} message: {}'.format(url, err)
        log.error(msg)
        raise ServerError(msg)