예제 #1
0
    def __init__(self, session=None, connection_factory=None, resource_factory=None, collection_factory=None):
        """
        Creates a ``Session`` instance.

        :param session: (Optional) Custom instantiated ``botocore`` instance.
            Useful if you have specific needs. If not present, a default
            ``Session`` will be created.
        :type session: <botocore.session.Session> instance

        :param connection_factory: (Optional) Specifies a custom
            ``ConnectionFactory`` to be used. Useful if you need to change how
            ``Connection`` objects are constructed by the session.
        :type connection_factory: <kotocore.connection.ConnectionFactory>
            instance

        :param resource_factory: (Optional) Specifies a custom
            ``ResourceFactory`` to be used. Useful if you need to change how
            ``Resource`` objects are constructed by the session.
        :type resource_factory: <kotocore.resources.ResourceFactory>
            instance

        :param collection_factory: (Optional) Specifies a custom
            ``CollectionFactory`` to be used. Useful if you need to change how
            ``Collection`` objects are constructed by the session.
        :type collection_factory: <kotocore.collections.CollectionFactory>
            instance
        """
        super(Session, self).__init__()
        self.core_session = session
        self.connection_factory = connection_factory
        self.resource_factory = resource_factory
        self.collection_factory = collection_factory

        self.cache = self.cache_class()

        if not self.core_session:
            self.core_session = botocore.session.get_session()

        self.core_session.user_agent_name = USER_AGENT_NAME
        self.core_session.user_agent_version = USER_AGENT_VERSION

        if not self.connection_factory:
            from kotocore.connection import ConnectionFactory

            self.connection_factory = ConnectionFactory(session=self)

        if not self.resource_factory:
            from kotocore.resources import ResourceFactory

            self.resource_factory = ResourceFactory(session=self)

        if not self.collection_factory:
            from kotocore.collections import CollectionFactory

            self.collection_factory = CollectionFactory(session=self)
예제 #2
0
 def setUp(self):
     super(ConnectionFactoryTestCase, self).setUp()
     self.session = Session(FakeSession(TestCoreService()))
     self.sf = ConnectionFactory(session=self.session)
     self.test_service_class = self.sf.construct_for('test')
예제 #3
0
class ConnectionFactoryTestCase(unittest.TestCase):
    def setUp(self):
        super(ConnectionFactoryTestCase, self).setUp()
        self.session = Session(FakeSession(TestCoreService()))
        self.sf = ConnectionFactory(session=self.session)
        self.test_service_class = self.sf.construct_for('test')

    def test__check_method_params(self):
        _cmp = self.test_service_class()._check_method_params
        op_params = [
            {
                'var_name': 'queue_name',
                'api_name': 'QueueName',
                'required': True,
                'type': 'string',
            },
            {
                'var_name': 'attributes',
                'api_name': 'Attributes',
                'required': False,
                'type': 'map',
            },
        ]
        # Missing the required ``queue_name`` parameter.
        self.assertRaises(TypeError, _cmp, op_params)
        self.assertRaises(TypeError, _cmp, op_params, attributes=1)

        # All required params present.
        self.assertEqual(_cmp(op_params, queue_name='boo'), None)
        self.assertEqual(_cmp(op_params, queue_name='boo', attributes=1), None)

    def test__build_service_params(self):
        _bsp = self.test_service_class()._build_service_params
        op_params = [
            {
                'var_name': 'queue_name',
                'api_name': 'QueueName',
                'required': True,
                'type': 'string',
            },
            {
                'var_name': 'attributes',
                'api_name': 'Attributes',
                'required': False,
                'type': 'map',
            },
        ]
        self.assertEqual(_bsp(op_params, queue_name='boo'), {
            'queue_name': 'boo',
        })
        self.assertEqual(_bsp(op_params, queue_name='boo', attributes=1), {
            'queue_name': 'boo',
            'attributes': 1,
        })

    def test__create_operation_method(self):
        func = self.sf._create_operation_method('test', {
            'method_name': 'test',
            'api_name': 'Test',
            'docs': 'This is a test.',
            'params': [],
            'output': True,
        })
        self.assertEqual(func.__name__, 'test')
        self.assertEqual(
            func.__doc__,
            'This is a test.\n:' + \
            'returns: The response data received\n' + \
            ':rtype: dict\n'
        )

    def test__check_for_errors(self):
        cfe = self.test_service_class()._check_for_errors

        # Make sure a success call doesn't throw an exception.
        cfe((None, {'success': True}))

        # With an empty list of errors (S3-style success.
        cfe((None, {'Errors': [], 'success': True}))

        # With a list of errors.
        with self.assertRaises(ServerError) as cm:
            cfe((None, {'Errors': [{'Message': 'Not much.'}]}))

        self.assertEqual(cm.exception.code, 'ConnectionError')
        self.assertEqual(cm.exception.message, 'Not much.')
        self.assertEqual(cm.exception.full_response, {
            'Errors': [{'Message': 'Not much.'}]
        })

        # With a single, detailed error.
        with self.assertRaises(ServerError) as cm:
            cfe((None, {
                'Errors': {
                    'Code': 'FellApart',
                    'Message': 'The robot serving the request fell apart.'
                }
            }))

        self.assertEqual(cm.exception.code, 'FellApart')
        self.assertEqual(
            cm.exception.message,
            'The robot serving the request fell apart.'
        )

        # With an error string.
        with self.assertRaises(ServerError) as cm:
            cfe((None, {'Errors': 'Sadness.'}))

        self.assertEqual(cm.exception.code, 'ConnectionError')
        self.assertEqual(cm.exception.message, 'Sadness.')

    def test__post_process_results(self):
        ppr = self.test_service_class()._post_process_results
        self.assertEqual(ppr('whatever', {}, (None, True)), True)
        self.assertEqual(ppr('whatever', {}, (None, False)), False)
        self.assertEqual(ppr('whatever', {}, (None, 'abc')), 'abc')
        self.assertEqual(ppr('whatever', {}, (None, ['abc', 1])), [
            'abc',
            1
        ])
        self.assertEqual(ppr('whatever', {}, (None, {'abc': 1})), {
            'abc': 1,
        })

    def test_integration(self):
        # Essentially testing ``_build_methods``.
        # This is a painful integration test. If the other methods don't work,
        # this will certainly fail.
        self.assertTrue(hasattr(self.test_service_class, 'create_queue'))
        self.assertTrue(hasattr(self.test_service_class, 'delete_queue'))

        ts = self.test_service_class()

        # Missing required parameters.
        self.assertRaises(TypeError, ts, 'create_queue')
        self.assertRaises(TypeError, ts, 'delete_queue')

        # Successful calls.
        self.assertEqual(ts.create_queue(queue_name='boo'), {
            'QueueUrl': 'http://example.com'
        })
        self.assertEqual(ts.delete_queue(queue_name='boo'), {'success': True})

        # Test the params.
        create_queue_params = ts._get_operation_params('create_queue')
        self.assertEqual(
            [param['var_name'] for param in create_queue_params],
            ['queue_name', 'attributes']
        )

        # Check the docstring.
        self.assertTrue(
            ':param queue_name: The name' in ts.create_queue.__doc__
        )
        self.assertTrue(
            ':type queue_name: string' in ts.create_queue.__doc__
        )

    def test_late_binding(self):
        # If the ``ConnectionDetails`` data changes, it should be reflected in
        # the dynamic methods.
        ts = self.test_service_class()

        # Successful calls.
        self.assertEqual(ts.create_queue(queue_name='boo'), {
            'QueueUrl': 'http://example.com'
        })

        # Now the required params change underneath us.
        # This is ugly/fragile, but also unlikely.
        sd = ts._details._loaded_service_data
        sd['create_queue']['params'][1]['required'] = True

        # Now this call should fail, since there's a new required parameter.
        self.assertRaises(TypeError, ts, 'create_queue')
예제 #4
0
class Session(object):
    """
    Stores all the state for a given ``kotocore`` session.

    Can dynamically create all the various ``Connection`` classes.

    Usage::

        >>> from kotocore.session import Session
        >>> session = Session()
        >>> sqs_conn = session.connect_to('sqs', region_name='us-west-2')

    """

    cache_class = ServiceCache

    def __init__(self, session=None, connection_factory=None, resource_factory=None, collection_factory=None):
        """
        Creates a ``Session`` instance.

        :param session: (Optional) Custom instantiated ``botocore`` instance.
            Useful if you have specific needs. If not present, a default
            ``Session`` will be created.
        :type session: <botocore.session.Session> instance

        :param connection_factory: (Optional) Specifies a custom
            ``ConnectionFactory`` to be used. Useful if you need to change how
            ``Connection`` objects are constructed by the session.
        :type connection_factory: <kotocore.connection.ConnectionFactory>
            instance

        :param resource_factory: (Optional) Specifies a custom
            ``ResourceFactory`` to be used. Useful if you need to change how
            ``Resource`` objects are constructed by the session.
        :type resource_factory: <kotocore.resources.ResourceFactory>
            instance

        :param collection_factory: (Optional) Specifies a custom
            ``CollectionFactory`` to be used. Useful if you need to change how
            ``Collection`` objects are constructed by the session.
        :type collection_factory: <kotocore.collections.CollectionFactory>
            instance
        """
        super(Session, self).__init__()
        self.core_session = session
        self.connection_factory = connection_factory
        self.resource_factory = resource_factory
        self.collection_factory = collection_factory

        self.cache = self.cache_class()

        if not self.core_session:
            self.core_session = botocore.session.get_session()

        self.core_session.user_agent_name = USER_AGENT_NAME
        self.core_session.user_agent_version = USER_AGENT_VERSION

        if not self.connection_factory:
            from kotocore.connection import ConnectionFactory

            self.connection_factory = ConnectionFactory(session=self)

        if not self.resource_factory:
            from kotocore.resources import ResourceFactory

            self.resource_factory = ResourceFactory(session=self)

        if not self.collection_factory:
            from kotocore.collections import CollectionFactory

            self.collection_factory = CollectionFactory(session=self)

    def get_connection(self, service_name):
        """
        Returns a ``Connection`` **class** for a given service.

        :param service_name: A string that specifies the name of the desired
            service. Ex. ``sqs``, ``sns``, ``dynamodb``, etc.
        :type service_name: string

        :rtype: <kotocore.connection.Connection subclass>
        """
        try:
            return self.cache.get_connection(service_name)
        except NotCached:
            pass

        # We didn't find it. Construct it.
        new_class = self.connection_factory.construct_for(service_name)
        self.cache.set_connection(service_name, new_class)
        return new_class

    def get_resource(self, service_name, resource_name, base_class=None):
        """
        Returns a ``Resource`` **class** for a given service.

        :param service_name: A string that specifies the name of the desired
            service. Ex. ``sqs``, ``sns``, ``dynamodb``, etc.
        :type service_name: string

        :param resource_name: A string that specifies the name of the desired
            class. Ex. ``Queue``, ``Notification``, ``Table``, etc.
        :type resource_name: string

        :param base_class: (Optional) The base class of the object. Prevents
            "magically" loading the wrong class (one with a different base).
        :type base_class: class

        :rtype: <kotocore.resources.Resource subclass>
        """
        try:
            return self.cache.get_resource(service_name, resource_name, base_class=base_class)
        except NotCached:
            pass

        # We didn't find it. Construct it.
        new_class = self.resource_factory.construct_for(service_name, resource_name, base_class=base_class)
        self.cache.set_resource(service_name, resource_name, new_class)
        return new_class

    def get_collection(self, service_name, collection_name, base_class=None):
        """
        Returns a ``Collection`` **class** for a given service.

        :param service_name: A string that specifies the name of the desired
            service. Ex. ``sqs``, ``sns``, ``dynamodb``, etc.
        :type service_name: string

        :param collection_name: A string that specifies the name of the desired
            class. Ex. ``QueueCollection``, ``NotificationCollection``,
            ``TableCollection``, etc.
        :type collection_name: string

        :param base_class: (Optional) The base class of the object. Prevents
            "magically" loading the wrong class (one with a different base).
        :type base_class: class

        :rtype: <kotocore.collections.Collection subclass>
        """
        try:
            return self.cache.get_collection(service_name, collection_name, base_class=base_class)
        except NotCached:
            pass

        # We didn't find it. Construct it.
        new_class = self.collection_factory.construct_for(service_name, collection_name, base_class=base_class)
        self.cache.set_collection(service_name, collection_name, new_class)
        return new_class

    def connect_to(self, service_name, **kwargs):
        """
        Shortcut method to make instantiating the ``Connection`` classes
        easier.

        Forwards ``**kwargs`` like region, keys, etc. on to the constructor.

        :param service_name: A string that specifies the name of the desired
            service. Ex. ``sqs``, ``sns``, ``dynamodb``, etc.
        :type service_name: string

        :rtype: <kotocore.connection.Connection> instance
        """
        service_class = self.get_connection(service_name)
        return service_class.connect_to(**kwargs)

    def get_core_service(self, service_name):
        """
        Returns a ``botocore.service.Service``.

        Mostly an abstraction for the ``*Connection`` objects to get what
        they need for introspection.

        :param service_name: A string that specifies the name of the desired
            service. Ex. ``sqs``, ``sns``, ``dynamodb``, etc.
        :type service_name: string

        :rtype: <botocore.service.Service subclass>
        """
        return self.core_session.get_service(service_name)