Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Singleton annotated class instances are separate for interface and implementation binding #181

Open
sudopk opened this issue Mar 21, 2021 · 2 comments

Comments

@sudopk
Copy link

sudopk commented Mar 21, 2021

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>
@sudopk
Copy link
Author

sudopk commented Mar 21, 2021

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.singleton to both binder.bind statements.

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)

@jstasiak
Copy link
Collaborator

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants