class TestLazyLoading(TestCase): ''' Test concentrating on lazy-loading and actions via the assembler. ''' def setUp(self): self.locator = Locator() self.transformer = Transformer(self.locator) self.assembler = Assembler(self.transformer) filename = 'locator-lazy-action.xml' filepath = abspath(join(dirname(__file__), '..', 'data', filename)) self.assembler.load(filepath) def tearDown(self): del self.locator del self.transformer del self.assembler def test_lazy_initialization(self): self.assertIsInstance(self.locator.get('alpha').call_accompany(), Beta) self.assertIsInstance(self.locator.get('beta'), Beta) self.assertEquals(self.locator.get('alpha').call_accompany(), self.locator.get('beta')) # Test for actionable events... due to be in a seperate files. def test_actions_without_events(self): c = self.locator.get('charlie') self.assertIsInstance(c.introduce, Action, c.introduce.__class__) def test_actions(self): self.locator.get('charlie').cook()
def _register_imagination_services(self, configuration, base_path): """ Register services. """ service_blocks = configuration.children('service') assembler = ImaginationAssembler(ImaginationTransformer(AppServices)) for service_block in service_blocks: service_config_path = service_block.data() # If the file path contains ':', treat the leading part for the full # module name and re-assembles the file path. config_file_path = resolve_file_path(service_config_path) if service_config_path[0] != '/': config_file_path = os.path.join(base_path, service_config_path) self._logger.info('Loading services from {}'.format(config_file_path)) assembler.load(config_file_path)
class Core(object): """ The Core of the Framework This relies on Imagination Framework. """ def __init__(self, locator=None): self.locator = locator or Locator() self.transformer = Transformer(self.locator) self.assembler = Assembler(self.transformer) self._cache_map = None @contextmanager def passive_mode(self): self.assembler.activate_passive_loading() yield self.assembler.deactivate_passive_loading() def get(self, id): """ Get the service container. """ return self.locator.get(id) def load(self, *paths): """ Load service containers from multiple configuration files. """ with self.passive_mode(): [ self.assembler.load(path) for path in paths ] self._cache_map = None def all(self): if not self._cache_map: self._cache_map = { i: self.locator.get(i) for i in self.locator.entity_identifiers } return self._cache_map def set_entity(self, entity_id, entity_fqcn, *args, **kwargs): try: entity = self._create_entity(entity_id, entity_fqcn, args, kwargs) self.locator.set(entity_id, entity) except ImportError as exception: raise ImportError('Failed to register {} ({})'.format(entity_id, entity_fqcn)) def _create_entity(self, id, entity_fqcn, args, kwargs): loader = Loader(entity_fqcn) return Entity(id, loader, *args, **kwargs)
class test_locator_with_proxy_and_factorization(TestCase): ''' Test the locator via the assembler for factorization. ''' def setUp(self): self.locator = Locator() self.transformer = Transformer(self.locator) self.assembler = Assembler(self.transformer) def tearDown(self): del self.locator del self.transformer del self.assembler def test_good_locator_xml_on_entity_registration(self): self.__prepare_good_locator_from_xml() # Normal entity self.assertTrue(self.locator.has('manager'), self.locator._entities) # Factorized entities self.assertTrue(self.locator.has('worker.alpha')) self.assertTrue(self.locator.has('worker.bravo')) self.assertTrue(self.locator.has('def.doubler')) self.assertTrue(self.locator.has('def.trippler')) def test_get_object(self): self.__prepare_good_locator_from_xml() ticker = self.locator.get('ticker') trigger = self.locator.get('something') entity = self.locator.get('worker.alpha') self.assertIsInstance(entity, Worker) self.assertEqual(entity.name, 'Alpha') self.assertEqual(0, len(ticker.sequence)) trigger.alpha() self.assertEqual(1, len(ticker.sequence)) def test_get_callback(self): self.__prepare_good_locator_from_xml() ticker = self.locator.get('ticker') trigger = self.locator.get('something') doubler = self.locator.get('def.doubler') self.assertTrue(callable(doubler)) self.assertEqual(12, doubler(6)) self.assertEqual(0, len(ticker.sequence)) trigger.doubler() # No interception for callable object as of 1.9. self.assertEqual(0, len(ticker.sequence)) def __get_data_path(self, filename): return abspath(join(dirname(__file__), '..', 'data', filename)) def __load_from_xml(self, path_from_data): test_file = self.__get_data_path(path_from_data) self.assembler.load(test_file) def __prepare_good_locator_from_xml(self): self.__load_from_xml('locator-factorization.xml')
class Application(BaseApplication): """ Interface to bootstrap a WSGI application with Tornado Web Server. `settings` is a dictionary of extra settings to Tornado engine. For more information, please read Tornado documentation. """ _registered_routing_types = ['controller', 'proxy', 'redirection', 'resource'] _default_services = [ ('finder', 'tori.common.Finder', [], {}), ('renderer', 'tori.template.service.RenderingService', [], {}), ('session', 'tori.session.repository.memory.Memory', [], {}), ('routing_map', 'tori.navigation.RoutingMap', [], {}), ('db', 'passerine.db.manager.ManagerFactory', [], {}) ] _data_transformer = ImaginationTransformer(ImaginationLocator()) def __init__(self, configuration_location, **settings): BaseApplication.__init__(self, **settings) self._service_assembler = ImaginationAssembler(ImaginationTransformer(AppServices)) self._config_main_path = os.path.join(self._base_path, configuration_location) self._config_base_path = os.path.dirname(self._config_main_path) self._config = load_from_file(self._config_main_path) # Initialize the routing map self._routing_map = RoutingMap() # Default properties self._scope = settings['scope'] if 'scope' in settings else None self._port = 8000 # Register the default services. self._register_default_services() # Add the main configuration to the watch list. watch(self._config_main_path) # Configure with the configuration files self._service_assembler.activate_passive_loading() for inclusion in self._config.children('include'): self._load_inclusion(inclusion) self._configure(self._config) self._prepare_db_connections() self._prepare_session_manager() self._service_assembler.deactivate_passive_loading() # Override the properties with the parameters. if 'port' in settings: self._port = settings['port'] self._logger.info('Changed the listening port: %s' % self._port) # Update the routing map AppServices.get('routing_map').update(self._routing_map) # Normal procedure self._update_routes(self._routing_map.export()) self.listen(self._port) self._activate() def _prepare_session_manager(self): config = self._settings['session'] self._set_service_entity('session', config['class'], **config['params']) def _prepare_db_connections(self): db_config = self._settings['db'] manager_config = db_config['managers'] em_factory = AppServices.get('db') for alias in manager_config: url = manager_config[alias]['url'] em_factory.set(alias, url) def callback(em_factory, db_alias): return em_factory.get(db_alias) callback_proxy = CallbackProxy(callback, em_factory, alias) AppServices.set('db.{}'.format(alias), callback_proxy) def _load_inclusion(self, inclusion): source_location = inclusion.attribute('src') if source_location[0] != '/': source_location = os.path.join(self._config_base_path, source_location) pre_config = load_from_file(source_location) self._configure(pre_config, source_location) watch(source_location) self._logger.info('Included the configuration from %s' % source_location) def _load_new_style_configuration(self, configuration): # Load the data directly from a JSON file. for inclusion in configuration.children('use'): source_location = inclusion.attribute('src') if source_location[0] != '/': source_location = os.path.join(self._config_base_path, source_location) with open(source_location) as f: decoded_config = json.load(f) self._override_sub_config_tree(self._settings, decoded_config) watch(source_location) def _override_sub_config_tree(self, original_subtree, modified_subtree): for key in modified_subtree: if key not in original_subtree: original_subtree[key] = modified_subtree[key] continue original_node_type = type(original_subtree[key]) modified_node_type = type(modified_subtree[key]) if original_node_type is dict: if modified_node_type != original_node_type: raise ValueError('The overriding configuration tree does not align with the predefined one.') self._override_sub_config_tree(original_subtree[key], modified_subtree[key]) continue original_subtree[key] = modified_subtree[key] def _configure(self, configuration, config_path=None): if len(configuration.children('server')) > 1: raise InvalidConfigurationError('Too many server configuration.') if len(configuration.children('routes')) > 1: raise InvalidConfigurationError('Too many routing configuration.') if len(configuration.children('settings')) > 1: raise InvalidConfigurationError('Too many setting groups (limited to 1).') self._load_new_style_configuration(configuration) # Then, load the legacy configuration. (Deprecated in 3.1) # Load the general settings for config in configuration.find('server config'): key = config.attribute('key') kind = config.attribute('type') if not key: raise InvalidConfigurationError('Invalid server configuration key') self._settings[key] = self._data_transformer.cast(config, kind) # Set the cookie secret for secure cookies. client_secret = configuration.find('server secret') if client_secret: self._settings['cookie_secret'] = client_secret.data() # Set the port number. port = configuration.find('server port') if len(port) > 1: raise DuplicatedPortError() elif port: self._port = port.data() # Find the debugging flag if (configuration.find('server debug')): self._settings['debug'] = configuration.find('server debug').data().lower() == 'true' self._logger.info('Debug Mode: {}'.format('On' if self._settings['debug'] else 'Off')) for setting in configuration.children('settings').children('setting'): setting_name = setting.attr('name') if not setting_name: raise InvalidConfigurationError('A setting block does not specify the name. ({})'.format(config_path or 'primary configuration')) self._settings[setting_name] = setting.data() # Exclusive procedures self._register_imagination_services(configuration, config_path or self._config_base_path) self._map_routing_table(configuration) self._set_error_delegate(configuration) def _set_error_delegate(self, configuration): """ Set a new error delegate based on the given configuration file if specified. """ delegate = configuration.find('server error').data() if delegate: self._logger.info('Custom Error Handler: %s' % delegate) tornado.web.ErrorHandler = ImaginationLoader(delegate).package def _register_default_services(self): for id, package_path, args, kwargs in self._default_services: try: self._set_service_entity(id, package_path, *args, **kwargs) except ImportError as exception: if id == "db": self._logger.info('Ignored {} as package "passerine" is neither available or loadable (containing errors).'.format(package_path)) continue raise ImportError('Failed to register {} ({})'.format(id, package_path)) def _register_imagination_services(self, configuration, base_path): """ Register services. """ service_blocks = configuration.children('service') for service_block in service_blocks: service_config_path = service_block.data() # If the file path contains ':', treat the leading part for the full # module name and re-assembles the file path. config_file_path = resolve_file_path(service_config_path) if service_config_path[0] != '/': config_file_path = os.path.join(base_path, service_config_path) self._logger.info('Loading services from {}'.format(config_file_path)) self._service_assembler.load(config_file_path) def _map_routing_table(self, configuration): """ Update a routing table based on the configuration. """ routing_sequences = configuration.children('routes') self._logger.debug('Registering the routes from the configuration...') if not routing_sequences: return # Register the routes to controllers. for routing_sequence in routing_sequences: new_routing_map = RoutingMap.make(routing_sequence, self._base_path) self._routing_map.update(new_routing_map) self._logger.debug('Registered the routes from the configuration.') def _make_service_entity(self, id, package_path, *args, **kwargs): """ Make and return a service entity. *id* is the ID of a new service entity. *package_path* is the package path. *args* and *kwargs* are parameters used to instantiate the service. """ loader = ImaginationLoader(package_path) entity = ImaginationEntity(id, loader, *args, **kwargs) return entity def _set_service_entity(self, id, package_path, *args, **kwargs): """ Set the given service entity. *id* is the ID of a new service entity. *package_path* is the package path. *args* and *kwargs* are parameters used to instantiate the service. """ AppServices.set(id, self._make_service_entity(id, package_path, *args, **kwargs)) def get_route(self, routing_pattern): """ Get the route. """ return self._routing_map.find_by_pattern(routing_pattern)
class TestAction(TestCase): ''' Test concentrating on lazy-loading and actions via the assembler. ''' def setUp(self): self.locator = Locator() self.transformer = Transformer(self.locator) self.assembler = Assembler(self.transformer) filename = 'locator-lazy-action.xml' filepath = abspath(join(dirname(__file__), '..', 'data', filename)) self.assembler.load(filepath) def tearDown(self): del self.locator del self.transformer del self.assembler def test_interceptable_method(self): c = self.locator.get('charlie') self.assertIsInstance(c.introduce, Action) self.assertEquals(c.name, c.introduce()) def test_normal_execution(self): ''' This is a sanity test. ''' expected_log_sequence = [ # sequence as charlie cooks 'Charlie: introduce itself as "Charlie"', 'Alpha: order "egg and becon"', 'Charlie: repeat "egg and becon"', 'Alpha: confirm for egg and becon', 'Charlie: respond "wilco"', 'Charlie: cook', # sequence as charlie serves 'Alpha: speak to Beta, "watch your hand"', 'Beta: acknowledge', 'Alpha: wash hands', 'Charlie: serve' ] alpha = self.locator.get('alpha') charlie = self.locator.get('charlie') charlie.cook() charlie.serve() self.assertEquals( len(expected_log_sequence), len(Conversation.logs), 'The number of sequences in the mock scenario must be the same.' ) for step in range(len(Conversation.logs)): expectation = expected_log_sequence[step] actual = Conversation.logs[step] self.assertEquals( expectation, actual, 'Failed at step {step}: should be "{expectation}", not "{actual}"'.format( step=step, expectation=expectation, actual=actual ) ) #print '\n'.join(Conversation.logs)
class TestLocator(TestCase): ''' Test the locator via the assembler. ''' class UnknownEntity(object): pass def setUp(self): self.locator = Locator() self.transformer = Transformer(self.locator) self.assembler = Assembler(self.transformer) def tearDown(self): del self.locator del self.transformer del self.assembler def test_checker(self): entity = self.__make_good_entity() self.assertFalse(self.locator.has('poo')) self.assertRaises(UnknownEntityError, self.locator.get, ('poo')) def test_before_activation(self): entity = self.__make_good_entity() self.locator.set('poo', entity) self.assertTrue(self.locator.has('poo')) self.assertFalse(entity.activated) def test_after_activation(self): entity = self.__make_good_entity() self.locator.set('poo', entity) self.assertIsInstance(self.locator.get('poo'), PlainOldObject) self.assertTrue(entity.activated) def test_good_locator_xml_on_entity_registration(self): self.__prepare_good_locator_from_xml() self.assertTrue(self.locator.has('poo')) self.assertTrue(self.locator.has('poow-1')) self.assertTrue(self.locator.has('poow-2')) self.assertTrue(self.locator.has('dioc')) self.assertTrue(self.locator.has('dioe')) def test_good_locator_xml_on_class_injection(self): self.__prepare_good_locator_from_xml() self.assertEqual(self.locator.get('dioc').r, PlainOldObject) def test_good_locator_xml_on_class_injection(self): self.__prepare_good_locator_from_xml() self.assertIsInstance(self.locator.get('dioe').e, PlainOldObjectWithParameters) def test_entities_with_same_class(self): self.__prepare_good_locator_from_xml() self.assertIsInstance(self.locator.get('poow-1'), PlainOldObjectWithParameters) self.assertIsInstance(self.locator.get('poow-2'), PlainOldObjectWithParameters) self.assertNotEquals(self.locator.get('poow-1').method(), self.locator.get('poow-2').method()) self.assertEquals('%.2f' % self.locator.get('poow-1').method(), '0.67') self.assertEquals(self.locator.get('poow-2').method(), 35) def __make_good_entity(self): return Entity( 'poo', Loader('dummy.core.PlainOldObject') ) def __get_data_path(self, filename): return abspath(join(dirname(__file__), '..', 'data', filename)) def __load_from_xml(self, path_from_data): test_file = self.__get_data_path(path_from_data) self.assembler.load(test_file) def __prepare_good_locator_from_xml(self): self.__load_from_xml('locator.xml')
class Container(object): def __init__(self, locator=None): self.logger = get_logger('{}.{}'.format(__name__, self.__class__.__name__)) self.locator = Locator() self.transformer = Transformer(self.locator) self.assembler = Assembler(self.transformer) self.default_services = [ ('finder', 'tori.common.Finder', [], {}), ('renderer', 'tori.template.service.RenderingService', [], {}), ('db', 'passerine.db.manager.ManagerFactory', [], {}) ] self.cache_map = None self._register_default_services() @contextmanager def passive_mode(self): self.assembler.activate_passive_loading() yield self.assembler.deactivate_passive_loading() def get(self, id): return self.locator.get(id) def load(self, *paths): with self.passive_mode(): [ self.assembler.load(path) for path in paths ] self.cache_map = None def all(self): if not self.cache_map: self.cache_map = { i: self.locator.get(i) for i in self.locator.entity_identifiers } return self.cache_map def _register_default_services(self): for entity_id, package_path, args, kwargs in self.default_services: try: entity = self._create_entity(entity_id, package_path, *args, **kwargs) self.locator.set(entity_id, entity) except ImportError as exception: if entity_id == "db": self.logger.info('Ignored {} as package "passerine" is neither available or loadable (containing errors).'.format(package_path)) continue raise ImportError('Failed to register {} ({})'.format(entity_id, package_path)) def _create_entity(self, id, package_path, *args, **kwargs): loader = Loader(package_path) return Entity(id, loader, *args, **kwargs)