jeni-python icon indicating copy to clipboard operation
jeni-python copied to clipboard

Injector.alias()

Open groner opened this issue 10 years ago • 2 comments

Instead of writing:

@Injector.factory('send_mail')
def send_mail_factory(send_mail: annotate.partial(send_mail)):
    return send_mail

What if you could just write:

Injector.alias('send_mail', annotate.partial(send_mail))

Another possible use:

# In a web application
Injector.alias('user', 'session:user')
# In tests
Injector.alias('user', 'fixture:user')

groner avatar Sep 24 '15 20:09 groner

Here is a quick implementation of Injector.alias().

import functools

from jeni import Injector, Provider, FactoryProvider, annotate


class AliasProvider(Provider):
    @classmethod
    def bind(cls, note):
        basenote, name = note
        if name is not None:
            @annotate
            def alias(v: note):
                return FactoryProvider(lambda: v)
            return alias
        return lambda: cls(basenote)

    def __init__(self, basenote):
        self.basenote = basenote

    @annotate
    def get(self, *, name=None, injector: 'injector'):
        return injector.get((self.basenote, name))


class AliasingInjector(Injector):
    alias_provider = AliasProvider

    def __init__(self, provide_self=True):
        if provide_self is not True:
            raise ValueError('provide_self can only be True')
        # Work around #14.
        #super().__init__(True)
        super().__init__(False)
        self.values['injector'] = self

    @classmethod
    def alias(cls, note, other_note=None, *, partial=None):
        if other_note is None:
            return functools.partial(cls.alias, note, partial=partial)

        if partial:
            other_note = annotate.partial(other_note)
        else:
            other_note = cls.parse_note(other_note)
        provider = cls.alias_provider.bind(other_note)
        cls.register(note, provider)


if __name__ == "__main__":
    import unittest

    class TestAliasingInjector(unittest.TestCase):
        def setUp(self):
            class TestInjector(AliasingInjector): pass
            self.TestInjector = TestInjector

            @TestInjector.provider('named', name=True)
            def named_provider():
                name = None
                while True:
                    if name is None:
                        name = yield 'named'
                    else:
                        name = yield 'named[{}]'.format(name)

        def test_alias_basenote(self):
            self.TestInjector.alias('borrower', 'named')

            with self.TestInjector() as inj:
                self.assertEqual(inj.get('borrower'), 'named')
                self.assertEqual(inj.get('borrower:bar'), 'named[bar]')

        def test_alias_namednote(self):
            self.TestInjector.alias('borrower', 'named:foo')

            with self.TestInjector() as inj:
                self.assertEqual(inj.get('borrower'), 'named[foo]')
                with self.assertRaises(TypeError):
                    inj.get('borrower:bar')

        def test_alias_partial(self):
            class QuuxificationError(Exception): pass

            @self.TestInjector.alias('quuxify', partial=True)
            @annotate
            def quuxify(x: 'named:x'):
                raise QuuxificationError()

            with self.TestInjector() as inj:
                with self.assertRaises(QuuxificationError):
                    inj.get('quuxify')()

    unittest.main()

groner avatar Jan 11 '16 21:01 groner

I just realized that this makes it easier to register annotated functions than plain functions. If Injector.value(note) worked as a decorator, that would fix it.

groner avatar Jan 20 '16 23:01 groner