Add low-level API base class
Adds the foundation of a low-level REST API client. This is the final prep stage in the conversion of the object-store commands from the old restapi interface to the keystoneclient.session-based API. * api.api.BaseAPI holds the common operations Change-Id: I8fba980e3eb2d787344f766507a9d0dae49dcadf
This commit is contained in:
		
							
								
								
									
										0
									
								
								openstackclient/api/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								openstackclient/api/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										349
									
								
								openstackclient/api/api.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										349
									
								
								openstackclient/api/api.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,349 @@
 | 
			
		||||
#   Licensed under the Apache License, Version 2.0 (the "License"); you may
 | 
			
		||||
#   not use this file except in compliance with the License. You may obtain
 | 
			
		||||
#   a copy of the License at
 | 
			
		||||
#
 | 
			
		||||
#        http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
#
 | 
			
		||||
#   Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
#   distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | 
			
		||||
#   WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | 
			
		||||
#   License for the specific language governing permissions and limitations
 | 
			
		||||
#   under the License.
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
"""Base API Library"""
 | 
			
		||||
 | 
			
		||||
import simplejson as json
 | 
			
		||||
 | 
			
		||||
from keystoneclient.openstack.common.apiclient \
 | 
			
		||||
    import exceptions as ksc_exceptions
 | 
			
		||||
from keystoneclient import session as ksc_session
 | 
			
		||||
from openstackclient.common import exceptions
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class KeystoneSession(object):
 | 
			
		||||
    """Wrapper for the Keystone Session
 | 
			
		||||
 | 
			
		||||
    Restore some requests.session.Session compatibility;
 | 
			
		||||
    keystoneclient.session.Session.request() has the method and url
 | 
			
		||||
    arguments swapped from the rest of the requests-using world.
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(
 | 
			
		||||
        self,
 | 
			
		||||
        session=None,
 | 
			
		||||
        endpoint=None,
 | 
			
		||||
        **kwargs
 | 
			
		||||
    ):
 | 
			
		||||
        """Base object that contains some common API objects and methods
 | 
			
		||||
 | 
			
		||||
        :param Session session:
 | 
			
		||||
            The default session to be used for making the HTTP API calls.
 | 
			
		||||
        :param string endpoint:
 | 
			
		||||
            The URL from the Service Catalog to be used as the base for API
 | 
			
		||||
            requests on this API.
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        super(KeystoneSession, self).__init__()
 | 
			
		||||
 | 
			
		||||
        # a requests.Session-style interface
 | 
			
		||||
        self.session = session
 | 
			
		||||
        self.endpoint = endpoint
 | 
			
		||||
 | 
			
		||||
    def _request(self, method, url, session=None, **kwargs):
 | 
			
		||||
        """Perform call into session
 | 
			
		||||
 | 
			
		||||
        All API calls are funneled through this method to provide a common
 | 
			
		||||
        place to finalize the passed URL and other things.
 | 
			
		||||
 | 
			
		||||
        :param string method:
 | 
			
		||||
            The HTTP method name, i.e. ``GET``, ``PUT``, etc
 | 
			
		||||
        :param string url:
 | 
			
		||||
            The API-specific portion of the URL path
 | 
			
		||||
        :param Session session:
 | 
			
		||||
            HTTP client session
 | 
			
		||||
        :param kwargs:
 | 
			
		||||
            keyword arguments passed to requests.request().
 | 
			
		||||
        :return: the requests.Response object
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        if not session:
 | 
			
		||||
            session = self.session
 | 
			
		||||
        if not session:
 | 
			
		||||
            session = ksc_session.Session()
 | 
			
		||||
 | 
			
		||||
        if self.endpoint:
 | 
			
		||||
            if url:
 | 
			
		||||
                url = '/'.join([self.endpoint.rstrip('/'), url.lstrip('/')])
 | 
			
		||||
            else:
 | 
			
		||||
                url = self.endpoint.rstrip('/')
 | 
			
		||||
 | 
			
		||||
        # Why is ksc session backwards???
 | 
			
		||||
        return session.request(url, method, **kwargs)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BaseAPI(KeystoneSession):
 | 
			
		||||
    """Base API"""
 | 
			
		||||
 | 
			
		||||
    def __init__(
 | 
			
		||||
        self,
 | 
			
		||||
        session=None,
 | 
			
		||||
        service_type=None,
 | 
			
		||||
        endpoint=None,
 | 
			
		||||
        **kwargs
 | 
			
		||||
    ):
 | 
			
		||||
        """Base object that contains some common API objects and methods
 | 
			
		||||
 | 
			
		||||
        :param Session session:
 | 
			
		||||
            The default session to be used for making the HTTP API calls.
 | 
			
		||||
        :param string service_type:
 | 
			
		||||
            API name, i.e. ``identity`` or ``compute``
 | 
			
		||||
        :param string endpoint:
 | 
			
		||||
            The URL from the Service Catalog to be used as the base for API
 | 
			
		||||
            requests on this API.
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        super(BaseAPI, self).__init__(session=session, endpoint=endpoint)
 | 
			
		||||
 | 
			
		||||
        self.service_type = service_type
 | 
			
		||||
 | 
			
		||||
    # The basic action methods all take a Session and return dict/lists
 | 
			
		||||
 | 
			
		||||
    def create(
 | 
			
		||||
        self,
 | 
			
		||||
        url,
 | 
			
		||||
        session=None,
 | 
			
		||||
        method=None,
 | 
			
		||||
        **params
 | 
			
		||||
    ):
 | 
			
		||||
        """Create a new resource
 | 
			
		||||
 | 
			
		||||
        :param string url:
 | 
			
		||||
            The API-specific portion of the URL path
 | 
			
		||||
        :param Session session:
 | 
			
		||||
            HTTP client session
 | 
			
		||||
        :param string method:
 | 
			
		||||
            HTTP method (default POST)
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        if not method:
 | 
			
		||||
            method = 'POST'
 | 
			
		||||
        ret = self._request(method, url, session=session, **params)
 | 
			
		||||
        # Should this move into _requests()?
 | 
			
		||||
        try:
 | 
			
		||||
            return ret.json()
 | 
			
		||||
        except json.JSONDecodeError:
 | 
			
		||||
            return ret
 | 
			
		||||
 | 
			
		||||
    def delete(
 | 
			
		||||
        self,
 | 
			
		||||
        url,
 | 
			
		||||
        session=None,
 | 
			
		||||
        **params
 | 
			
		||||
    ):
 | 
			
		||||
        """Delete a resource
 | 
			
		||||
 | 
			
		||||
        :param string url:
 | 
			
		||||
            The API-specific portion of the URL path
 | 
			
		||||
        :param Session session:
 | 
			
		||||
            HTTP client session
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        return self._request('DELETE', url, **params)
 | 
			
		||||
 | 
			
		||||
    def list(
 | 
			
		||||
        self,
 | 
			
		||||
        path,
 | 
			
		||||
        session=None,
 | 
			
		||||
        body=None,
 | 
			
		||||
        detailed=False,
 | 
			
		||||
        **params
 | 
			
		||||
    ):
 | 
			
		||||
        """Return a list of resources
 | 
			
		||||
 | 
			
		||||
        GET ${ENDPOINT}/${PATH}
 | 
			
		||||
 | 
			
		||||
        path is often the object's plural resource type
 | 
			
		||||
 | 
			
		||||
        :param string path:
 | 
			
		||||
            The API-specific portion of the URL path
 | 
			
		||||
        :param Session session:
 | 
			
		||||
            HTTP client session
 | 
			
		||||
        :param body: data that will be encoded as JSON and passed in POST
 | 
			
		||||
            request (GET will be sent by default)
 | 
			
		||||
        :param bool detailed:
 | 
			
		||||
            Adds '/details' to path for some APIs to return extended attributes
 | 
			
		||||
        :returns:
 | 
			
		||||
            JSON-decoded response, could be a list or a dict-wrapped-list
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        if detailed:
 | 
			
		||||
            path = '/'.join([path.rstrip('/'), 'details'])
 | 
			
		||||
 | 
			
		||||
        if body:
 | 
			
		||||
            ret = self._request(
 | 
			
		||||
                'POST',
 | 
			
		||||
                path,
 | 
			
		||||
                # service=self.service_type,
 | 
			
		||||
                json=body,
 | 
			
		||||
                params=params,
 | 
			
		||||
            )
 | 
			
		||||
        else:
 | 
			
		||||
            ret = self._request(
 | 
			
		||||
                'GET',
 | 
			
		||||
                path,
 | 
			
		||||
                # service=self.service_type,
 | 
			
		||||
                params=params,
 | 
			
		||||
            )
 | 
			
		||||
        try:
 | 
			
		||||
            return ret.json()
 | 
			
		||||
        except json.JSONDecodeError:
 | 
			
		||||
            return ret
 | 
			
		||||
 | 
			
		||||
    # Layered actions built on top of the basic action methods do not
 | 
			
		||||
    # explicitly take a Session but one may still be passed in kwargs
 | 
			
		||||
 | 
			
		||||
    def find_attr(
 | 
			
		||||
        self,
 | 
			
		||||
        path,
 | 
			
		||||
        value=None,
 | 
			
		||||
        attr=None,
 | 
			
		||||
        resource=None,
 | 
			
		||||
    ):
 | 
			
		||||
        """Find a resource via attribute or ID
 | 
			
		||||
 | 
			
		||||
        Most APIs return a list wrapped by a dict with the resource
 | 
			
		||||
        name as key.  Some APIs (Identity) return a dict when a query
 | 
			
		||||
        string is present and there is one return value.  Take steps to
 | 
			
		||||
        unwrap these bodies and return a single dict without any resource
 | 
			
		||||
        wrappers.
 | 
			
		||||
 | 
			
		||||
        :param string path:
 | 
			
		||||
            The API-specific portion of the URL path
 | 
			
		||||
        :param string value:
 | 
			
		||||
            value to search for
 | 
			
		||||
        :param string attr:
 | 
			
		||||
            attribute to use for resource search
 | 
			
		||||
        :param string resource:
 | 
			
		||||
            plural of the object resource name; defaults to path
 | 
			
		||||
        For example:
 | 
			
		||||
            n = find(netclient, 'network', 'networks', 'matrix')
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        # Default attr is 'name'
 | 
			
		||||
        if attr is None:
 | 
			
		||||
            attr = 'name'
 | 
			
		||||
 | 
			
		||||
        # Default resource is path - in many APIs they are the same
 | 
			
		||||
        if resource is None:
 | 
			
		||||
            resource = path
 | 
			
		||||
 | 
			
		||||
        def getlist(kw):
 | 
			
		||||
            """Do list call, unwrap resource dict if present"""
 | 
			
		||||
            ret = self.list(path, **kw)
 | 
			
		||||
            if type(ret) == dict and resource in ret:
 | 
			
		||||
                ret = ret[resource]
 | 
			
		||||
            return ret
 | 
			
		||||
 | 
			
		||||
        # Search by attribute
 | 
			
		||||
        kwargs = {attr: value}
 | 
			
		||||
        data = getlist(kwargs)
 | 
			
		||||
        if type(data) == dict:
 | 
			
		||||
            return data
 | 
			
		||||
        if len(data) == 1:
 | 
			
		||||
            return data[0]
 | 
			
		||||
        if len(data) > 1:
 | 
			
		||||
            msg = "Multiple %s exist with %s='%s'"
 | 
			
		||||
            raise ksc_exceptions.CommandError(
 | 
			
		||||
                msg % (resource, attr, value),
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        # Search by id
 | 
			
		||||
        kwargs = {'id': value}
 | 
			
		||||
        data = getlist(kwargs)
 | 
			
		||||
        if len(data) == 1:
 | 
			
		||||
            return data[0]
 | 
			
		||||
        msg = "No %s with a %s or ID of '%s' found"
 | 
			
		||||
        raise exceptions.CommandError(msg % (resource, attr, value))
 | 
			
		||||
 | 
			
		||||
    def find_bulk(
 | 
			
		||||
        self,
 | 
			
		||||
        path,
 | 
			
		||||
        **kwargs
 | 
			
		||||
    ):
 | 
			
		||||
        """Bulk load and filter locally
 | 
			
		||||
 | 
			
		||||
        :param string path:
 | 
			
		||||
            The API-specific portion of the URL path
 | 
			
		||||
        :param kwargs:
 | 
			
		||||
            A dict of AVPs to match - logical AND
 | 
			
		||||
        :returns: list of resource dicts
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        items = self.list(path)
 | 
			
		||||
        if type(items) == dict:
 | 
			
		||||
            # strip off the enclosing dict
 | 
			
		||||
            key = list(items.keys())[0]
 | 
			
		||||
            items = items[key]
 | 
			
		||||
 | 
			
		||||
        ret = []
 | 
			
		||||
        for o in items:
 | 
			
		||||
            try:
 | 
			
		||||
                if all(o[attr] == kwargs[attr] for attr in kwargs.keys()):
 | 
			
		||||
                    ret.append(o)
 | 
			
		||||
            except KeyError:
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
        return ret
 | 
			
		||||
 | 
			
		||||
    def find_one(
 | 
			
		||||
        self,
 | 
			
		||||
        path,
 | 
			
		||||
        **kwargs
 | 
			
		||||
    ):
 | 
			
		||||
        """Find a resource by name or ID
 | 
			
		||||
 | 
			
		||||
        :param string path:
 | 
			
		||||
            The API-specific portion of the URL path
 | 
			
		||||
        :returns:
 | 
			
		||||
            resource dict
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        bulk_list = self.find_bulk(path, **kwargs)
 | 
			
		||||
        num_bulk = len(bulk_list)
 | 
			
		||||
        if num_bulk == 0:
 | 
			
		||||
            msg = "none found"
 | 
			
		||||
            raise ksc_exceptions.NotFound(msg)
 | 
			
		||||
        elif num_bulk > 1:
 | 
			
		||||
            msg = "many found"
 | 
			
		||||
            raise RuntimeError(msg)
 | 
			
		||||
        return bulk_list[0]
 | 
			
		||||
 | 
			
		||||
    def find(
 | 
			
		||||
        self,
 | 
			
		||||
        path,
 | 
			
		||||
        value=None,
 | 
			
		||||
        attr=None,
 | 
			
		||||
    ):
 | 
			
		||||
        """Find a single resource by name or ID
 | 
			
		||||
 | 
			
		||||
        :param string path:
 | 
			
		||||
            The API-specific portion of the URL path
 | 
			
		||||
        :param string search:
 | 
			
		||||
            search expression
 | 
			
		||||
        :param string attr:
 | 
			
		||||
            name of attribute for secondary search
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            ret = self._request('GET', "/%s/%s" % (path, value)).json()
 | 
			
		||||
        except ksc_exceptions.NotFound:
 | 
			
		||||
            kwargs = {attr: value}
 | 
			
		||||
            try:
 | 
			
		||||
                ret = self.find_one("/%s/detail" % (path), **kwargs)
 | 
			
		||||
            except ksc_exceptions.NotFound:
 | 
			
		||||
                msg = "%s not found" % value
 | 
			
		||||
                raise ksc_exceptions.NotFound(msg)
 | 
			
		||||
 | 
			
		||||
        return ret
 | 
			
		||||
							
								
								
									
										0
									
								
								openstackclient/tests/api/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								openstackclient/tests/api/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										362
									
								
								openstackclient/tests/api/test_api.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										362
									
								
								openstackclient/tests/api/test_api.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,362 @@
 | 
			
		||||
#   Licensed under the Apache License, Version 2.0 (the "License"); you may
 | 
			
		||||
#   not use this file except in compliance with the License. You may obtain
 | 
			
		||||
#   a copy of the License at
 | 
			
		||||
#
 | 
			
		||||
#        http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
#
 | 
			
		||||
#   Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
#   distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | 
			
		||||
#   WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | 
			
		||||
#   License for the specific language governing permissions and limitations
 | 
			
		||||
#   under the License.
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
"""Base API Library Tests"""
 | 
			
		||||
 | 
			
		||||
from requests_mock.contrib import fixture
 | 
			
		||||
 | 
			
		||||
from keystoneclient import session
 | 
			
		||||
from openstackclient.api import api
 | 
			
		||||
from openstackclient.common import exceptions
 | 
			
		||||
from openstackclient.tests import utils
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
RESP_ITEM_1 = {
 | 
			
		||||
    'id': '1',
 | 
			
		||||
    'name': 'alpha',
 | 
			
		||||
    'status': 'UP',
 | 
			
		||||
}
 | 
			
		||||
RESP_ITEM_2 = {
 | 
			
		||||
    'id': '2',
 | 
			
		||||
    'name': 'beta',
 | 
			
		||||
    'status': 'DOWN',
 | 
			
		||||
}
 | 
			
		||||
RESP_ITEM_3 = {
 | 
			
		||||
    'id': '3',
 | 
			
		||||
    'name': 'delta',
 | 
			
		||||
    'status': 'UP',
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
LIST_RESP = [RESP_ITEM_1, RESP_ITEM_2]
 | 
			
		||||
 | 
			
		||||
LIST_BODY = {
 | 
			
		||||
    'p1': 'xxx',
 | 
			
		||||
    'p2': 'yyy',
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestSession(utils.TestCase):
 | 
			
		||||
 | 
			
		||||
    BASE_URL = 'https://api.example.com:1234/vX'
 | 
			
		||||
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        super(TestSession, self).setUp()
 | 
			
		||||
        self.sess = session.Session()
 | 
			
		||||
        self.requests_mock = self.useFixture(fixture.Fixture())
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestKeystoneSession(TestSession):
 | 
			
		||||
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        super(TestKeystoneSession, self).setUp()
 | 
			
		||||
        self.api = api.KeystoneSession(
 | 
			
		||||
            session=self.sess,
 | 
			
		||||
            endpoint=self.BASE_URL,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def test_session_request(self):
 | 
			
		||||
        self.requests_mock.register_uri(
 | 
			
		||||
            'GET',
 | 
			
		||||
            self.BASE_URL + '/qaz',
 | 
			
		||||
            json=RESP_ITEM_1,
 | 
			
		||||
            status_code=200,
 | 
			
		||||
        )
 | 
			
		||||
        ret = self.api._request('GET', '/qaz')
 | 
			
		||||
        self.assertEqual(RESP_ITEM_1, ret.json())
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestBaseAPI(TestSession):
 | 
			
		||||
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        super(TestBaseAPI, self).setUp()
 | 
			
		||||
        self.api = api.BaseAPI(
 | 
			
		||||
            session=self.sess,
 | 
			
		||||
            endpoint=self.BASE_URL,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def test_create_post(self):
 | 
			
		||||
        self.requests_mock.register_uri(
 | 
			
		||||
            'POST',
 | 
			
		||||
            self.BASE_URL + '/qaz',
 | 
			
		||||
            json=RESP_ITEM_1,
 | 
			
		||||
            status_code=202,
 | 
			
		||||
        )
 | 
			
		||||
        ret = self.api.create('qaz')
 | 
			
		||||
        self.assertEqual(RESP_ITEM_1, ret)
 | 
			
		||||
 | 
			
		||||
    def test_create_put(self):
 | 
			
		||||
        self.requests_mock.register_uri(
 | 
			
		||||
            'PUT',
 | 
			
		||||
            self.BASE_URL + '/qaz',
 | 
			
		||||
            json=RESP_ITEM_1,
 | 
			
		||||
            status_code=202,
 | 
			
		||||
        )
 | 
			
		||||
        ret = self.api.create('qaz', method='PUT')
 | 
			
		||||
        self.assertEqual(RESP_ITEM_1, ret)
 | 
			
		||||
 | 
			
		||||
    def test_delete(self):
 | 
			
		||||
        self.requests_mock.register_uri(
 | 
			
		||||
            'DELETE',
 | 
			
		||||
            self.BASE_URL + '/qaz',
 | 
			
		||||
            status_code=204,
 | 
			
		||||
        )
 | 
			
		||||
        ret = self.api.delete('qaz')
 | 
			
		||||
        self.assertEqual(204, ret.status_code)
 | 
			
		||||
 | 
			
		||||
    # find tests
 | 
			
		||||
 | 
			
		||||
    def test_find_attr_by_id(self):
 | 
			
		||||
 | 
			
		||||
        # All first requests (by name) will fail in this test
 | 
			
		||||
        self.requests_mock.register_uri(
 | 
			
		||||
            'GET',
 | 
			
		||||
            self.BASE_URL + '/qaz?name=1',
 | 
			
		||||
            json={'qaz': []},
 | 
			
		||||
            status_code=200,
 | 
			
		||||
        )
 | 
			
		||||
        self.requests_mock.register_uri(
 | 
			
		||||
            'GET',
 | 
			
		||||
            self.BASE_URL + '/qaz?id=1',
 | 
			
		||||
            json={'qaz': [RESP_ITEM_1]},
 | 
			
		||||
            status_code=200,
 | 
			
		||||
        )
 | 
			
		||||
        ret = self.api.find_attr('qaz', '1')
 | 
			
		||||
        self.assertEqual(RESP_ITEM_1, ret)
 | 
			
		||||
 | 
			
		||||
        # value not found
 | 
			
		||||
        self.requests_mock.register_uri(
 | 
			
		||||
            'GET',
 | 
			
		||||
            self.BASE_URL + '/qaz?name=0',
 | 
			
		||||
            json={'qaz': []},
 | 
			
		||||
            status_code=200,
 | 
			
		||||
        )
 | 
			
		||||
        self.requests_mock.register_uri(
 | 
			
		||||
            'GET',
 | 
			
		||||
            self.BASE_URL + '/qaz?id=0',
 | 
			
		||||
            json={'qaz': []},
 | 
			
		||||
            status_code=200,
 | 
			
		||||
        )
 | 
			
		||||
        self.assertRaises(
 | 
			
		||||
            exceptions.CommandError,
 | 
			
		||||
            self.api.find_attr,
 | 
			
		||||
            'qaz',
 | 
			
		||||
            '0',
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        # Attribute other than 'name'
 | 
			
		||||
        self.requests_mock.register_uri(
 | 
			
		||||
            'GET',
 | 
			
		||||
            self.BASE_URL + '/qaz?status=UP',
 | 
			
		||||
            json={'qaz': [RESP_ITEM_1]},
 | 
			
		||||
            status_code=200,
 | 
			
		||||
        )
 | 
			
		||||
        ret = self.api.find_attr('qaz', 'UP', attr='status')
 | 
			
		||||
        self.assertEqual(RESP_ITEM_1, ret)
 | 
			
		||||
        ret = self.api.find_attr('qaz', value='UP', attr='status')
 | 
			
		||||
        self.assertEqual(RESP_ITEM_1, ret)
 | 
			
		||||
 | 
			
		||||
    def test_find_attr_by_name(self):
 | 
			
		||||
        self.requests_mock.register_uri(
 | 
			
		||||
            'GET',
 | 
			
		||||
            self.BASE_URL + '/qaz?name=alpha',
 | 
			
		||||
            json={'qaz': [RESP_ITEM_1]},
 | 
			
		||||
            status_code=200,
 | 
			
		||||
        )
 | 
			
		||||
        ret = self.api.find_attr('qaz', 'alpha')
 | 
			
		||||
        self.assertEqual(RESP_ITEM_1, ret)
 | 
			
		||||
 | 
			
		||||
        # value not found
 | 
			
		||||
        self.requests_mock.register_uri(
 | 
			
		||||
            'GET',
 | 
			
		||||
            self.BASE_URL + '/qaz?name=0',
 | 
			
		||||
            json={'qaz': []},
 | 
			
		||||
            status_code=200,
 | 
			
		||||
        )
 | 
			
		||||
        self.requests_mock.register_uri(
 | 
			
		||||
            'GET',
 | 
			
		||||
            self.BASE_URL + '/qaz?id=0',
 | 
			
		||||
            json={'qaz': []},
 | 
			
		||||
            status_code=200,
 | 
			
		||||
        )
 | 
			
		||||
        self.assertRaises(
 | 
			
		||||
            exceptions.CommandError,
 | 
			
		||||
            self.api.find_attr,
 | 
			
		||||
            'qaz',
 | 
			
		||||
            '0',
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        # Attribute other than 'name'
 | 
			
		||||
        self.requests_mock.register_uri(
 | 
			
		||||
            'GET',
 | 
			
		||||
            self.BASE_URL + '/qaz?status=UP',
 | 
			
		||||
            json={'qaz': [RESP_ITEM_1]},
 | 
			
		||||
            status_code=200,
 | 
			
		||||
        )
 | 
			
		||||
        ret = self.api.find_attr('qaz', 'UP', attr='status')
 | 
			
		||||
        self.assertEqual(RESP_ITEM_1, ret)
 | 
			
		||||
        ret = self.api.find_attr('qaz', value='UP', attr='status')
 | 
			
		||||
        self.assertEqual(RESP_ITEM_1, ret)
 | 
			
		||||
 | 
			
		||||
    def test_find_attr_path_resource(self):
 | 
			
		||||
 | 
			
		||||
        # Test resource different than path
 | 
			
		||||
        self.requests_mock.register_uri(
 | 
			
		||||
            'GET',
 | 
			
		||||
            self.BASE_URL + '/wsx?name=1',
 | 
			
		||||
            json={'qaz': []},
 | 
			
		||||
            status_code=200,
 | 
			
		||||
        )
 | 
			
		||||
        self.requests_mock.register_uri(
 | 
			
		||||
            'GET',
 | 
			
		||||
            self.BASE_URL + '/wsx?id=1',
 | 
			
		||||
            json={'qaz': [RESP_ITEM_1]},
 | 
			
		||||
            status_code=200,
 | 
			
		||||
        )
 | 
			
		||||
        ret = self.api.find_attr('wsx', '1', resource='qaz')
 | 
			
		||||
        self.assertEqual(RESP_ITEM_1, ret)
 | 
			
		||||
 | 
			
		||||
    def test_find_bulk_none(self):
 | 
			
		||||
        self.requests_mock.register_uri(
 | 
			
		||||
            'GET',
 | 
			
		||||
            self.BASE_URL + '/qaz',
 | 
			
		||||
            json=LIST_RESP,
 | 
			
		||||
            status_code=200,
 | 
			
		||||
        )
 | 
			
		||||
        ret = self.api.find_bulk('qaz')
 | 
			
		||||
        self.assertEqual(LIST_RESP, ret)
 | 
			
		||||
 | 
			
		||||
    def test_find_bulk_one(self):
 | 
			
		||||
        self.requests_mock.register_uri(
 | 
			
		||||
            'GET',
 | 
			
		||||
            self.BASE_URL + '/qaz',
 | 
			
		||||
            json=LIST_RESP,
 | 
			
		||||
            status_code=200,
 | 
			
		||||
        )
 | 
			
		||||
        ret = self.api.find_bulk('qaz', id='1')
 | 
			
		||||
        self.assertEqual([LIST_RESP[0]], ret)
 | 
			
		||||
 | 
			
		||||
        ret = self.api.find_bulk('qaz', id='0')
 | 
			
		||||
        self.assertEqual([], ret)
 | 
			
		||||
 | 
			
		||||
        ret = self.api.find_bulk('qaz', name='beta')
 | 
			
		||||
        self.assertEqual([LIST_RESP[1]], ret)
 | 
			
		||||
 | 
			
		||||
        ret = self.api.find_bulk('qaz', error='bogus')
 | 
			
		||||
        self.assertEqual([], ret)
 | 
			
		||||
 | 
			
		||||
    def test_find_bulk_two(self):
 | 
			
		||||
        self.requests_mock.register_uri(
 | 
			
		||||
            'GET',
 | 
			
		||||
            self.BASE_URL + '/qaz',
 | 
			
		||||
            json=LIST_RESP,
 | 
			
		||||
            status_code=200,
 | 
			
		||||
        )
 | 
			
		||||
        ret = self.api.find_bulk('qaz', id='1', name='alpha')
 | 
			
		||||
        self.assertEqual([LIST_RESP[0]], ret)
 | 
			
		||||
 | 
			
		||||
        ret = self.api.find_bulk('qaz', id='1', name='beta')
 | 
			
		||||
        self.assertEqual([], ret)
 | 
			
		||||
 | 
			
		||||
        ret = self.api.find_bulk('qaz', id='1', error='beta')
 | 
			
		||||
        self.assertEqual([], ret)
 | 
			
		||||
 | 
			
		||||
    def test_find_bulk_dict(self):
 | 
			
		||||
        self.requests_mock.register_uri(
 | 
			
		||||
            'GET',
 | 
			
		||||
            self.BASE_URL + '/qaz',
 | 
			
		||||
            json={'qaz': LIST_RESP},
 | 
			
		||||
            status_code=200,
 | 
			
		||||
        )
 | 
			
		||||
        ret = self.api.find_bulk('qaz', id='1')
 | 
			
		||||
        self.assertEqual([LIST_RESP[0]], ret)
 | 
			
		||||
 | 
			
		||||
    # list tests
 | 
			
		||||
 | 
			
		||||
    def test_list_no_body(self):
 | 
			
		||||
        self.requests_mock.register_uri(
 | 
			
		||||
            'GET',
 | 
			
		||||
            self.BASE_URL,
 | 
			
		||||
            json=LIST_RESP,
 | 
			
		||||
            status_code=200,
 | 
			
		||||
        )
 | 
			
		||||
        ret = self.api.list('')
 | 
			
		||||
        self.assertEqual(LIST_RESP, ret)
 | 
			
		||||
 | 
			
		||||
        self.requests_mock.register_uri(
 | 
			
		||||
            'GET',
 | 
			
		||||
            self.BASE_URL + '/qaz',
 | 
			
		||||
            json=LIST_RESP,
 | 
			
		||||
            status_code=200,
 | 
			
		||||
        )
 | 
			
		||||
        ret = self.api.list('qaz')
 | 
			
		||||
        self.assertEqual(LIST_RESP, ret)
 | 
			
		||||
 | 
			
		||||
    def test_list_params(self):
 | 
			
		||||
        params = {'format': 'json'}
 | 
			
		||||
        self.requests_mock.register_uri(
 | 
			
		||||
            'GET',
 | 
			
		||||
            self.BASE_URL + '?format=json',
 | 
			
		||||
            json=LIST_RESP,
 | 
			
		||||
            status_code=200,
 | 
			
		||||
        )
 | 
			
		||||
        ret = self.api.list('', **params)
 | 
			
		||||
        self.assertEqual(LIST_RESP, ret)
 | 
			
		||||
 | 
			
		||||
        self.requests_mock.register_uri(
 | 
			
		||||
            'GET',
 | 
			
		||||
            self.BASE_URL + '/qaz?format=json',
 | 
			
		||||
            json=LIST_RESP,
 | 
			
		||||
            status_code=200,
 | 
			
		||||
        )
 | 
			
		||||
        ret = self.api.list('qaz', **params)
 | 
			
		||||
        self.assertEqual(LIST_RESP, ret)
 | 
			
		||||
 | 
			
		||||
    def test_list_body(self):
 | 
			
		||||
        self.requests_mock.register_uri(
 | 
			
		||||
            'POST',
 | 
			
		||||
            self.BASE_URL + '/qaz',
 | 
			
		||||
            json=LIST_RESP,
 | 
			
		||||
            status_code=200,
 | 
			
		||||
        )
 | 
			
		||||
        ret = self.api.list('qaz', body=LIST_BODY)
 | 
			
		||||
        self.assertEqual(LIST_RESP, ret)
 | 
			
		||||
 | 
			
		||||
    def test_list_detailed(self):
 | 
			
		||||
        self.requests_mock.register_uri(
 | 
			
		||||
            'GET',
 | 
			
		||||
            self.BASE_URL + '/qaz/details',
 | 
			
		||||
            json=LIST_RESP,
 | 
			
		||||
            status_code=200,
 | 
			
		||||
        )
 | 
			
		||||
        ret = self.api.list('qaz', detailed=True)
 | 
			
		||||
        self.assertEqual(LIST_RESP, ret)
 | 
			
		||||
 | 
			
		||||
    def test_list_filtered(self):
 | 
			
		||||
        self.requests_mock.register_uri(
 | 
			
		||||
            'GET',
 | 
			
		||||
            self.BASE_URL + '/qaz?attr=value',
 | 
			
		||||
            json=LIST_RESP,
 | 
			
		||||
            status_code=200,
 | 
			
		||||
        )
 | 
			
		||||
        ret = self.api.list('qaz', attr='value')
 | 
			
		||||
        self.assertEqual(LIST_RESP, ret)
 | 
			
		||||
 | 
			
		||||
    def test_list_wrapped(self):
 | 
			
		||||
        self.requests_mock.register_uri(
 | 
			
		||||
            'GET',
 | 
			
		||||
            self.BASE_URL + '/qaz?attr=value',
 | 
			
		||||
            json={'responses': LIST_RESP},
 | 
			
		||||
            status_code=200,
 | 
			
		||||
        )
 | 
			
		||||
        ret = self.api.list('qaz', attr='value')
 | 
			
		||||
        self.assertEqual({'responses': LIST_RESP}, ret)
 | 
			
		||||
		Reference in New Issue
	
	Block a user