Esempio n. 1
0
    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)
Esempio n. 2
0
    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)
Esempio n. 3
0
    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')
Esempio n. 4
0
    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)
Esempio n. 5
0
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
Esempio n. 6
0
    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()
Esempio n. 7
0
    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
Esempio n. 8
0
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())
Esempio n. 9
0
 def setUp(self):
     self.ports = PortArray()
Esempio n. 10
0
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')
Esempio n. 11
0
 def __init__(self):
     super(Service, self).__init__()
     self.deps = PortArray.replicate(
         self.__class__.deps
     )  # each service instance should have its own copy
Esempio n. 12
0
    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)
Esempio n. 13
0
 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])