Singleton annotated class instances are separate for interface and implementation binding
Unless I am doing something wrong, I will expect following binding to get single instance, but I am getting two different instances:
Code (python3):
#!/usr/bin/env python3
import abc
import injector
class SomeInterface(metaclass=abc.ABCMeta):
"""Interface with multiple implementations."""
@abc.abstractmethod
def some_method(self) -> None:
pass
@injector.singleton
class SomeImplementation(SomeInterface):
"""One implementation."""
def __init__(self):
print(f'Created instance: {self}')
def some_method(self) -> None:
pass
class SomeModule(injector.Module):
"""Injector module."""
def configure(self, binder: injector.Binder) -> None:
binder.bind(SomeImplementation)
binder.bind(SomeInterface, to=SomeImplementation)
def test_singleton() -> None:
inj = injector.Injector((SomeModule(),), auto_bind=False)
# Following two create different instances. Expected to be same since the implementation is marked singleton
print(inj.get(SomeInterface))
print(inj.get(SomeImplementation))
# These get the two instances created above.
print(inj.get(SomeInterface))
print(inj.get(SomeImplementation))
if __name__ == '__main__':
test_singleton()
Output:
Created instance: <cli.cli_lib.SomeImplementation object at 0x7f69b1bb3ef0>
<cli.cli_lib.SomeImplementation object at 0x7f69b1bb3ef0>
Created instance: <cli.cli_lib.SomeImplementation object at 0x7f69b1bb3e80>
<cli.cli_lib.SomeImplementation object at 0x7f69b1bb3e80>
<cli.cli_lib.SomeImplementation object at 0x7f69b1bb3ef0>
<cli.cli_lib.SomeImplementation object at 0x7f69b1bb3e80>
As far as I can think, following shouldn't be needed but I have tried, but no use:
- Add @injector.inject to implementations
__init__() - Add
scope=injector.singletonto bothbinder.bindstatements.
Note that auto_bind=False doesn't make any different; same behavior even if I don't set it and remove the line binder.bind(SomeImplementation)
You're right and adding @inject to __init__() is not necessary (since there's nothing being injected there) and adding scope=singleton to Binder.bind() won't make a difference (since singleton is already in force, becauses the implementation (SomeImplementation) declares that.
To the question at hand: while possibly counterintuitive in this case this is an expected behavior. Scope is defined per binding (type -> type mapping) and we have two bindings here:
-
SomeInterface->SomeImplementation -
SomeImplementation->SomeImplementation
If you really need this, in short term I suggest a workaround like:
class SomeModule(injector.Module):
def configure(self, binder: injector.Binder) -> None:
binder.bind(SomeImplementation)
@provider
def provide_someinterface(self, implementation: SomeImplementation) -> SomeInterface:
return implementation
I'm open to revising this behavior long-term but I have no time to explore the design space and the associated consequences right now.