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 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')
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')
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)