예제 #1
0
def test_format_action(openapi_specs, action_factory):
    formatter = DefaultFormatter(openapi_specs)
    action = action_factory(path='products/PRD-000/endsale')
    formatted = formatter.format(action)
    assert 'Endsale action' in formatted
    assert 'path: /products/PRD-000/endsale' in formatted

    action = action_factory(path='does-not-exists')
    formatted = formatter.format(action)
    assert 'does not exist' in formatted
예제 #2
0
def test_format_resource(openapi_specs, res_factory):
    formatter = DefaultFormatter(openapi_specs)
    res = res_factory(path='products/PRD-000')
    formatted = formatter.format(res)
    assert 'Product resource' in formatted
    assert 'path: /products/PRD-000' in formatted

    res = res_factory(path='subscriptions/assets/AS-0000')
    formatted = formatter.format(res)
    assert 'Asset resource' in formatted
    assert 'path: /subscriptions/assets/AS-0000' in formatted

    res = res_factory(path='does-not-exists')
    formatted = formatter.format(res)
    assert 'does not exist' in formatted
예제 #3
0
def test_format_rs(openapi_specs, rs_factory):
    formatter = DefaultFormatter(openapi_specs)
    rs = rs_factory(path='products')
    formatted = formatter.format(rs)
    assert 'Search the Products collection' in formatted
    assert 'path: /products' in formatted
    assert 'Available filters' in formatted

    rs = rs_factory(path='products/PRD-000/items')
    formatted = formatter.format(rs)
    assert 'Search the Items collection' in formatted
    assert 'path: /products/PRD-000/items' in formatted
    assert 'Available filters' in formatted

    rs = rs_factory(path='does-not-exists')
    formatted = formatter.format(rs)
    assert 'does not exist' in formatted
예제 #4
0
    def __init__(
        self,
        api_key,
        endpoint=None,
        use_specs=True,
        specs_location=None,
        validate_using_specs=True,
        default_headers=None,
        default_limit=100,
        max_retries=0,
    ):
        """
        Create a new instance of the ConnectClient.

        :param api_key: The API key used for authentication.
        :type api_key: str
        :param endpoint: The API endpoint, defaults to CONNECT_ENDPOINT_URL
        :type endpoint: str, optional
        :param specs_location: The Connect OpenAPI specification local path or URL, defaults to
                               CONNECT_SPECS_URL
        :type specs_location: str, optional
        :param default_headers: Http headers to apply to each request, defaults to {}
        :type default_headers: dict, optional
        """
        if default_headers and 'Authorization' in default_headers:
            raise ValueError(
                '`default_headers` cannot contains `Authorization`')

        self.endpoint = endpoint or CONNECT_ENDPOINT_URL
        self.api_key = api_key
        self.default_headers = default_headers or {}
        self.default_limit = default_limit
        self.max_retries = max_retries
        self._use_specs = use_specs
        self._validate_using_specs = validate_using_specs
        self.specs_location = specs_location or CONNECT_SPECS_URL
        self.specs = None
        if self._use_specs:
            self.specs = OpenAPISpecs(self.specs_location)
        self.response = None
        self._help_formatter = DefaultFormatter(self.specs)
예제 #5
0
class ConnectClient(threading.local):
    """
    Connect ReST API client.
    """
    def __init__(
        self,
        api_key,
        endpoint=None,
        use_specs=True,
        specs_location=None,
        validate_using_specs=True,
        default_headers=None,
        default_limit=100,
        max_retries=0,
    ):
        """
        Create a new instance of the ConnectClient.

        :param api_key: The API key used for authentication.
        :type api_key: str
        :param endpoint: The API endpoint, defaults to CONNECT_ENDPOINT_URL
        :type endpoint: str, optional
        :param specs_location: The Connect OpenAPI specification local path or URL, defaults to
                               CONNECT_SPECS_URL
        :type specs_location: str, optional
        :param default_headers: Http headers to apply to each request, defaults to {}
        :type default_headers: dict, optional
        """
        if default_headers and 'Authorization' in default_headers:
            raise ValueError(
                '`default_headers` cannot contains `Authorization`')

        self.endpoint = endpoint or CONNECT_ENDPOINT_URL
        self.api_key = api_key
        self.default_headers = default_headers or {}
        self.default_limit = default_limit
        self.max_retries = max_retries
        self._use_specs = use_specs
        self._validate_using_specs = validate_using_specs
        self.specs_location = specs_location or CONNECT_SPECS_URL
        self.specs = None
        if self._use_specs:
            self.specs = OpenAPISpecs(self.specs_location)
        self.response = None
        self._help_formatter = DefaultFormatter(self.specs)

    def __getattr__(self, name):
        """
        Returns a collection object called ``name``.

        :param name: The name of the collection to retrieve.
        :type name: str
        :return: a collection called ``name``.
        :rtype: Collection
        """
        if '_' in name:
            name = name.replace('_', '-')
        return self.collection(name)

    def __call__(self, name):
        return self.ns(name)

    def ns(self, name):
        """
        Returns the namespace called ``name``.

        :param name: The name of the namespace to access.
        :type name: str
        :return: The namespace called ``name``.
        :rtype: NS
        """
        if not isinstance(name, str):
            raise TypeError('`name` must be a string.')

        if not name:
            raise ValueError('`name` must not be blank.')

        return NS(self, name)

    def collection(self, name):
        """
        Returns the collection called ``name``.

        :param name: The name of the collection to access.
        :type name: str
        :return: The collection called ``name``.
        :rtype: Collection
        """
        if not isinstance(name, str):
            raise TypeError('`name` must be a string.')

        if not name:
            raise ValueError('`name` must not be blank.')

        return Collection(
            self,
            name,
        )

    def get(self, url, **kwargs):
        return self.execute('get', url, **kwargs)

    def create(self, url, payload=None, **kwargs):
        kwargs = kwargs or {}

        if payload:
            kwargs['json'] = payload

        return self.execute('post', url, **kwargs)

    def update(self, url, payload=None, **kwargs):
        kwargs = kwargs or {}

        if payload:
            kwargs['json'] = payload

        return self.execute('put', url, **kwargs)

    def delete(self, url, **kwargs):
        return self.execute('delete', url, **kwargs)

    def execute(self, method, path, **kwargs):
        if (self._use_specs and self._validate_using_specs
                and not self.specs.exists(method, path)):
            # TODO more info, specs version, method etc
            raise ClientError(f'The path `{path}` does not exist.')

        url = f'{self.endpoint}/{path}'

        kwargs = self._prepare_call_kwargs(kwargs)

        self.response = None

        try:
            self._execute_http_call(method, url, kwargs)
            if self.response.status_code == 204:
                return None
            if self.response.headers['Content-Type'] == 'application/json':
                return self.response.json()
            else:
                return self.response.content

        except RequestException as re:
            api_error = self._get_api_error_details() or {}
            status_code = self.response.status_code if self.response is not None else None
            raise ClientError(status_code=status_code, **api_error) from re

    def print_help(self, obj):
        print()
        print(self._help_formatter.format(obj))

    def help(self):
        self.print_help(None)
        return self

    def _prepare_call_kwargs(self, kwargs):
        kwargs = kwargs or {}
        if 'headers' in kwargs:
            kwargs['headers'].update(get_headers(self.api_key))
        else:
            kwargs['headers'] = get_headers(self.api_key)

        if self.default_headers:
            kwargs['headers'].update(self.default_headers)
        return kwargs

    def _execute_http_call(self, method, url, kwargs):
        retry_count = 0
        while True:
            self.response = requests.request(method, url, **kwargs)
            if (  # pragma: no branch
                    self.response.status_code == 502
                    and retry_count < self.max_retries):
                retry_count += 1
                time.sleep(1)
                continue
            break  # pragma: no cover
        if self.response.status_code >= 400:
            self.response.raise_for_status()

    def _get_api_error_details(self):
        if self.response is not None:
            try:
                error = self.response.json()
                if 'error_code' in error and 'errors' in error:
                    return error
            except JSONDecodeError:
                pass
예제 #6
0
def test_no_specs():
    formatter = DefaultFormatter(None)
    formatted = formatter.format(None)
    assert 'No OpenAPI specs available.' in formatted
예제 #7
0
def test_format_collection_not_exists(openapi_specs, col_factory):
    formatter = DefaultFormatter(openapi_specs)
    col = col_factory(path='does-not-exists')
    formatted = formatter.format(col)
    assert 'does not exist' in formatted
예제 #8
0
def test_format_collection(openapi_specs, col_factory, title, path):
    formatter = DefaultFormatter(openapi_specs)
    col = col_factory(path=path)
    formatted = formatter.format(col)
    assert f'{title} collection' in formatted
    assert f'path: /{path}' in formatted
예제 #9
0
def test_format_ns_not_exist(openapi_specs, ns_factory):
    formatter = DefaultFormatter(openapi_specs)
    ns = ns_factory(path='does-not-exists')
    formatted = formatter.format(ns)
    assert 'does not exist' in formatted
예제 #10
0
def test_format_ns(openapi_specs, ns_factory, title, path):
    formatter = DefaultFormatter(openapi_specs)
    ns = ns_factory(path=path)
    formatted = formatter.format(ns)
    assert f'{title} namespace' in formatted
    assert f'path: /{path}' in formatted
예제 #11
0
def test_format_client(openapi_specs):
    formatter = DefaultFormatter(openapi_specs)
    formatted = formatter.format(None)
    assert openapi_specs.title in formatted
    assert openapi_specs.version in formatted
class _ConnectClientBase(threading.local):

    """
    Connect ReST API client.
    """
    def __init__(
        self,
        api_key,
        endpoint=None,
        use_specs=True,
        specs_location=None,
        validate_using_specs=True,
        default_headers=None,
        default_limit=100,
        max_retries=0,
    ):
        """
        Create a new instance of the ConnectClient.

        :param api_key: The API key used for authentication.
        :type api_key: str
        :param endpoint: The API endpoint, defaults to CONNECT_ENDPOINT_URL
        :type endpoint: str, optional
        :param specs_location: The Connect OpenAPI specification local path or URL, defaults to
                               CONNECT_SPECS_URL
        :type specs_location: str, optional
        :param default_headers: Http headers to apply to each request, defaults to {}
        :type default_headers: dict, optional
        """
        if default_headers and 'Authorization' in default_headers:
            raise ValueError('`default_headers` cannot contains `Authorization`')

        self.endpoint = endpoint or CONNECT_ENDPOINT_URL
        self.api_key = api_key
        self.default_headers = default_headers or {}
        self.default_limit = default_limit
        self.max_retries = max_retries
        self._use_specs = use_specs
        self._validate_using_specs = validate_using_specs
        self.specs_location = specs_location or CONNECT_SPECS_URL
        self.specs = None
        if self._use_specs:
            self.specs = OpenAPISpecs(self.specs_location)
        self.response = None
        self._help_formatter = DefaultFormatter(self.specs)

    def __getattr__(self, name):
        """
        Returns a collection object called ``name``.

        :param name: The name of the collection to retrieve.
        :type name: str
        :return: a collection called ``name``.
        :rtype: Collection
        """
        if '_' in name:
            name = name.replace('_', '-')
        return self.collection(name)

    def __call__(self, name):
        return self.ns(name)

    def ns(self, name):
        """
        Returns the namespace called ``name``.

        :param name: The name of the namespace to access.
        :type name: str
        :return: The namespace called ``name``.
        :rtype: NS
        """
        if not isinstance(name, str):
            raise TypeError('`name` must be a string.')

        if not name:
            raise ValueError('`name` must not be blank.')

        return self._get_namespace_class()(self, name)

    def collection(self, name):
        """
        Returns the collection called ``name``.

        :param name: The name of the collection to access.
        :type name: str
        :return: The collection called ``name``.
        :rtype: Collection
        """
        if not isinstance(name, str):
            raise TypeError('`name` must be a string.')

        if not name:
            raise ValueError('`name` must not be blank.')

        return self._get_collection_class()(
            self,
            name,
        )

    def print_help(self, obj):
        print()
        print(self._help_formatter.format(obj))

    def help(self):
        self.print_help(None)
        return self

    def _get_collection_class(self):
        raise NotImplementedError()

    def _get_namespace_class(self):
        raise NotImplementedError()

    def _prepare_call_kwargs(self, kwargs):
        kwargs = kwargs or {}
        if 'headers' in kwargs:
            kwargs['headers'].update(get_headers(self.api_key))
        else:
            kwargs['headers'] = get_headers(self.api_key)

        if self.default_headers:
            kwargs['headers'].update(self.default_headers)
        return kwargs

    def _get_api_error_details(self):
        if self.response is not None:
            try:
                error = self.response.json()
                if 'error_code' in error and 'errors' in error:
                    return error
            except JSONDecodeError:
                pass