def test_retry_on_retry_eligible_failures_lowercase_verbs(
        mock_session, m_sleep):
    expected_call_count = 0
    max_calls = 3
    api_response = {'key': 'value'}
    session_context = mock_session.return_value.__enter__.return_value
    session_context.send.return_value.json.return_value = api_response
    for verb in RETRY_VERBS:
        for code in RETRY_CODES:
            expected_call_count += max_calls
            session_context.send.return_value.status_code = code

            request_info = dict(
                params={
                    'secondParameter': 'b',
                    'firstParameter': 'a'
                },
                json={},
                url='https://api.civisanalytics.com/wobble/wubble',
                method=verb.lower())

            request = Request(**request_info)
            pre_request = session_context.prepare_request(request)
            retry_request(verb, pre_request, session_context, max_calls)

            assert session_context.send.call_count == expected_call_count
def test_no_retry_on_connection_error(mock_session):
    expected_call_count = 0
    api_response = {'key': 'value'}
    session_context = mock_session.return_value.__enter__.return_value
    session_context.send.return_value.json.return_value = api_response

    for verb in RETRY_VERBS:
        expected_call_count += 1

        request_info = dict(params={
            'secondParameter': 'b',
            'firstParameter': 'a'
        },
                            json={},
                            url='https://api.civisanalytics.com/wobble/wubble',
                            method=verb)
        request = Request(**request_info)
        pre_request = session_context.prepare_request(request)

        session_context.send.side_effect = ConnectionError()
        try:
            retry_request(verb, pre_request, session_context, 3)
        except ConnectionError:
            pass

        assert session_context.send.call_count == expected_call_count
Exemple #3
0
    def _make_request(self,
                      method,
                      path=None,
                      params=None,
                      data=None,
                      **kwargs):
        url = self._build_path(path)

        with self._lock:
            with open_session(**self._session_kwargs) as sess:
                request = Request(method,
                                  url,
                                  json=data,
                                  params=params,
                                  **kwargs)
                pre_request = sess.prepare_request(request)
                response = retry_request(method, pre_request, sess,
                                         MAX_RETRIES)

        if response.status_code == 401:
            auth_error = response.headers["www-authenticate"]
            raise CivisAPIKeyError(auth_error) from CivisAPIError(response)

        if not response.ok:
            raise CivisAPIError(response)

        return response
def get_api_spec(api_key, api_version="1.0", user_agent="civis-python"):
    """Download the Civis API specification.

    Parameters
    ----------
    api_key : str
        Your API key obtained from the Civis Platform.
    api_version : string, optional
        The version of endpoints to call. May instantiate multiple client
        objects with different versions.  Currently only "1.0" is supported.
    user_agent : string, optional
        Provide this user agent to the the Civis API, along with an
        API client version tag and ``requests`` version tag.
    """
    if api_version == "1.0":
        with open_session(api_key, user_agent=user_agent) as sess:
            request = Request('GET', "{}endpoints".format(get_base_url()))
            pre_request = sess.prepare_request(request)
            response = retry_request('get', pre_request, sess, MAX_RETRIES)
    else:
        msg = "API specification for api version {} cannot be found"
        raise ValueError(msg.format(api_version))
    if response.status_code in (401, 403):
        msg = "{} error downloading API specification. API key may be expired."
        raise requests.exceptions.HTTPError(msg.format(response.status_code))
    response.raise_for_status()
    spec = response.json(object_pairs_hook=OrderedDict)
    return spec
def test_no_retry_on_post_success(mock_session):
    expected_call_count = 1
    max_calls = 3
    api_response = {'key': 'value'}
    session_context = mock_session.return_value.__enter__.return_value
    session_context.send.return_value.json.return_value = api_response

    session_context.send.return_value.status_code = 200

    request_info = dict(params={
        'secondParameter': 'b',
        'firstParameter': 'a'
    },
                        json={},
                        url='https://api.civisanalytics.com/wobble/wubble',
                        method='POST')
    request = Request(**request_info)
    pre_request = session_context.prepare_request(request)
    retry_request('post', pre_request, session_context, max_calls)

    assert session_context.send.call_count == expected_call_count
def test_no_retry_on_success(mock_session):
    expected_call_count = 0
    api_response = {'key': 'value'}
    session_context = mock_session.return_value.__enter__.return_value
    session_context.send.return_value.json.return_value = api_response

    for verb in RETRY_VERBS:
        expected_call_count += 1
        session_context.send.return_value.status_code = 200

        request_info = dict(params={
            'secondParameter': 'b',
            'firstParameter': 'a'
        },
                            json={},
                            url='https://api.civisanalytics.com/wobble/wubble',
                            method=verb)
        request = Request(**request_info)
        pre_request = session_context.prepare_request(request)
        retry_request(verb, pre_request, session_context, 3)

        assert session_context.send.call_count == expected_call_count
def test_retry_respect_retry_after_headers(mock_session):
    expected_call_count = 0
    max_calls = 2
    retry_after = 1
    api_response = {'key': 'value'}
    session_context = mock_session.return_value.__enter__.return_value
    session_context.send.return_value.json.return_value = api_response

    session_context.send.return_value.status_code = 429
    session_context.send.return_value.headers = {
        'Retry-After': str(retry_after)
    }

    for verb in [
            'HEAD', 'TRACE', 'GET', 'PUT', 'OPTIONS', 'DELETE', 'POST', 'head',
            'trace', 'get', 'put', 'options', 'delete', 'post'
    ]:
        expected_call_count += max_calls

        request_info = dict(params={
            'secondParameter': 'b',
            'firstParameter': 'a'
        },
                            json={},
                            url='https://api.civisanalytics.com/wobble/wubble',
                            method=verb)

        request = Request(**request_info)
        pre_request = session_context.prepare_request(request)

        start_time = datetime.now().timestamp()
        retry_request(verb, pre_request, session_context, max_calls)
        end_time = datetime.now().timestamp()
        duration = end_time - start_time

        assert session_context.send.call_count == expected_call_count
        assert floor(duration) == retry_after * (max_calls - 1)
def invoke(method, path, op, *args, **kwargs):
    """
    If json_output is in `kwargs` then the output is json. Otherwise, it is
    yaml.
    """
    # Remove None b/c click passes everything in as None if it's not set.
    kwargs = {k: v for k, v in kwargs.items() if v is not None}
    json_output = kwargs.pop('json_output', False)

    # Construct the body of the request.
    body = {}
    body_params = [p for p in op['parameters'] if p['in'] == 'body']
    if body_params:
        if len(body_params) != 1:
            raise ValueError(
                "There can be only one body parameter, "
                f"but {len(body_params)} are found: {body_params}")
        props = body_params[0]['schema']['properties']
        param_map = param_case_map(props.keys())
        body = {param_map[k]: v for k, v in kwargs.items() if k in param_map}

    # Construct the query part of the request.
    query_names = {p['name'] for p in op['parameters'] if p['in'] == 'query'}
    param_map = param_case_map(query_names)
    query = {param_map[k]: v for k, v in kwargs.items() if k in param_map}

    # Make the request.
    request_info = dict(params=query,
                        json=body,
                        url=get_base_api_url() + path.format(**kwargs),
                        method=method)
    with open_session(get_api_key(), user_agent=CLI_USER_AGENT) as sess:
        request = Request(**request_info)
        pre_request = sess.prepare_request(request)
        response = retry_request(method, pre_request, sess, MAX_RETRIES)

    # Print the response to stderr and set exit code to 1 if there was an error
    output_file = sys.stdout
    exit_code = 0
    if not (200 <= response.status_code < 300):
        output_file = sys.stderr
        exit_code = 1

    # Print the output, if there is any.
    # For commands such as DELETE /scripts/containers/{script_id}/runs/{id},
    # response ends up being " " here.
    try:
        if json_output:
            json.dump(response.json(), output_file)
        else:
            yaml.safe_dump(response.json(),
                           output_file,
                           default_flow_style=False)
        output_file.flush()

    # json throws a ValueError if it is passed a blank string to load.
    except ValueError as e:
        # If the message was not blank, print an error message.
        # Otherwise, do nothing.
        if response.text.strip():
            print("Error parsing response: {}".format(e), file=sys.stderr)

    sys.exit(exit_code)