def test_discovery_when_there_are_no_ports(self): class Empty(Service): pass empty_component = Empty() discovered = AutoDiscoverConnections([empty_component]) self.assertEqual([], discovered.get_needs()) self.assertEqual([], discovered.get_provides()) self.assertEqual([], discovered.unsatisfied_needs()) self.assertEqual([], list(discovered.connections()))
def test_SelfReferencingMadness_raised_if_component_attempts_to_satisfy_itself( self): class Mad(Service): """This isn't strictly speaking wrong, but cannot sensible participate in auto-wiring.""" deps = Needs('attention') @provides def attention(self): return self.deps.attention() with self.assertRaisesRegexp( SelfReferencingMadness, '.* both needs and provides "attention".*'): AutoDiscoverConnections([Mad()])
def test_DuplicateProviders_raised_when_more_than_one_component_provides_the_same_port( self): class X(Service): @provides def x(self): return 'X' @provides def y(self): return 'Y' class Axe(Service): @provides def x(self): return 'X' with self.assertRaisesRegexp(DuplicateProviders, 'Duplicate providers for "x".*'): AutoDiscoverConnections([X(), Axe()])
def test_discovery_of_ports_from_a_collection_of_components_classes(self): # We need to be able to discover connects between uninstantiated components for visualisation and analysis A, B, C = self.get_services() discovered = AutoDiscoverConnections([A, B, C]) self.assertEqual(['b1', 'c1', 'x', 'y'], discovered.get_needs()) self.assertEqual(['a1', 'b1', 'b2', 'c1'], discovered.get_provides()) self.assertEqual(['x', 'y'], discovered.unsatisfied_needs()) self.assertItemsEqual([ DiscoveredConnection(port_name='b1', consumer=A, provider=B), DiscoveredConnection(port_name='c1', consumer=B, provider=C) ], discovered.connections()) self.assertEqual(A, discovered.get_provider('a1')) self.assertEqual(B, discovered.get_provider('b1')) self.assertEqual(B, discovered.get_provider('b2')) self.assertEqual(C, discovered.get_provider('c1')) with self.assertRaisesRegexp(UnknownPort, '"x1" is not a valid port'): self.assertEqual(C, discovered.get_provider('x1'))
def __init__(self): super(Domain, self).__init__() self._service_map = service_map = self._instantiate_and_map_services() # replace 'meta' with a variant for the instance (don't share self.__class__.meta) self.meta = self.__class__.meta.get_instance_metadata( service_map=service_map) # replace 'deps' with a ShadowPortArray which serves as proxy to the deps of internal services components = service_map.values() component_deps = [ c.deps for c in components if isinstance(getattr(c, 'deps', None), (PortArray, ShadowPortArray)) ] discovered = AutoDiscoverConnections(components=components) self.deps = ShadowPortArray(arrays=component_deps, ignore_ports=discovered.satisfied_needs()) # materialize connections between services wire_up_discovered_connections(discovered=discovered)
def test_discovery_of_ports_from_a_collection_of_components_instances( self): A, B, C = self.get_services() a = A() b = B() c = C() discovered = AutoDiscoverConnections([a, b, c]) self.assertEqual(['b1', 'c1', 'x', 'y'], discovered.get_needs()) self.assertEqual(['a1', 'b1', 'b2', 'c1'], discovered.get_provides()) self.assertEqual(['x', 'y'], discovered.unsatisfied_needs()) self.assertItemsEqual([ DiscoveredConnection(port_name='b1', consumer=a, provider=b), DiscoveredConnection(port_name='c1', consumer=b, provider=c) ], discovered.connections()) self.assertEqual(a, discovered.get_provider('a1')) self.assertEqual(b, discovered.get_provider('b1')) self.assertEqual(b, discovered.get_provider('b2')) self.assertEqual(c, discovered.get_provider('c1')) with self.assertRaisesRegexp(UnknownPort, '"x1" is not a valid port'): self.assertEqual(c, discovered.get_provider('x1'))
def __new__(mcs, name, bases, state): if bases == ( INeed, IProvide): # This is the Domain class itself, not its subclass return type.__new__(mcs, name, bases, state) mcs.validate_overridden_attributes(attrs=state, class_name=name) if '__services__' not in state or not isinstance( state['__services__'], (list, tuple)): raise DomainDefinitionError( '{}.__services__ must be defined with a list of component classes' .format(name)) else: service_classes = state['__services__'] for service_class in service_classes: mcs._assert_is_compatible_class(name, service_class) discovered = AutoDiscoverConnections(service_classes) provides = state.get('__provides__', None) if provides is None or not isinstance(provides, (list, tuple, AutoProvide)): raise DomainDefinitionError( '{}.__provides__ must be defined with a list of port names'. format(name)) else: if isinstance(provides, AutoProvide): # auto-discover provides ports auto_provider = provides provides_ports = auto_provider.filter( discovered.get_provides()) else: for port_name in provides: if port_name not in discovered.get_provides(): msg = '"{}" listed in {}.__provides__ is not provided by any of the services'.format( port_name, name, ) raise DomainDefinitionError(msg) provides_ports = provides # all unsatisfied deps are exposed as dependencies of the domain state['deps'] = deps = PortArray() for port_name in discovered.unsatisfied_needs(): deps.add_port(port_name) # make a shadow copy of template_funcs. Used mainly for tracking intended interfaces for ports so we can # use for validation during testing. At some point we might use this for wiring-time checks too to ensure # compatibility between ports. deps._needs_template_funcs = template_funcs = {} for port in deps.get_ports(): template_func = mcs._assert_providers_compatible_and_extract_template_func( providers=discovered._needs[port], port_name=port, ) template_funcs[port] = template_func # declared 'provides' ports are registered and entry points created state['meta'] = meta = DomainProviderMetadata() for port in provides_ports: provider = discovered.get_provider(port_name=port) if not issubclass(provider, (Service, Domain)): msg = 'Port of non-service class ({}.{}) cannot be published on the domain'.format( provider.__name__, port) raise DomainDefinitionError(msg) inherited_flags = provider.get_provider_flags(port) inherited_flags.pop('with_name', None) # don't inherit name-change flags meta.register_provider(port_name=port, service=provider, flags=inherited_flags) state[port] = generate_domain_method(port_name=port, provider=provider) return type.__new__(mcs, name, bases, state)