google / pinject Goto Github PK
View Code? Open in Web Editor NEWA pythonic dependency injection library.
Home Page: https://pypi.org/project/pinject/
License: Apache License 2.0
A pythonic dependency injection library.
Home Page: https://pypi.org/project/pinject/
License: Apache License 2.0
Hi there
I'm having a difficulty using pinject when I'm trying to resolve a dependency which has inner nested dependencies as well.
import pinject
class User:
def __init__(self, user_id=None, username=None):
self.user_id = user_id
self.username = username
def get_user_id(self):
return self.user_id
def get_username(self):
return self.username
def __str__(self):
return str(self.__dict__)
def __eq__(self, other):
return self.__dict__ == other.__dict__
class QueueUserRepository(BaseUserRepository):
def __init__(self, data_provider):
self.data_provider = data_provider
def get_user(self):
user_data = self.data_provider.get_data('user')
return User(user_data['user_id'], user_data['username'])
def get_users_batch(self):
return UserBatch(self.data_provider.get_data('users'))
class QueueDataProvider:
class DataDoesNotExistsError(Exception):
pass
def __init__(self, provider_data):
print(provider_data)
self.data = self.normalize_data(provider_data)
@staticmethod
def normalize_data(data):
return data
def get_data(self, key):
try:
return self.data[key]
except KeyError:
raise self.DataDoesNotExistsError('{} key does not exists'.format(key))
class MyBindingSpec(pinject.BindingSpec):
def configure(self, bind):
bind('provider_data', to_instance={'user': {'username': 'super', 'user_id': 'y'}})
bind('data_provider', to_instance=QueueDataProvider)
class TestQueueUserRepository(unittest.TestCase):
def setUp(self):
self.container = pinject.new_object_graph(binding_specs=[MyBindingSpec()])
def test_if_can_get_user(self):
z = self.container.provide(QueueUserRepository)
u = User('y', 'super')
self.assertEqual(r.get_user(), u)
It seems pinject does not care about 'dependency of dependencies' as QueueDataProvider constructor does not seem to be called at all
The above code does not work however when I manually build objects, it works perfectly fine.
As I searched a lot in the documentation, I could not find any proper explanation with this.
Could you please help me with this.
EDIT:
I really tried so hard but I could not get code highlight support in this ...
UPADTE:
Unit tests are added.
To be more clear: QueueDataProvider is not receiving 'provider_data' on creation. I can say the QueueDataProvider's constructor is not called at all as far as I checked.
Does Pinject use class constructor arguments' type hints information (e.g. using inspect module) to find appropriate class object to be injected?
For example:
import pinject
import abc
class AbstrctClass(object):
... metaclass = abc.ABCMeta
...
class SomeClass(AbstrctClass):
... pass
...
obj_graph = pinject.new_object_graph(modules=None, classes=[AbstrctClass])
Traceback (most recent call last):
File "", line 1, in
File "/google/src/cloud/daming/fix/google3/third_party/py/pinject/object_graph.py", line 159, in new_object_graph
raise e
pinject.errors.WrongArgElementTypeError: wrong type for element 0 of arg classes: expected type but got ABCMeta
I can't figure out a way to assign values to class with default init values.
Example as below.
I expect the output to be 'bbb' instead of 'B'.
Am I missing something? please help.
import pinject
class A(object):
def __init__(self, a, b='B'):
self.a=a
self.b=b
class B(object):
def __init__(self, obja):
self.obja = obja
def printit(self):
print(self.obja.a)
print(self.obja.b)
class MyBindingSpec(pinject.BindingSpec):
def configure(self, bind):
bind('obja',to_class=A)
bind('a',to_instance='aaa')
bind('b',to_instance='bbb')
obj_graph = pinject.new_object_graph(binding_specs=[MyBindingSpec()])
objb = obj_graph.provide(B)
objb.printit()
Where is errors
defined?
% flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
./pinject/pinject/__init__.py:32:23: F821 undefined name 'errors'
for thing_name in dir(errors):
^
./pinject/pinject/__init__.py:33:21: F821 undefined name 'errors'
thing = getattr(errors, thing_name)
^
2 F821 undefined name 'errors'
2
Hello,
every time I cause a TypeError in the init method of an injected class, pinject raises a pretty cryptic Exception that makes debugging way harder:
import pinject
class Foo(object):
def init(self, bar):
self.bar = bar
class Bar(object):
def init(self, foo_bar):
self.foo_bar = foo_bar
class FooBar(object):
def init(self):
raise TypeError("test")
o = pinject.new_object_graph()
o.provide(Foo)
Traceback (most recent call last):
File "/tmp/a.py", line 16, in
o.provide(Foo)
File "/spare/local/secmaster-overwatch-infra/lib/python2.7/site-packages/pinject/object_graph.py", line 244, in provide
raise e
pinject.errors.OnlyInstantiableViaProviderFunctionError: when injecting Bar.init at /tmp/a.py:8, the arg named "foo_bar" unannotated cannot be injected, because its provider, the class main.FooBar at /tmp/a.py:11, needs at least one directly passed arg
Thanks
Mirko
According to PEP 8 -- Style Guide for Python Code, when using acronyms in CapWords, capitalize all the letters of the acronym. Thus HTTPServerError is better than HttpServerError.
Nevertheless, the default implementation of get_arg_names_from_class_name
considers only one uppercase letter when splitting the class name, as can be seen below:
def default_get_arg_names_from_class_name(class_name):
# content removed for brevity
while True:
m = re.match(r'([A-Z][a-z]*|[0-9][a-z0-9]*)(.*)', rest)
# content removed for brevity
You can see next examples that show the problem:
main.py
if __name__ == '__main__':
print(pinject.bindings.default_get_arg_names_from_class_name(sys.argv[1]))
Executing the sample program for 'HttpServer' works great:
$ python main.py HttpServer
['http_server']
But, executing the sample program for 'HTTPServer' results in an invalid value:
$ python main.py HTTPServer
['h_t_t_p_server']
The same occurs when the class name has digits before an capitalized digit:
$ python main.py Ec2Server
['ec_2_server']
We're in python 3.8.1 today.
Could you please add automated builds for 3.7, 3.8 python versions as well?
I bet they will pass ;)
Thanks.
A lot of times I just want to call a function which has arguments that need to be injected. In order to call the function like thisI basically have to create a wrapper class for the function which specifies the arguments required, and then call some method on the wrapper to pass in the injected arguments. This works but is a lot of boilerplate. e.g.
def foobar(foo: Foo) -> int:
...
class FoobarWrapper:
def __init__(foo: Foo) -> None:
self.foo = foo
def call(self): -> int:
return foobar(self.foo)
obj_graph = new_object_graph()
print("foobar returns:", obj_graph.provide(FoobarWrapper).call())
I want to be able to call functions with arbitrary parameters and have pinject construct all of the necessary inputs for me.
It seems like the library is set up nicely to support this, however, it requires me to access private members of the ObjectGraph
instance.
After browsing through the implementation, I've been able to achieve my goal with the following 3 line hack. I provide a helper method around it to keep things simple and type-safe.
from typing import TypeVar, Callable
from pinject.object_graph import ObjectGraph
T = TypeVar("T")
def inject_func(obj_graph: ObjectGraph, func: Callable[..., T]) -> T:
context = obj_graph._injection_context_factory.new(func)
args, kwargs = obj_graph._obj_provider.get_injection_pargs_kwargs(func, context, [], {})
return func(*args, **kwargs)
And here's how you'd call it:
class Bar:
def __init__(self) -> None:
self.a = 1
class Foo:
def __init__(self, bar: Bar) -> None:
self.bar = bar
def foobar(foo: Foo) -> int:
return foo.bar.a
obj_graph = new_object_graph()
print("foobar returns:", inject_func(obj_graph, foobar))
foobar returns: 1
What I'd like to see is something like this at the same level as ObjectGraph.provide
. Maybe ObjectGraph.invoke
obj_graph = new_object_graph()
print("foobar returns:", obj_graph.invoke(foobar))
The only way to bind the same name to two different objects depending on where they'll be used seems to be to annotate the "non-binding-spec" code to make the two parameters distinguishable.
But annotations of the non-binding-spec code go against pinject's unique selling point, which is that you can leave your regular code completely untouched. In fact, the very first point of the "Why pinject?" README section says:
[...] Forget having to decorate your code with @inject_this and @annotate_that just to get started. With Pinject, you call new_object_graph(), one line, and you're good to go.
Annotations to avoid name collisions brings back that exact kind of @annotate_that
mess people who like pinject want to avoid.
So this is a feature request to come up with and implement an alternative to annotations to avoid name collisions and have certain bindings only apply "locally", e.g. only to one specific class.
The feature could take the form of e.g. an extra parameter to bind
that allows you to choose a specific requesting class for which it is applied:
class SomeBindingSpec:
def configure(bind):
bind("common_name", to_class=SomeBoundClass, local_to=SomeRequestingClass)
class SomeOtherBindingSpec:
def configure(bind):
bind("common_name", to_class=SomeOtherBoundClass, local_to=SomeOtherRequestingClass)
Another, perhaps more flexible way to do this would be to instead implement locality on the level of binding specs, e.g.:
@pinject.local_binding_spec(requesters=[SomeRequestingClass])
class SomeBindingSpec:
def configure(bind):
bind("common_name", to_class=SomeBoundClass)
@pinject.local_binding_spec(requesters=[SomeOtherRequestingClass])
class SomeOtherBindingSpec:
def configure(bind):
bind("common_name", to_class=SomeOtherBoundClass)
Another idea that would allow one to more easily work around the issue would be if binding specs and object graphs were more "composable" than they are now, so you could e.g. create a common object graph without collisions and two separate object graphs each consisting of the common one plus the bindings that would otherwise collide, then use those to provide the conflicting requesting classes separately. pinject would however have to ensure that both object graphs return the exact same instances for the common parts, just like it does normally within the same object graph.
Thoughts?
I see there are prerelease versions out there since March of 2020. The last stable version release was May 2019. Are there any intentions to release the PRs from the past 18 months as a stable release?
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?
Issue Description:
Unable to run the example mentioned in the documentation (https://github.com/google/pinject) of pinject. Getting ModuleNotFoundError: No module named '_gdbm'
.
I've installed python-gdbm
through conda but still, I see the ModuleNotFoundError. Could you please help me to understand what could be the issue?
conda install -c anaconda python-gdbm
Code:
import pinject
class OuterClass(object):
def __init__(self, inner_class):
self.inner_class = inner_class
class InnerClass(object):
def __init__(self):
self.forty_two = 42
obj_graph = pinject.new_object_graph()
outer_class = obj_graph.provide(OuterClass)
print(outer_class.inner_class.forty_two)
Error Log Trace:
ModuleNotFoundError Traceback (most recent call last)
<ipython-input-4-6f1689830d13> in <module>()
9 self.forty_two = 42
10
---> 11 obj_graph = pinject.new_object_graph()
12 outer_class = obj_graph.provide(OuterClass)
13 # print(outer_class.inner_class.forty_two)
~/anaconda3/lib/python3.6/site-packages/pinject/object_graph.py in new_object_graph(modules, classes, binding_specs, only_use_explicit_bindings, allow_injecting_none, configure_method_name, dependencies_method_name, get_arg_names_from_class_name, get_arg_names_from_provider_fn_name, id_to_scope, is_scope_usable_from_scope, use_short_stack_traces)
98 known_scope_ids = id_to_scope.keys()
99
--> 100 found_classes = finding.find_classes(modules, classes)
101 if only_use_explicit_bindings:
102 implicit_class_bindings = []
~/anaconda3/lib/python3.6/site-packages/pinject/finding.py in find_classes(modules, classes)
30 # TODO(kurts): how is a module getting to be None??
31 if module is not None:
---> 32 all_classes |= _find_classes_in_module(module)
33 return all_classes
34
~/anaconda3/lib/python3.6/site-packages/pinject/finding.py in _find_classes_in_module(module)
44 def _find_classes_in_module(module):
45 classes = set()
---> 46 for member_name, member in inspect.getmembers(module):
47 if inspect.isclass(member) and not member_name == '__class__':
48 classes.add(member)
~/anaconda3/lib/python3.6/inspect.py in getmembers(object, predicate)
340 # looking in the __dict__.
341 try:
--> 342 value = getattr(object, key)
343 # handle the duplicate key
344 if key in processed:
~/anaconda3/lib/python3.6/site-packages/six.py in __get__(self, obj, tp)
90
91 def __get__(self, obj, tp):
---> 92 result = self._resolve()
93 setattr(obj, self.name, result) # Invokes __set__.
94 try:
~/anaconda3/lib/python3.6/site-packages/six.py in _resolve(self)
113
114 def _resolve(self):
--> 115 return _import_module(self.mod)
116
117 def __getattr__(self, attr):
~/anaconda3/lib/python3.6/site-packages/six.py in _import_module(name)
80 def _import_module(name):
81 """Import module, returning the module after the last dot."""
---> 82 __import__(name)
83 return sys.modules[name]
84
~/anaconda3/lib/python3.6/dbm/gnu.py in <module>()
1 """Provide the _gdbm module as a dbm submodule."""
2
----> 3 from _gdbm import *
ModuleNotFoundError: No module named '_gdbm'
I noticed this in the most-recent PyPI releases (0.12.2, 0.12.6):
Win10 with 64-bit Python 2.7.15, pip 10.0.1, pinject 0.12.6:
C:\temp\20181129>pip download pinject
Collecting pinject
Downloading https://files.pythonhosted.org/packages/3c/85/1a422b22b0e7d6f4b017597d78fa6ebdfd401e668d8a92f06165f5e43f48/pinject-0.12.6.tar.gz (59kB)
100% |################################| 61kB 124kB/s
Saved c:\temp\20181129\pinject-0.12.6.tar.gz
Complete output from command python setup.py egg_info:
Traceback (most recent call last):
File "<string>", line 1, in <module>
File "c:\users\SCRUBBED\appdata\local\temp\pip-download-xvd2h3\pinject\setup.py", line 19, in <module>
from pinject import (
File "pinject\__init__.py", line 28, in <module>
from .bindings import BindingSpec
File "pinject\bindings.py", line 22, in <module>
from . import decorators
File "pinject\decorators.py", line 17, in <module>
import decorator
ImportError: No module named decorator
----------------------------------------
Command "python setup.py egg_info" failed with error code 1 in c:\users\SCRUBBED\appdata\local\temp\pip-download-xvd2h3\pinject\
The same line fails with the same error on Ubuntu 16 using 64-bit Python 2.7.15rc1, pip version 9.0.1, trying to download 0.12.2.
I download the module to convert it to a distribution-specific package for internal distribution. The pip download command itself is failing.
Version 0.10.2 downloads fine and I've updated my software's setup.py to be bound to it instead of trying to acquire the newer versions.
Hi,
I'm really struggling with pinject, even the basic example isn't working for me.
I'm on Windows 10 with python 3.7.3, pinject 0.12.6
My first try resulted in an exception:
ModuleNotFoundError: No module named '_gdbm'
So I applied a workaround I found in issue #22
Now this simple code:
class OuterClass(object):
def __init__(self, inner_class):
self.inner_class = inner_class
class InnerClass(object):
def __init__(self):
self.forty_two = 42
# somewhere else in my code:
obj_graph = pinject.new_object_graph(
modules=[core, api] # workaround
)
outer_class = obj_graph.provide(OuterClass)
Raises this exception:
{NothingInjectableForArgError} when injecting OuterClass.__init__ at C:\Users\me\PycharmProjects\MyProject\api\views.py:32, nothing injectable for the binding name "inner_class" (unannotated)
I even tried to add this line before requesting the outer_class:
inner_class = obj_graph.provide(InnerClass)
It knows how to create it! pinject should now inject that instance to create the outer_class, but it doesn't.
Any idea?
Hello,
I decided to give pinject a try, but unfortunately it does not import. Here's what I do: install pinject with pip install pinject
and then interactively import pinject
.
It seem to fail on line 156 of third_party/decorator.py
. I'm on python 3.4 so maybe that's the reason although looking at code I can see blocks dealing with python >3.2
Is that a bug?
This test should not fail, i.e., a
and b.a
should be the same instance in a singleton scope:
def test_graph_creation_with_binding_to_instance(self):
class A:
def do(self):
return 1
class B:
def __init__(self, a):
self.a = a
class Binding(BindingSpec):
def configure(self, bind):
bind('a', to_instance=A())
graph = new_object_graph(classes=[A, B], binding_specs=[Binding()])
a = graph.provide(A)
self.assertEqual(1, a.do())
b = graph.provide(B)
self.assertEqual(1, b.a.do())
self.assertIs(a, b.a)
The last assertion fails:
AssertionError: <test_injection.Test.test_graph_creation_with_binding.<locals>.A object at 0x10422f310> is not <test_injection.Test.test_graph_creation_with_binding.<locals>.A object at 0x1043fa2d0>
When importing the requests library and calling new_object_graph
, it fails complaining about Tkinter not being configured for the system.
The problem is that when calling new_object_graph, Pinject traverses the imported modules by default but for some reason it's error on importing Tkinter on python installations where Tkinter isn't supported (intentionally, i.e. python:3.7-alpine
). I believe the import tree is application -> requests -> urllib3 -> six -> Tkinter. I'm not sure what the proper fix is here, but I wanted to report it in case it is an actual behavioral issue with Pinject.
Docker container: python3.7-alpine
Requests: 2.2.1
Pinject: v0.12
This should be enough to repro it:
# repro.py
import pinject
import requests
def main():
graph = pinject.new_object_graph() # errors
if __name__ == "__main__":
main()
(pinject-requests-tkinter) ~/w/pinject-requests-tkinter ❯❯❯ docker run --rm -it pinject-requests-tkinter master ✱ ◼
Python 3.7.3 (default, May 11 2019, 02:00:41)
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import repro
>>> repro.main()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/opt/app/repro.py", line 7, in main
pinject.new_object_graph()
File "/root/.local/share/virtualenvs/app-ueEJiAOq/lib/python3.7/site-packages/pinject/object_graph.py", line 100, in new_object_graph
found_classes = finding.find_classes(modules, classes)
File "/root/.local/share/virtualenvs/app-ueEJiAOq/lib/python3.7/site-packages/pinject/finding.py", line 32, in find_classes
all_classes |= _find_classes_in_module(module)
File "/root/.local/share/virtualenvs/app-ueEJiAOq/lib/python3.7/site-packages/pinject/finding.py", line 46, in _find_classes_in_module
for member_name, member in inspect.getmembers(module):
File "/usr/local/lib/python3.7/inspect.py", line 341, in getmembers
value = getattr(object, key)
File "/root/.local/share/virtualenvs/app-ueEJiAOq/lib/python3.7/site-packages/urllib3/packages/six.py", line 92, in __get__
result = self._resolve()
File "/root/.local/share/virtualenvs/app-ueEJiAOq/lib/python3.7/site-packages/urllib3/packages/six.py", line 115, in _resolve
return _import_module(self.mod)
File "/root/.local/share/virtualenvs/app-ueEJiAOq/lib/python3.7/site-packages/urllib3/packages/six.py", line 82, in _import_module
__import__(name)
File "/usr/local/lib/python3.7/tkinter/__init__.py", line 36, in <module>
import _tkinter # If this fails your Python may not be configured for Tk
ImportError: Error loading shared library libtk8.6.so: No such file or directory (needed by /root/.local/share/virtualenvs/app-ueEJiAOq/lib/python3.7/lib-dynload/_tkinter.cpython-37m-x86_64-linux-gnu.so)
>>>
Also, apologies if this is the wrong place to put this! I figured it wasn't an issue with requests/urllib3/six (at least, I'd hope not) since they're so prevalent.
I am getting this error when installing the latest version (0.12.2) with pip install pinject
:
File "/home/ronen/reach/reach_venv3.6/lib/python3.6/site-packages/pinject/version.py", line 10, in
VERSION = open(VERSION_FILE).read().strip()
FileNotFoundError: [Errno 2] No such file or directory: '/home/ronen/reach/reach_venv3.6/lib/python3.6/site-packages/pinject/../VERSION'
So I have looked and there isn't really any VERSION
file in my site-packages
folder (should there be?).
I am running python3.6.7
on Ubuntu 18.04
. We have been running with django and pinject for a long time now and everything was great.
Please help, thanks in advance!
What is the reasoning for only allowing classes for injection and not allowing all callables?
Most of my providers/factories are simple callables / functions. Adding an unnecessary self
parameter does not feel right to me.
An example of what I am trying to do:
def factory_1(some_value): return some_value + 2
def some_value(a: int): return a * 2
data = {"a": 1}
class MySpec(pinject.BindingSpec):
def configure(self, bind):
for k, v in data.items(): bind(k, to_instance=v)
graph = pinject.new_object_graph(binding_specs=[MySpec()])
print(graph.provide(factory_1))
I would expect the output to be 4
Some things I tried:
setattr(Spec, "provide_some_value", pinject.provide("some_value")(some_value)
For this to work I need to change some_value
to
def some_value(self, a: int): ...
But when I want users to provide their custom providers I don't' want to force them to add this unneeded self
.
I hope I could make it understood what I want to achieve. An inspiration are pytest fixtures.
[root@localhost ~]# python3
Python 3.6.8 (default, Apr 16 2020, 01:36:27)
[GCC 8.3.1 20191121 (Red Hat 8.3.1-5)] on linux
Type "help", "copyright", "credits" or "license" for more information.
import pinject
from enum import Enumclass SendKind(Enum):
... text = 't'
... link = 'l'
...
class test(object):
... @pinject.copy_args_to_internal_fields
... def init(self,send_kind):
... pass
... def demo(self):
... print(self._send_kind.text)
...
obj_graph = pinject.new_object_graph()
test_class = obj_graph.provide(test)
Traceback (most recent call last):
File "/usr/local/lib/python3.6/site-packages/pinject/scoping.py", line 62, in provide
return self._binding_key_to_instance[binding_key]
KeyError: <the binding name "send_kind" (unannotated)>
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/usr/local/lib/python3.6/site-packages/pinject/object_providers.py", line 51, in provide_from_arg_binding_key
provided = provider_indirection.StripIndirectionIfNeeded(Provide)
File "/usr/local/lib/python3.6/site-packages/pinject/provider_indirections.py", line 26, in StripIndirectionIfNeeded
return provide_fn()
File "/usr/local/lib/python3.6/site-packages/pinject/object_providers.py", line 43, in Provide
lambda: binding.proviser_fn(child_injection_context, self,
File "/usr/local/lib/python3.6/site-packages/pinject/scoping.py", line 64, in provide
instance = default_provider_fn()
File "/usr/local/lib/python3.6/site-packages/pinject/object_providers.py", line 44, in
pargs, kwargs))
File "/usr/local/lib/python3.6/site-packages/pinject/bindings.py", line 264, in Proviser
to_class, injection_context, pargs, kwargs)
File "/usr/local/lib/python3.6/site-packages/pinject/object_providers.py", line 70, in provide_class
return cls(*init_pargs, **init_kwargs)
TypeError: call() missing 1 required positional argument: 'value'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "", line 1, in
File "/usr/local/lib/python3.6/site-packages/pinject/object_graph.py", line 203, in provide
raise e
File "/usr/local/lib/python3.6/site-packages/pinject/object_graph.py", line 200, in provide
direct_init_pargs=[], direct_init_kwargs={})
File "/usr/local/lib/python3.6/site-packages/pinject/object_providers.py", line 66, in provide_class
direct_init_pargs, direct_init_kwargs)
File "/usr/local/lib/python3.6/site-packages/pinject/object_providers.py", line 83, in get_injection_pargs_kwargs
lambda abk: self.provide_from_arg_binding_key(
File "/usr/local/lib/python3.6/site-packages/pinject/arg_binding_keys.py", line 108, in create_kwargs
for arg_binding_key in arg_binding_keys}
File "/usr/local/lib/python3.6/site-packages/pinject/arg_binding_keys.py", line 108, in
for arg_binding_key in arg_binding_keys}
File "/usr/local/lib/python3.6/site-packages/pinject/object_providers.py", line 84, in
fn, abk, injection_context))
File "/usr/local/lib/python3.6/site-packages/pinject/object_providers.py", line 58, in provide_from_arg_binding_key
binding.get_binding_target_desc_fn())
pinject.errors.OnlyInstantiableViaProviderFunctionError: when injecting test.init, the arg named "send_kind" unannotated cannot be injected, because its provider, the class main.SendKind, needs at least one directly passed arg
Hello,
How can I use Pinject to inject dependencies into decorators?
For example, say I have a decorator like:
# foo.py
def cache_decorator(cache_client):
def decorator(func):
def wrapper(*args, **kwargs):
cache_client.read()
result = func(*args, **kwargs)
cache_client.write
return result
return wrapper
return decorator
class A:
@cache_decorator(dependency_I_want_to_inject)
def foo(bar):
bar.baz()
The goal is to swap the cache_client with a mock for unit testing.
Is something like this possible with Pinject?
Hi, I've developed a builder for having a multiple step configurations, or a configuration manager that can be imported from different places, configured and in the end can build the object graph.
I've made a different repo, since I wanted to use it immediately for my own project, however I'm opening this ticket as a suggested, if you are interested in integrating it with the original package, I'd be happy to port it, open a PR and shut down the additional package.
This is the package, and please check the documentation for use cases.
https://github.com/eshta/object-graph-builder
Please let me know your thoughts, and thanks :)
Hi,
I didn't see support for this behavior... is it possible? planned feature?
An example scenario:
When bootstrapping the application, resolve an instance of all the classes derived from startable and call their start method.
I bumped into a very very weird issue.
#! /usr/bin/env python2.7
import pinject
class FoundationA(object):
@pinject.inject()
def __init__(self, arg):
self.arg = arg
class FoundationB(object):
@pinject.inject()
def __init__(self, arg):
self._arg = arg
class BindingSpec(pinject.BindingSpec):
def configure(self, bind):
bind('arg', to_instance=1)
object_graph = pinject.new_object_graph(
binding_specs=[
BindingSpec(),
],
only_use_explicit_bindings=True
)
foundation_a = object_graph.provide(FoundationA)
print(foundation_a.arg)
foundation_b = object_graph.provide(FoundationB)
print(foundation_b.arg)
This should print
1
1
Instead it raises ConflictingExplicitBindingsError
.
$ python ./t.py
Traceback (most recent call last):
File "./t.py", line 25, in <module>
only_use_explicit_bindings=True
File "/tmp/venv/lib/python2.7/site-packages/pinject/object_graph.py", line 159, in new_object_graph
raise e
pinject.errors.ConflictingExplicitBindingsError: multiple explicit bindings for same binding name:
the binding at ./t.py:5, from the binding name "foundation" (unannotated) to the class __main__.FoundationA at ./t.py:5, in "singleton scope" scope
the binding at ./t.py:11, from the binding name "foundation" (unannotated) to the class __main__.FoundationB at ./t.py:11, in "singleton scope" scope
Some very weird magic is happening behind the scenes, even though I've set only_use_explicit_bindings=True
!
Any idea what might be wrong?
The default translation from class names to argument names cannot handle class names containing numbers.
Example. If you run this:
from pinject import new_object_graph
class Model1:
pass
class Obj:
def __init__(self, model1):
pass
graph = new_object_graph()
obj = graph.provide(Obj)
You get this error:
nothing injectable for the binding name "model1" (unannotated)
The same appens if the class name contains a single letter camel case word such as ModelA
:
from pinject import new_object_graph
class ModelA:
pass
class Obj:
def __init__(self, model_a):
pass
graph = new_object_graph()
obj = graph.provide(Obj)
It fails too:
nothing injectable for the binding name "model_a" (unannotated)
You can solve the issue by passing a customized get_arg_names_from_class_name
like this:
import re
def get_arg_names_from_class_name_with_nums(class_name):
parts = []
rest = class_name
if rest.startswith('_'):
rest = rest[1:]
while True:
m = re.match(r'([A-Z][a-z]*|[0-9][a-z0-9]*)(.*)', rest)
if m is None:
break
parts.append(m.group(1))
rest = m.group(2)
if not parts:
return []
return ['_'.join(part.lower() for part in parts)]
graph = new_object_graph(get_arg_names_from_class_name=get_arg_names_from_class_name_with_nums)
obj = graph.provide(Obj)
I have prepared a pull request to fix the default get_arg_names_from_class_name
like in the example above.
Because it seems that both the versions are deprecated.
##[error]Version 3.3 with arch x64 not found
##[error]Version 3.4 with arch x64 not found
Available versions:
2.7.17 (x64)
3.5.9 (x64)
3.6.10 (x64)
3.7.6 (x64)
3.8.2 (x64)
Downloading archive: https://storage.googleapis.com/travis-ci-language-archives/python/binaries/ubuntu/16.04/x86_64/python-3.3.tar.bz2
0.11s$ curl -sSf --retry 5 -o python-3.3.tar.bz2 ${archive_url}
curl: (22) The requested URL returned error: 404 Not Found
Unable to download 3.3 archive. The archive may not exist. Please consider a different version.
The 'six' library includes a six.moves package that sets up a meta importer import hook to lazily load a bunch of modules only when they are first accessed. The finding.find_classes code when looping over sys.modules (via modules=ALL_IMPORTED_MODULES) is tripped up by this because as soon as it starts trying to introspect the six.moves module, it triggers lazy loading of a bunch of modules which may or may not be present in the systems given Python installation (common examples of modules that'll show up in the resulting ImportError messages: gdbm and Tkinter)
A workaround for this is to filter the list and avoid module names that start with 'six.'. Very hacky, agreed. Possibly worth considering a bug in https://pypi.python.org/pypi/six but given what it is trying to do I think code just needs to learn to play together. (i've encountered one other piece of code with this same problem due to six.moves having been imported)
I'm mailing you a CL internally, I'll let you push it upstream.
Is Pinject compatible with GTK? For me, this simple example doesn't work.
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk
import pinject
class MyWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="Hello World")
self.button = Gtk.Button(label="Click Here")
self.button.connect("clicked", self.on_button_clicked)
self.add(self.button)
def on_button_clicked(self, widget):
print("Hello World")
obj_graph = pinject.new_object_graph()
Error:
Traceback (most recent call last):
File "main.py", line 54, in <module>
obj_graph = pinject.new_object_graph()
File "/home/lukas/.cache/pypoetry/virtualenvs/test-di-1-QD_7cPfB-py3.7/lib/python3.7/site-packages/pinject/object_graph.py", line 100, in new_object_graph
found_classes = finding.find_classes(modules, classes)
File "/home/lukas/.cache/pypoetry/virtualenvs/test-di-1-QD_7cPfB-py3.7/lib/python3.7/site-packages/pinject/finding.py", line 32, in find_classes
all_classes |= _find_classes_in_module(module)
File "/home/lukas/.cache/pypoetry/virtualenvs/test-di-1-QD_7cPfB-py3.7/lib/python3.7/site-packages/pinject/finding.py", line 46, in _find_classes_in_module
for member_name, member in inspect.getmembers(module):
File "/usr/lib/python3.7/inspect.py", line 341, in getmembers
value = getattr(object, key)
File "/home/lukas/.cache/pypoetry/virtualenvs/test-di-1-QD_7cPfB-py3.7/lib/python3.7/site-packages/gi/module.py", line 163, in __getattr__
setattr(wrapper, value_name, wrapper(value_info.get_value()))
ValueError: invalid enum value: 6
I used pinject to create object graphs in my automated testing. This works really well, except that the time required for new_object_graph() is rapidly adding up. The problem is that pinject is spending a lot of time gathering information about the available classes and what-not, but this information isn't changing between invocations of the function. It would be nice if I could construct a fresh object graph, but keep the static information.
For the moment, I've accomplished this by a rather nasty use of monkey patching in pinject.scoping.get_id_to_scope_with_defaults and replacing the SINGLETON scope with my own that I reset before each test. It works, (I think), but it'd be nice if there was a better approach.
Hi there(who are looking for the Python3 support of pinject for a long time)!
We had just merged #19 (thanks @trein for the great work!) and staging a test version v0.11 on test.pypi.org.
Please install the dev version of pinject by running the following pip command:
pip install \
--no-deps \
--no-cache \
--upgrade \
--index-url https://test.pypi.org/simple/ \
pinject
Then let us know whether it works under your environment.
We will publish the v0.12 version to pypi.org as an official release after we confirmed the v0.11 is work as expected by collecting enough confirmation replies under this issue.
Thanks for your help and let's looking forward to the v0.12 release!
Hi!
Could you publish pinject to pypi? It would make installing and using it really easy.
Thanks!
I have a simple class with a no-op decorator defined and I'm trying to use pinject to instantiate it, but haven't got this to work yet.
Python 3.8
pinject==0.14.1
class Controller:
def __init__(self, original_instance):
self.original_instance = original_instance
@Controller
class UsersController:
pass
users = obj_graph.provide(UsersController)
Trying to run this i get the following error:
example_python-3XnK4UKl\Scripts\python.exe "C:\Program Files\JetBrains\PyCharm Community Edition 2019.2.3\helpers\pydev\pydevd.py" --multiproc --qt-support=auto --client 127.0.0.1 --port 56103 --file Main.py
pydev debugger: process 33564 is connecting
Connected to pydev debugger (build 192.6817.19)
Traceback (most recent call last):
File "example_python/Main.py", line 29, in <module>
users = obj_graph.provide(UsersController)
File "example_python-3XnK4UKl\lib\site-packages\pinject\object_graph.py", line 193, in provide
support.verify_class_type(cls, 'cls')
File "example_python-3XnK4UKl\lib\site-packages\pinject\support.py", line 85, in verify_class_type
_verify_type(inspect.isclass, elt, arg_name, 'class')
File "example_python-3XnK4UKl\lib\site-packages\pinject\support.py", line 104, in _verify_type
raise errors.WrongArgTypeError(
pinject.errors.WrongArgTypeError: wrong type for arg cls: expected class but got Controller
Process finished with exit code 1
pinject.object_graph.ObjectGraph.provide()
expects "cls" to be types.Type
type. This effectively removes support for any class with custom metaclass:
from pinject import new_object_graph
class Meta(type):
pass
class A(object):
__metaclass__ = Meta
new_object_graph().provide(A)
Code above ends up with error:
Traceback (most recent call last):
File "t.py", line 9, in <module>
new_object_graph().provide(A)
File "/local/lib/python2.7/site-packages/pinject/object_graph.py", line 234, in provide
_verify_type(cls, types.TypeType, 'cls')
File "/local/lib/python2.7/site-packages/pinject/object_graph.py", line 175, in _verify_type
arg_name, required_type.__name__, type(elt).__name__)
pinject.errors.WrongArgTypeError: wrong type for arg cls: expected type but got Meta
What's the reason for requiring types.Type
on class?
pinject/pinject/third_party/decorator.py
Line 156 in 80b6759
Hi! I got an error when I tried to use pinject. I use Python 3.7.
"/usr/local/lib/python3.7/dist-packages/pinject/third_party/decorator.py", line 156
exec code in evaldict
Sample code:
import pinject
import rdflib
foo = pinject.new_object_graph()
Running this results in the following traceback:
Traceback (most recent call last):
File "bug.py", line 4, in <module>
foo = pinject.new_object_graph()
File "/Users/bob/.pyenv/versions/2.7.13/Python.framework/Versions/2.7/lib/python2.7/site-packages/pinject/object_graph.py", line 105, in new_object_graph
found_classes = finding.find_classes(modules, classes)
File "/Users/bob/.pyenv/versions/2.7.13/Python.framework/Versions/2.7/lib/python2.7/site-packages/pinject/finding.py", line 32, in find_classes
all_classes |= _find_classes_in_module(module)
File "/Users/bob/.pyenv/versions/2.7.13/Python.framework/Versions/2.7/lib/python2.7/site-packages/pinject/finding.py", line 47, in _find_classes_in_module
for member_name, member in inspect.getmembers(module):
File "/Users/bob/.pyenv/versions/2.7.13/Python.framework/Versions/2.7/lib/python2.7/inspect.py", line 252, in getmembers
value = getattr(object, key)
File "/Users/bob/.pyenv/versions/2.7.13/Python.framework/Versions/2.7/lib/python2.7/site-packages/pkg_resources/_vendor/six.py", line 92, in __get__
result = self._resolve()
File "/Users/bob/.pyenv/versions/2.7.13/Python.framework/Versions/2.7/lib/python2.7/site-packages/pkg_resources/_vendor/six.py", line 115, in _resolve
return _import_module(self.mod)
File "/Users/bob/.pyenv/versions/2.7.13/Python.framework/Versions/2.7/lib/python2.7/site-packages/pkg_resources/_vendor/six.py", line 82, in _import_module
__import__(name)
ImportError: dlopen(/Users/bob/.pyenv/versions/2.7.13/Python.framework/Versions/2.7/lib/python2.7/lib-dynload/gdbm.so, 2): Symbol not found: _gdbm_errno
Referenced from: /Users/bob/.pyenv/versions/2.7.13/Python.framework/Versions/2.7/lib/python2.7/lib-dynload/gdbm.so
Expected in: /usr/local/opt/gdbm/lib/libgdbm.4.dylib
in /Users/bob/.pyenv/versions/2.7.13/Python.framework/Versions/2.7/lib/python2.7/lib-dynload/gdbm.so
Environment:
As a workaround, I'm passing a list of modules to new_object_graph
to exclude rdflib, and that seems to be working so far.
Thank you for this awesome library. Other than this one small hiccup, it has been working very well, doing exactly what I would expect it to do.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.