diff --git a/README.rst b/README.rst index a463bbe..c8fc9bf 100644 --- a/README.rst +++ b/README.rst @@ -312,6 +312,46 @@ More information on this topic: * `"How to use Google Guice to create objects that require parameters?" on Stack Overflow `_ * `Google Guice assisted injection `_ +Child injectors +--------------- + +Concept similar to Guice's child injectors is supported by ``Injector``. This way you can +have one injector that inherits bindings from other injector (parent) but these bindings +can be overriden in it and it doesn't affect parent injector bindings:: + + >>> def configure_parent(binder): + ... binder.bind(str, to='asd') + ... binder.bind(int, to=42) + ... + >>> def configure_child(binder): + ... binder.bind(str, to='qwe') + ... + >>> parent = Injector(configure_parent) + >>> child = parent.create_child_injector(configure_child) + >>> parent.get(str), parent.get(int) + ('asd', 42) + >>> child.get(str), child.get(int) + ('qwe', 42) + +**Note**: Default scopes are bound only to root injector. Binding them manually to child +injectors will result in unexpected behaviour. +**Note 2**: Once a binding key is present in parent injector scope (like ``singleton`` +scope), provider saved there takes predecence when binding is overridden in child injector in +the same scope. This behaviour is subject to change:: + + >>> def configure_parent(binder): + ... binder.bind(str, to='asd', scope=singleton) + ... + >>> def configure_child(binder): + ... binder.bind(str, to='qwe', scope=singleton) + ... + >>> parent = Injector(configure_parent) + >>> child = parent.create_child_injector(configure_child) + >>> child.get(str) # this behaves as expected + 'qwe' + >>> parent.get(str) # wat + 'qwe' + Scopes ====== diff --git a/injector.py b/injector.py index d027a07..0cd980f 100644 --- a/injector.py +++ b/injector.py @@ -196,11 +196,13 @@ class Binder(object): """Create a new Binder. :param injector: Injector we are binding for. + :param auto_bind: Whether to automatically bind missing types. + :param parent: Parent binder. """ self.injector = injector self._auto_bind = auto_bind self._bindings = {} - self.parent = parent or _EmptyBinder() + self.parent = parent def bind(self, interface, to=None, annotation=None, scope=None): """Bind an interface to an implementation. @@ -284,7 +286,14 @@ class Binder(object): (interface, to)) def _get_binding(self, key): - return self._bindings.get(key) or self.parent._get_binding(key) + binding = self._bindings.get(key) + if not binding and self.parent: + binding = self.parent._get_binding(key) + + if not binding: + raise KeyError + + return binding def get_binding(self, cls, key): try: @@ -297,10 +306,6 @@ class Binder(object): return binding raise UnsatisfiedRequirement(cls, key) -class _EmptyBinder(object): - def _get_binding(self, key): - raise KeyError - class Scope(object): """A Scope looks up the Provider for a binding. @@ -439,6 +444,7 @@ class Injector(object): Signature is ``configure(binder)``. :param auto_bind: Whether to automatically bind missing types. + :param parent: Parent injector. """ # Stack of keys currently being injected. Used to detect circular # dependencies.