def __new__(mcs, name, bases, state): if bases == (INeed, IProvide ): # This is the Service class itself, not its subclass return type.__new__(mcs, name, bases, state) mcs.validate_overridden_attributes(attrs=state, class_name=name) # walk attributes and register the ones that have been tagged by @provides meta = ServiceProviderMetadata() for attr_name, member in state.iteritems(): if hasattr(member, '__port_attributes__') and callable(member): # tagged port_name = member.__port_attributes__.get( 'with_name', attr_name) PortArray.assert_valid_port_name(port_name) meta.register_provider(port_name=port_name, method_name=attr_name, flags=member.__port_attributes__) mcs.validate_deps_declaration_and_usage(class_state=state, class_name=name) state['meta'] = meta return type.__new__(mcs, name, bases, state)
def __new__(mcs, name, bases, state): if bases == ( Needs, ): # This is the NeedsInterface class itself, not its subclass return type.__new__(mcs, name, bases, state) needs = {} for attr_name, member in state.items(): if attr_name == '__init__' and bases != (Needs, ): msg = '{}.__init__ - cannot override constructor of Needs Interface'.format( name) raise NeedsInterfaceDefinitionError(msg) if attr_name.startswith('__') and attr_name.endswith('__'): continue if not isinstance(member, types.FunctionType): msg = '{}.{} - only functions are allowed as attributes of a Needs Interface'.format( name, attr_name) raise NeedsInterfaceDefinitionError(msg) PortArray.assert_valid_port_name(port_name=attr_name) # actual functions do not actually make it onto the class as they will be replaced by ports. # However, we do keep a reference to the original functions for debugging and testing purposes. needs[attr_name] = state.pop(attr_name) state['_needs'] = needs.keys() state['_needs_template_funcs'] = needs # SHC: not sure this is a good ideal. Hide this away for now # state['_template_class'] = type(name + 'Template', (), needs) # generate subclass-able interface class return type.__new__(mcs, name, bases, state)
def setUp(self): self.array_a = PortArray() self.array_a.add_port('a_only') self.array_a.add_port('in_both') self.array_a.add_port('also_a_only') self.array_b = PortArray() self.array_b.add_port('b_only') self.array_b.add_port('in_both')
def __init__(self, provider, ports): if isinstance(ports, basestring): ports = [ports] self._assert_attr_exists_on_provider(provider, ports) for port_name in ports: PortArray.assert_valid_port_name(port_name) self.provider = provider self.ports = frozenset(ports) _FlagQueryMixin.__init__(self, valid_ports=self.ports)
def provides_with(name=None, **kwargs): """ Decorator which tags class methods so they can be detected as a provider of a Service. Use this instead of @provides when exposing a port with a custom name or to tag on additional flags. """ PortArray.assert_valid_port_name(port_name=name) port_attrs = kwargs if name: PortArray.assert_valid_port_name(port_name=name) port_attrs['with_name'] = name def decorator(method): method.__port_attributes__ = port_attrs return method return decorator
def test_replicating_port_array_results_in_array_with_disconnected_ports(self): self.ports.add_port('hello') self.ports.add_port('world') self.ports.connect_port('hello', lambda: None) new_ports = PortArray.replicate(self.ports) self.assertIsNot(new_ports, self.ports) self.assertItemsEqual(['hello', 'world'], new_ports.get_ports()) # all ports should be disconnected with self.assertRaisesRegexp(DisconnectedPort, 'Port "hello" has not been connected'): new_ports.hello() with self.assertRaisesRegexp(DisconnectedPort, 'Port "world" has not been connected'): new_ports.world()
def get_instance_metadata(self, service_map): clone = self.__class__() clone.ports = PortArray() for port in self.get_provides(): provider_class = self.get_provider(port) provider_instance = service_map[provider_class] provider_flags = provider_instance.get_provider_flags( port_name=port) provider_func = provider_instance.get_provider_func(port_name=port) clone.register_provider(port_name=port, service=provider_instance, flags=provider_flags) # create and connect ports clone.ports.add_port(port_name=port) clone.ports.connect_port(port_name=port, func=provider_func) return clone
class ShadowPortArrayTest(TestCase): def setUp(self): self.array_a = PortArray() self.array_a.add_port('a_only') self.array_a.add_port('in_both') self.array_a.add_port('also_a_only') self.array_b = PortArray() self.array_b.add_port('b_only') self.array_b.add_port('in_both') def test_empty_shadow_port_array_can_be_created(self): shadow = ShadowPortArray([]) self.assertEqual([], shadow.get_ports()) def test_shadow_with_only_one_child(self): shadow = ShadowPortArray([self.array_b]) self.assertItemsEqual(['b_only', 'in_both'], shadow.get_ports()) def test_shadow_with_multiple_children(self): shadow = ShadowPortArray([self.array_a, self.array_b]) self.assertItemsEqual(['a_only', 'also_a_only', 'b_only', 'in_both'], shadow.get_ports()) def test_connecting_ports_via_shadow(self): shadow = ShadowPortArray([self.array_a, self.array_b]) shadow.connect_port('in_both', lambda: 'both') shadow.connect_port('a_only', lambda: 'a') self.assertEqual('a', self.array_a.a_only()) self.assertEqual('both', self.array_a.in_both()) self.assertEqual('both', self.array_b.in_both()) with self.assertRaisesRegexp(DisconnectedPort, 'Port "b_only" has not been connected'): self.array_b.b_only() def test_disconnecting_ports_via_shadow(self): shadow = ShadowPortArray([self.array_a, self.array_b]) shadow.connect_port('in_both', lambda: 'both') shadow.connect_port('a_only', lambda: 'a') shadow.connect_port('b_only', lambda: 'b') shadow.disconnect_port('in_both') shadow.disconnect_port('b_only') with self.assertRaisesRegexp(DisconnectedPort, 'Port "in_both" has not been connected'): self.array_a.in_both() with self.assertRaisesRegexp(DisconnectedPort, 'Port "in_both" has not been connected'): self.array_b.in_both() with self.assertRaisesRegexp(DisconnectedPort, 'Port "b_only" has not been connected'): self.array_b.b_only() self.assertEqual('a', self.array_a.a_only()) # this should remain connected def test_raises_WiringError_when_connecting_port_to_non_callable(self): shadow = ShadowPortArray([self.array_a, self.array_b]) with self.assertRaisesRegexp(WiringError, 'Cannot connect port to a non-callable object'): shadow.connect_port('in_both', None) def test_raise_UnknownPort_when_connecting_an_unknown_port(self): shadow = ShadowPortArray([self.array_a, self.array_b]) with self.assertRaisesRegexp(UnknownPort, '"hello" is not a valid port'): shadow.connect_port('hello', lambda: 'hello') def test_raise_UnknownPort_when_disconnecting_an_unknown_port(self): shadow = ShadowPortArray([self.array_a, self.array_b]) with self.assertRaisesRegexp(UnknownPort, '"hello" is not a valid port'): shadow.disconnect_port('hello') def test_shadow_does_not_inherit_ignored_ports(self): shadow = ShadowPortArray([self.array_a, self.array_b], ignore_ports=['in_both', 'also_a_only', 'fluff']) self.assertItemsEqual(['a_only', 'b_only'], shadow.get_ports())
def setUp(self): self.ports = PortArray()
class PortArrayTests(TestCase): def setUp(self): self.ports = PortArray() def test_empty_array_can_be_instantiated(self): self.assertEqual([], self.ports.get_ports()) def test_adding_port(self): self.ports.add_port('hello') self.assertItemsEqual(['hello'], self.ports.get_ports()) self.ports.add_port('world') self.assertItemsEqual(['hello', 'world'], self.ports.get_ports()) def test_newly_added_port_raises_DisconnectedPort_when_called(self): self.ports.add_port('hello') with self.assertRaisesRegexp(DisconnectedPort, 'Port "hello" has not been connected'): self.ports.hello() def test_connecting_port_to_plain_function(self): def echo(s): return s.upper() self.ports.add_port('hello') self.ports.connect_port('hello', echo) self.assertEqual('YOLO', self.ports.hello('yolo')) def test_connecting_port_to_lambda_function(self): self.ports.add_port('hello') self.ports.connect_port('hello', lambda s: s.upper()) self.assertEqual('YOLO', self.ports.hello('yolo')) def test_connecting_port_to_bound_method(self): class EchoChamber(object): def echo(self, s): return s.upper() self.ports.add_port('hello') self.ports.connect_port('hello', EchoChamber().echo) self.assertEqual('YOLO', self.ports.hello('yolo')) def test_connecting_port_to_static_method(self): class EchoChamber(object): @staticmethod def echo(s): return s.upper() self.ports.add_port('hello') self.ports.connect_port('hello', EchoChamber.echo) self.assertEqual('YOLO', self.ports.hello('yolo')) def test_connecting_port_to_class_method(self): class EchoChamber(object): @classmethod def echo(cls, s): return s.upper() self.ports.add_port('hello') self.ports.connect_port('hello', EchoChamber.echo) self.assertEqual('YOLO', self.ports.hello('yolo')) def test_connecting_port_to_callable_object(self): class EchoChamber(object): def __call__(self, s): return s.upper() self.ports.add_port('hello') self.ports.connect_port('hello', EchoChamber()) self.assertEqual('YOLO', self.ports.hello('yolo')) def test_raises_WiringError_when_connecting_port_to_non_callable(self): self.ports.add_port('hello') with self.assertRaisesRegexp(WiringError, 'Cannot connect port to a non-callable object'): self.ports.connect_port('hello', None) def test_raises_UnknownPort_when_connecting_to_an_unknown_port(self): with self.assertRaisesRegexp(UnknownPort, '"hello" is not a valid port'): self.ports.connect_port('hello', lambda: None) def test_raises_InvalidPortName_when_port_name_does_not_match_constraints(self): self.assert_invalid_port_name_pattern('_starts_with_underscore') self.assert_invalid_port_name_pattern('has spaces') self.assert_invalid_port_name_pattern('9_does_not_start_with_lowercase_char') self.assert_invalid_port_name_pattern('A_does_not_start_with_lowercase_char') self.assert_invalid_port_name_pattern('not_!_alphanum') self.assert_invalid_port_name_pattern('no_-_dashes_please') # these should be fine self.ports.add_port('has_underscores') self.ports.add_port('has_UppErCaSe') self.ports.add_port('has_d1g1ts') def assert_invalid_port_name_pattern(self, port_name): expected_msg = '"{}" does not have required format for port names'.format(port_name) with self.assertRaisesRegexp(InvalidPortName, expected_msg): self.ports.add_port(port_name) def test_raises_InvalidPortName_when_reserved_words_used(self): self.assert_raises_when_port_name_is_reserved_word('add_port') self.assert_raises_when_port_name_is_reserved_word('get_ports') self.assert_raises_when_port_name_is_reserved_word('connect_port') self.assert_raises_when_port_name_is_reserved_word('get_needs') self.assert_raises_when_port_name_is_reserved_word('get_provides') # also pull in all attrs in case we miss something port_array_attrs = [a for a in dir(self.ports) if not a.startswith('_')] for name in port_array_attrs: self.assert_raises_when_port_name_is_reserved_word(name) def assert_raises_when_port_name_is_reserved_word(self, port_name): expected_msg = '"{}" is a reserved word and cannot be used as port name'.format(port_name) with self.assertRaisesRegexp(InvalidPortName, expected_msg): self.ports.add_port(port_name) def test_disconnect_port(self): self.ports.add_port('hello') self.ports.connect_port('hello', lambda: None) self.ports.disconnect_port('hello') with self.assertRaisesRegexp(DisconnectedPort, 'Port "hello" has not been connected'): self.ports.hello() def test_if_is_fine_to_disconnect_port_that_is_not_connected(self): self.ports.add_port('hello') self.ports.disconnect_port('hello') with self.assertRaisesRegexp(DisconnectedPort, 'Port "hello" has not been connected'): self.ports.hello() def test_raise_UnknownPort_when_disconnecting_an_unknown_port(self): with self.assertRaisesRegexp(UnknownPort, '"hello" is not a valid port'): self.ports.disconnect_port('hello') def test_replicating_port_array_results_in_array_with_disconnected_ports(self): self.ports.add_port('hello') self.ports.add_port('world') self.ports.connect_port('hello', lambda: None) new_ports = PortArray.replicate(self.ports) self.assertIsNot(new_ports, self.ports) self.assertItemsEqual(['hello', 'world'], new_ports.get_ports()) # all ports should be disconnected with self.assertRaisesRegexp(DisconnectedPort, 'Port "hello" has not been connected'): new_ports.hello() with self.assertRaisesRegexp(DisconnectedPort, 'Port "world" has not been connected'): new_ports.world() def test_identification_of_disconnected_port(self): self.ports.add_port('hello') self.assertTrue(self.ports.is_disconnected_port('hello')) self.ports.connect_port('hello', lambda: None) self.assertFalse(self.ports.is_disconnected_port('hello')) self.ports.disconnect_port('hello') self.assertTrue(self.ports.is_disconnected_port('hello')) def test_identification_of_disconnected_port_raises_for_unknown_port(self): with self.assertRaisesRegexp(UnknownPort, '"boo" is not a valid port'): self.ports.is_disconnected_port('boo')
def __init__(self): super(Service, self).__init__() self.deps = PortArray.replicate( self.__class__.deps ) # each service instance should have its own copy
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)
def __init__(self, provider, port_name): PortArray.assert_valid_port_name(port_name) self.provider = provider self.port_name = port_name _FlagQueryMixin.__init__(self, valid_ports=[port_name])