Skip to content
This repository has been archived by the owner on Jan 10, 2023. It is now read-only.

Why a class with @pinject.inject is always singleton? Is it a bug? #40

Open
alexander-myltsev opened this issue Jun 12, 2019 · 2 comments

Comments

@alexander-myltsev
Copy link

OS: macOS 10.14.5
Python: 3.7
Dependencies:

[tool.poetry.dependencies]
python = "^3.6"
numpy = "^1.16"
PyContracts = "^1.8"
quandl = "^3.4"
pandas = "^0.24"
pinject = "^0.14.1"
typing_extensions = "^3.7"

[tool.poetry.dev-dependencies]
freezegun = "^0.3.11"
pytest = "^4.5"
PyHamcrest = "^1.9"
flake8 = "^3.7"
codecov = "^2.0"
pytest-cov = "^2.7"
jupyterlab = "^0.35.6"
nbval = "^0.9.1"
mypy = "^0.701.0"
pytype = "^2019.5"

Thank you for the lib. I enjoy your design decisions which make it the closest thing to dependency injection in Python I've seen so far. I'd like to use it. But have a problem in the code as follows:

class MyRegistry:
    def reg(self):
        return 42

class FooClass:
    @pinject.inject(['my_registry']) # https://github.com/google/pinject/blob/1e785b550cad4d4f9fd7f60a7d047dab9f7410e0/pinject/bindings.py#L168 make the FooClass singleton 
    def __init__(self, my_registry, param):
        self.my_registry = my_registry
        self.param = param

class MainClass:
    def __init__(self, provide_foo_class):
        a = provide_foo_class(param=1)
        b = provide_foo_class(param=2) # this line gets FooClass instance from the cache since it's singleton 
        print(a.my_registry.reg()) # works fine
        print(b.my_registry.reg()) # obivously, works fine too
        print(a.param)
        print(b.param) # this is actually `a` instance, so it prints 1 instead of 2

if __name__ == '__main__':
    obj_graph = pinject.new_object_graph()
    obj_graph.provide(MainClass)

I tried to override FooClass scope in custom spec, but it throws the exception of ambiguity since FooClass is registered as singleton. I can work around it with the FooClassFactory that creates instances of FooClass, but it seems unnecessary when there is provide_ facility.

How to make it work properly?

@miguelgflores-fever
Copy link

By default Pinject uses singleton, you can specify PROTOTYPE as scope.

Pinject has two built-in scopes. Singleton scope (SINGLETON) is the default and always caches. Prototype scope (PROTOTYPE) is the other built-in option and does no caching whatsoever.

Every binding is associated with a scope. You can specify a scope for a binding by decorating a provider method with @in_scope(), or by passing an in_scope arg to bind() in a binding spec's configure() method.

https://github.com/google/pinject/blob/master/README.rst#scopes

@mxab
Copy link

mxab commented Sep 24, 2020

I'm facing the same issue.
I tried to make it as prototype in a spec but then I get an ConflictingExplicitBindingsError when I do it like this:

class MyRegistry:
    def reg(self):
        return 42


class FooClass:
    @pinject.inject(['my_registry'])
    def __init__(self, my_registry, param):
        self.my_registry = my_registry
        self.param = param


class MainClass:
    def __init__(self, provide_foo_class):
        a = provide_foo_class(param=1)
        b = provide_foo_class(param=2)

        assert a.param != b.param


class MyBindingSpec(pinject.BindingSpec):
    def configure(self, bind):
        bind('foo_class', to_class=FooClass, in_scope=pinject.PROTOTYPE)


if __name__ == '__main__':
    obj_graph = pinject.new_object_graph(binding_specs=[MyBindingSpec()])
    obj_graph.provide(MainClass)

Traceback (most recent call last):
  File "/home/mafr/.pyenv/versions/3.6.10/lib/python3.6/runpy.py", line 193, in _run_module_as_main
    "__main__", mod_spec)
  File "/home/mafr/.pyenv/versions/3.6.10/lib/python3.6/runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "/home/mafr/git/pinject-demo/pinject_demo/__main__.py", line 113, in <module>
    obj_graph = pinject.new_object_graph(binding_specs=[MyBindingSpec()])
  File "/home/mafr/.cache/pypoetry/virtualenvs/pinject-demo-IwpV6JDg-py3.6/lib/python3.6/site-packages/pinject/object_graph.py", line 154, in new_object_graph
    raise e
  File "/home/mafr/.cache/pypoetry/virtualenvs/pinject-demo-IwpV6JDg-py3.6/lib/python3.6/site-packages/pinject/object_graph.py", line 148, in new_object_graph
    [implicit_class_bindings, explicit_bindings]))
  File "/home/mafr/.cache/pypoetry/virtualenvs/pinject-demo-IwpV6JDg-py3.6/lib/python3.6/site-packages/pinject/bindings.py", line 94, in get_overall_binding_key_to_binding_maps
    bindings, handle_binding_collision_fn))
  File "/home/mafr/.cache/pypoetry/virtualenvs/pinject-demo-IwpV6JDg-py3.6/lib/python3.6/site-packages/pinject/bindings.py", line 70, in _get_binding_key_to_binding_maps
    collided_binding_key_to_bindings)
  File "/home/mafr/.cache/pypoetry/virtualenvs/pinject-demo-IwpV6JDg-py3.6/lib/python3.6/site-packages/pinject/bindings.py", line 49, in _handle_explicit_binding_collision
    [colliding_binding, other_binding])
pinject.errors.ConflictingExplicitBindingsError: multiple explicit bindings for same binding name:
  the binding at <decorator-gen-2>:235, from the binding name "foo_class" (unannotated) to the provider method pinject.bindings.provide_it at <decorator-gen-2>:235, in "prototype scope" scope
  the binding at /home/mafr/git/pinject-demo/pinject_demo/__main__.py:92, from the binding name "foo_class" (unannotated) to the class __main__.FooClass at /home/mafr/git/pinject-demo/pinject_demo/__main__.py:92, in "singleton scope" scope

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

No branches or pull requests

3 participants