ivankorobkov / python-inject Goto Github PK
View Code? Open in Web Editor NEWPython dependency injection
License: Apache License 2.0
Python dependency injection
License: Apache License 2.0
def config_changed_handler(entry: Dict[str, Any]) -> Any:
logger.debug(f"get entry: {entry}")
old_config: Config = inject.instance(Config)
# 获取阿波罗实例
old_apollo_client: ApolloClient = inject.instance(ApolloClient)
# 重新绑定配置
inject.clear_and_configure(bind, bind_in_runtime=False)
# 立即获取apollo实例
new_apollo_client: ApolloClient = inject.instance(ApolloClient)
new_config: Config = inject.instance(Config)
logger.error(
f"old_config-{id(old_config)} is {old_config}\n"
f"new_config-{id(new_config)} is {new_config}\n"
f"old_apollo_client-{id(old_apollo_client)} is {old_apollo_client}\n"
f"new_apollo_client-{id(new_apollo_client)} is {new_apollo_client}\n"
)
# 停止当前线程
old_apollo_client.stop()
def init_apollo_client() -> ApolloClient:
from app.handlers.apollo import config_changed_handler
# 获取阿波罗配置中心的环境变量
apollo_config: ApolloClient = ApolloClient(
app_id=Config.APOLLO_APP_ID,
config_server_url=Config.APOLLO_CONFIG_SERVER_URL
)
apollo_config.set_config_changed_handler(config_changed_handler)
apollo_config.start()
return apollo_config
i use gunicorn to run the app, i can not get the new instance when inject reconfigure in api
Hi, great package. Im currently using the latest version 3.5.4
and I currently have to 'cast' my injected instances, is there a possibility of making this typed? (I might introduce a PR, but just wanted to check with you)
so instead of:
config: Configuration = inject.get_instance(Configuration)
I would just get
config = inject.get_instance(Configuration)
Hello, I really like your package!
I'm wondering about something in your docs, they say that: inject.params injects dependencies as keyword arguments or positional argument.
, but it seems that they only get injected as keyword arguments?
def injection_wrapper(*args: Any, **kwargs: Any) -> T:
# Assumes whatever is provided fulfills type constraints
provided_params = frozenset(arg_names[:len(args)]) | frozenset(kwargs.keys())
# Injects whatever is missing into kwargs
for param, cls in params_to_provide.items():
if param not in provided_params:
kwargs[param] = instance(cls)
return func(*args, **kwargs)
For example, the following works:
class Provider:
def __init__(self, num: int):
self._num = num
def provide(self):
return self._num
@inject.params(a=Provider)
def test_func(a: Provider, b: int = 100):
return a.provide() + b
inject.configure(lambda binder: binder.bind(Provider, Provider(123)))
assert test_func() == 223
assert test_func(b=200) == 323
but this doesn't:
assert test_func(200) == 323 # AttributeError: 'int' object has no attribute 'provide'
I've been playing with the code a bit, and have something that would satisfy the above tests (and your existing tests). Would you be interested in taking a look/is this something you think makes sense for this library?
For context:
I work with projects that have many signatures a la func(dep_1: A, dep_2: B, arg_1, arg_2)
and would love to use your library to inject them and invoke with func(arg_1, arg_2)
without needing to refactor the code base much.
Currently, it seems like I'd need to make them all this: func(arg_1, arg_2, dep_1: A, dep_2: B)
Is there a way to overwrite bindings? For testing I want to be able to binder.install(prod_binder)
and then selectively overwrite a subset of the prod bindings with test bindings instead. Right now i have to duplicate all the bindings just to set a test specific one for a single binding. I can do better groupings of my prod bindings but it would be nice if i can just overwrite ones.
Hello,
Due to a change in the Mock implementation in Python 3.8, inject.attr is basically busted. The change is this: when a spec is passed to a Mock, an iteration happens on all the classes's attributes, and getattr() is called for each one. Something like (other details are intentionally obfuscated there):
for attr in dir(spec_class):
getattr(attr)
Since it's normal to create mocks during the configuration step (inside the config function used in inject.configure()
), this triggers InjectionException because when getattr() hits an attribute with an _AttributeInjection
as its value, it tries to call instance() for that binding -> crash.
My team looked into some solutions, the best one we came up with is a change in the inject framework, as anything else caused a lot of disruption in our codebase. The goal of my change was to delay the attribute error until the injected attribute is actually used. This can be achieved by simply injecting a None instead of raising an exception, but instead I return some sentinel class that will fail if ever called on. Here is the proposed change:
class _NullInjectedAttribute(object):
"""
Returned for injected attributes that don't have an injection configuration.
"""
pass
class _AttributeInjection(object):
def __init__(self, cls):
self._cls = cls
def __get__(self, obj, owner):
# This is a patch to solve an issue that arose in Python 3.8.
# The base Mock class now iterates through all attributes and
# calls getattr() in a class that is used in the `spec` field
# for a Mock. This triggers this codepath and causes injection
# failures if any mock is initialized before the graph is configured.
try:
return instance(self._cls)
except InjectorException:
return _NullInjectedAttribute()
It's a bit crude, but gets the job done. I'm wondering if you agree with this general approach, I'd be happy to submit a PR if so.
Great work!
IMHO configure
, configure_once
and clear_and_configure
could be merged to just configure
.
configure
should not raise an exception. If the intention is to inform the user that the injector is already configured, the warnings
module could be used. clear_and_configure
could be achieved by using a clear
argument in configure
, having as default whatever you want. configure_once
could be the default (or not) behaviour of configure
. The same can be applied by having an extra argument.
I may missing something cause I am new to the library but that's my input.
When a function is decorated with with @param, the function cannot be called with positional arguments:
@param('a',A)
def f(a):
return a
Then a call with positional arguments fails:
f(1)
It might be that class might override __bool__
and restrict casting into boolean. Pandas DataFrame does it. In this case, it is impossible to use an instance of DataFrame as a dependency because of the following error:
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<timed exec> in <module>
/opt/conda/lib/python3.7/site-packages/inject/__init__.py in instance(cls)
321 def instance(cls: Binding) -> Injectable:
322 """Inject an instance of a class."""
--> 323 return get_injector_or_die().get_instance(cls)
324
325 @overload
/opt/conda/lib/python3.7/site-packages/inject/__init__.py in get_instance(self, cls)
169 binding = self._bindings.get(cls)
170 if binding:
--> 171 return binding()
172
173 # Try to create a runtime binding.
/opt/conda/lib/python3.7/site-packages/inject/__init__.py in __call__(self)
212 if self._created and self._instance:
213 return self._instance
--> 214 self._instance = self._constructor()
215 self._created = True
216 return self._instance
/opt/conda/lib/python3.7/site-packages/inject/__init__.py in injection_wrapper(*args, **kwargs)
262 for param, cls in params_to_provide.items():
263 if param not in provided_params:
--> 264 kwargs[param] = instance(cls)
265 return func(*args, **kwargs)
266 return injection_wrapper
/opt/conda/lib/python3.7/site-packages/inject/__init__.py in instance(cls)
321 def instance(cls: Binding) -> Injectable:
322 """Inject an instance of a class."""
--> 323 return get_injector_or_die().get_instance(cls)
324
325 @overload
/opt/conda/lib/python3.7/site-packages/inject/__init__.py in get_instance(self, cls)
169 binding = self._bindings.get(cls)
170 if binding:
--> 171 return binding()
172
173 # Try to create a runtime binding.
/opt/conda/lib/python3.7/site-packages/inject/__init__.py in __call__(self)
206
207 def __call__(self) -> T:
--> 208 if self._created and self._instance:
209 return self._instance
210
/opt/conda/lib/python3.7/site-packages/pandas/core/generic.py in __nonzero__(self)
1477 def __nonzero__(self):
1478 raise ValueError(
-> 1479 f"The truth value of a {type(self).__name__} is ambiguous. "
1480 "Use a.empty, a.bool(), a.item(), a.any() or a.all()."
1481 )
ValueError: The truth value of a GuestFeatures is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().
to fix this issue it would be better to avoid boolean casting for self._instance
in:
/opt/conda/lib/python3.7/site-packages/inject/__init__.py in __call__(self)
206
207 def __call__(self) -> T:
--> 208 if self._created and self._instance:
209 return self._instance
210
Hi,
I'm a bit lost when it comes to the difference between configure
, install
and configure_once
.
In a web app I want to inject some dependencies during the startup of the application (once for the entire lifetime of the app). Other dependencies I'd like to inject using bind_to_provider
but the provider is only just created at runtime because it's based on user inputs.
How can I mix both:
Do I have to clear_and_configure
all bindings including those that have already been bound during startup evertime I bind something (dynamically) to the provider? A MWE would be greatly appreciated :)
Type-hints for any API which accepts a dependency binding defines it as "Type[T]" when in fact this can be a non-type value that is simply Hashable. Taken from docs:
It is possible to use any hashable object as a binding key.
Here is my suggested alternative:
T = TypeVar('T')
Binding = Union[Type[T], Hashable]
Constructor = Provider = Callable[[], T]
I confirmed that my IDE understands that when you pass a non-type, it falls to "Hashable" and drops away type-hints from there. As it stands, I suspect that if you try to use Hashable values in conjunction with a Python type-checker, then the checker would give failures when it should be allowed.
It seems that currently, one needs to provide configuration once at initialization.
In some scenarios the complete set of bound classes might not be available at injector initialization (example: if injector is required while dynamically initializing other modules that require injector).
Please add a method to extend injector with more bindings dynamically.
Something like:
inject.configure_with_module(module1, module2, module3, ...)
Hi!
I've come across an issue when injecting classes that have signatures in which they return themselves. AFAIK you need to import annotations
from __future__
in order to do this kind of thing, and this seems to break DI for everything in the file where this is done, which is a bit unexpected IMO.
ex:
from __future__ import annotations
import inject
from test import BaseTestInject
class Something:
def return_self(self) -> Something:
return self
@inject.autoparams()
def test_func(self, val: int):
return val
class AnotherThing:
@inject.autoparams()
def test_func(self, val: int):
return val
class TestFutureSupport(BaseTestInject):
def test_future_support(self):
inject.configure(lambda binder: binder.bind(int, 123), bind_in_runtime=False)
self.assertRaises(inject.InjectorException, Something().test_func)
self.assertRaises(inject.InjectorException, AnotherThing().test_func)
Would it be feasible to register string versions of the annotations automatically, if they are not provided by user? It seems like this would resolve the issue, at least for my superficial test, and is something the user would not expect to need to do.
from __future__ import annotations
import inject
from test import BaseTestInject
class Something:
def return_self(self) -> Something:
return self
@inject.autoparams()
def test_func(self, val: int):
return val
class AnotherThing:
@inject.autoparams()
def test_func(self, val: int):
return val
class TestFutureSupport(BaseTestInject):
def test_future_support(self):
def deps(binder):
binder.bind(int, 123)
# This fixes things --> should it be done automatically?
binder.bind('int', 123)
inject.configure(deps, bind_in_runtime=False)
assert Something().test_func() == 123
assert AnotherThing().test_func() == 123
Hi,
First of all, great job on this project. I've been testing pinject, injector and dependency-injector, and in my opinion your DI framework is more convenient and stable then the others. Most important, you support hashable objects as binding keys.
Are there any plans on implementing support for asyncio? It could be very useful to have an asyncio
support.
An example for having an async Provider Binding:
# one module
async def get_cache():
return await cache.get('something')
# DI configuration module
def config(binder):
# Executes the provider on each injection.
binder.bind_to_provider('CACHE', get_cache)
# module that depends on db connection
@inject.params(cache='CACHE')
async def get_users(cache):
# do something with cache
Thanks in advance :)
Hello - I'm running into an issue where running mypy
on a project that uses inject
gives the following error on every import:
path/to/file.py:1: error: Cannot find module named 'inject'
...
Based on the mypy docs it appears that a py.typed
file is necessary for PEP 561 compatibility. I tried adding this file and installing my fork but I wasn't able to get it working in the time I had. I'm not an expert with setuptools by any means so maybe my simple change based on the mypy docs was not correct.
If it matters, I am using Python 3.7.4 and my project is using pipenv
. There are no issues with mypy and any other third party libs.
Any help would be appreciated!
cc @andrewborba10, not sure if you are using mypy
in your workflow
Dear authors,
Thank you for your hard working on the python-inject package.
I am new to the concept of dependency injection, and I am trying to use this idea and the python-inject package in my current project. I encountered a problem that I cannot solve after hours. My problem is as follows:
Suppose that we have two classes A and B, B's init function is dependent on A:
class A(object):
def __init__(self):
print('A')
class B(object):
@inject.autoparams()
def __init__(self, a: A):
self._a = a
print('B')
Then, to create the injection
def config():
binder.bind_to_constructor(A, A.__init__)
binder.bind_to_constructor(B, B.__init__)
inject.configure(config)
Running this code gives an error
"File "...\lib\site-packages\inject-4.0.0-py3.6.egg\inject_init_.py", line 201, in call
self._instance = self._constructor()
TypeError: init() missing 1 required positional argument: 'self'".
I know there is an alternative way to implement this, which is manually instantiate an instance of A, and then bind dependencies on A to that instance. But what if I have another class C, whose instantiation is dependent on B:
class C(object):
@inject.autoparams()
def __init__(self, b: B):
self._b = b
print('C')
Then how to create the dependencies,
def config():
a = A()
binder.bind(A, a)
binder.bind(B, ???)
Since django
is (unfortunately) loading some of its modules more than once, inject
is throwing an: inject.InjectorException: Injector is already configured
exception.
My current workaround is:
if inject.get_injector() is None:
inject.configure(ioc.production)
But that's exposing the internals of the inject
library. I think that it would be cleaner to have something like:
if not inject.is_configured():
inject.configure(ioc.production)
But, if you're OK with the "hacky" solution, I'll close the issue.
Thank you for your time :)
Is there any support or plan for enabling parallel test execution with inject?
Suppose I have class A like this:
class A:
@inject.autoparams('instance_b'):
def __init__(self, instance_b: B):
self.instance_b= instance_b
in my configuration method:
def di_configuration(binder):
binder.bind(B, B())
binder.bind(A, A())
inject.configure(di_configuration)
I expect this work because A need B and I config B to be injected. but when I run my django application I get this error:
inject.InjectorException: No injector is configured
Some files have type hints and others do not.
I think writing type hints make it easier to understand the patterns.
I suggest to write type hints and if its okay, I will work some.
ex. https://github.com/ivankorobkov/python-inject/blob/master/inject/__init__.py#L506
It seems like pip installing the latest version of inject on Python 2.7 tries to install version 4.3.1 which is compatible with 3.6+ . Is there a way for it to resolve to the latest 2.7 compatible version?
Thanks.
When I use the lastest version Inject==3.5.2,I got the error "AttributeError: type object 'Callable' has no attribute '_abc_registry' ".
But when I installed the lower version Inject==3.5.1,all the things are OK.Could you give me some help? Thank you !
Type-hints are separated into a .pyi file alongside inject.py. This seems unnecessary when they can be bundled together for easier integration. Right now, my IDE doesn't understand the type-hints by default, and while I'm sure I can fiddle with settings and get it to pick up the .pyi file (which is something I've had trouble with in the past) its just a hassle that gets passed on to every new consumer of this library.
I believe .pyi files were introduced as a stop-gap for the Python standard library to allow for incremental additions from the community and generally should be avoided in scenarios like this where the implementation can be fairly easily updated.
I would gladly volunteer to do the work on this (in fact, I already wrote some type-hints within our own project via a shim between our code and this library, but that approach has some short-falls that ultimately motivated me to come here).
Please let me know if you're on board with this idea and I can submit a pull request.
The tarballs available on PyPi lack the test/__init__.py
file, which makes it impossible to run the test suite, since all the tests fail with an import error.
there are no ascii encode chars in the readme.md , so when pip install inject on ascii encode platform there will be an error , I solve this by change the encoding to utf-8 in setup.py, but this error maybe confusing, hope to fix it.
Error messages should be improved when some argument is missing:
Traceback (most recent call last):
...
File "bin/console.py", line 22, in main
inject.instance(Trainer).train()
File "/Users/torinaki/www/htdocs/ecore-cart-recommender/env/lib/python3.7/site-packages/inject/__init__.py", line 362, in instance
return get_injector_or_die().get_instance(cls)
File "/Users/torinaki/www/htdocs/ecore-cart-recommender/env/lib/python3.7/site-packages/inject/__init__.py", line 211, in get_instance
return binding()
File "/Users/torinaki/www/htdocs/ecore-cart-recommender/env/lib/python3.7/site-packages/inject/__init__.py", line 254, in __call__
self._instance = self._constructor()
File "/Users/torinaki/www/htdocs/ecore-cart-recommender/env/lib/python3.7/site-packages/inject/__init__.py", line 303, in injection_wrapper
kwargs[param] = instance(cls)
File "/Users/torinaki/www/htdocs/ecore-cart-recommender/env/lib/python3.7/site-packages/inject/__init__.py", line 362, in instance
return get_injector_or_die().get_instance(cls)
File "/Users/torinaki/www/htdocs/ecore-cart-recommender/env/lib/python3.7/site-packages/inject/__init__.py", line 211, in get_instance
return binding()
File "/Users/torinaki/www/htdocs/ecore-cart-recommender/env/lib/python3.7/site-packages/inject/__init__.py", line 254, in __call__
self._instance = self._constructor()
File "/Users/torinaki/www/htdocs/ecore-cart-recommender/env/lib/python3.7/site-packages/inject/__init__.py", line 303, in injection_wrapper
kwargs[param] = instance(cls)
File "/Users/torinaki/www/htdocs/ecore-cart-recommender/env/lib/python3.7/site-packages/inject/__init__.py", line 362, in instance
return get_injector_or_die().get_instance(cls)
File "/Users/torinaki/www/htdocs/ecore-cart-recommender/env/lib/python3.7/site-packages/inject/__init__.py", line 211, in get_instance
return binding()
File "/Users/torinaki/www/htdocs/ecore-cart-recommender/env/lib/python3.7/site-packages/inject/__init__.py", line 254, in __call__
self._instance = self._constructor()
File "/Users/torinaki/www/htdocs/ecore-cart-recommender/env/lib/python3.7/site-packages/inject/__init__.py", line 304, in injection_wrapper
return func(*args, **kwargs)
TypeError: __init__() missing 1 required positional argument: 'name'
Error message should provide class/function name, parameter name and type.
Ran into some weirdness in using Inject. Weirdness that I consider to be a bug...
I had declared that I wanted only one copy of a certain kind of object - but it gave me two distinct objects.
It took a while to figure out the symptoms in my code were caused by that, and what the underlying cause was.
One of the consumers of these objects was in a certain module, and the other was in the same file as the object declaration.
Because I was testing this new code, I had some code below the 'if name == "main" clause.
That code thought the object class was <main.Store>, and the other consumers (and the code that set up the injection mapping) thought it was <store.Store>. Although they're the same class, Python treats them as different classes... Hence, any consumers of the object which used inject.params in main, were always going to get a unique object each time it asked for one. This happens because the inject code thinks it's OK to instantiate an object (with no parameters) if there is no declared supplier of the object. In my view, that's an error. If I declare that I need a certain kind of object in inject.params, then IMHO it's an error for me not to supply it in the injection parameters.
It would have cut several hours off the process to realize what was going on sooner - with an error message. Especially if the error message said something like: No constructor for object type blah - currently known constructors are: (list of known object types). That would have made it dead obvious what was wrong immediately.
So, I think that should be an error, at least by default (maybe all the time?).
Ideally, it would be nice to figure out how to keep this from happening, then you wouldn't have to make that an error.
Here's a thought about one approach:
Upon checking, no matter where you look, cls.name gives the same answer for both main.Store and store.store. So, if you paid attention to the class name instead of the actual class, then I believe that would solve the problem.
It seems really unlikely that someone would have two different classes of the same name, and want to supply two different constructors for them.
I'm not sure whether it's possible to do fix this at all, or whether it's an inherent Python thing. Also, just a heads up. This issue is only related to static type checking. It doesn't manifest itself in runtime.
I have an interface class (let's say BaseService
) that has ABCMeta
as its metaclass with @abstractmethod
decorated empty methods. Several other classes (in my example Service
) inherit from it and implement the methods.
Then I have a factory function that instantiates one of the Implementations and returns it with the BaseService
type hint. I use inject bind the factory to the BaseService
type.
To resolve the dependency in another class I used inject.attr(BaseService)
, but the returned value is type hinted as Union[object, Any]
, instead of BaseService
. This leads mypy to flag any code that uses the resolved dependency, saying that the object doesn't have any of the attributes that BaseService
has.
Here is a simple reproducible example:
from abc import ABCMeta, abstractmethod
import inject
# Dummy service interface (abstract base) class
class BaseService(metaclass=ABCMeta):
@abstractmethod
def f(self) -> None:
pass
# Dummy service implementation
class Service(BaseService):
def f(self) -> None:
print('f')
# Dummy service factory (for multiple different implementations)
def service_factory() -> BaseService:
return Service()
# Simple Test class to trigger the issue
class Test:
service = inject.attr(BaseService)
def test(self) -> None:
self.service.f() # mypy error: Item "object" of "Union[object, Any]" has no attribute "f"
# Binding the factory as a constructor (not sure if this is necessary)
def bindings(binder: inject.Binder) -> None:
binder.bind_to_constructor(BaseService, service_factory)
inject.configure(bindings)
Test().test()
As I said, this code works just fine when I run it through the CPython (3.8) interpreter. The problem is only related to mypy static checking. Running mypy on the code yields the following error:
test.py:29: error: Item "object" of "Union[object, Any]" has no attribute "f"
If i instead remove metaclass=ABCMeta
and the @abstractmethod
decorator from BaseService
and use it as a normal base class, there is no issue. My guess is that the ABCMeta
metaclass breaks the type hint for some reason, but I don't know enough about how it works to even make an educated guess.
If this issue is not in fact rooted in this library, let me know and I'll open the issue elsewhere.
Thanks :)
Hello,
I caught a type error with IntelliJ IDE caused by the inferred type of inject.attr(IFoo)
is not IFoo
but Type[IFoo]
. Inspecting the library, I found the two different annotation of type:
def attr(cls: T) -> T:
# type: (Binding) -> T
"""Return a attribute injection (descriptor)."""
return _AttributeInjection(cls)
After changing cls: T
with cls: Binding
, the inference goes right.
Is there any reason for these different annotations (backward compatibility, etc.)?
I should have made a PR for that if it is an unintentional mistake but asked at first for clarification.
Thanks for your works.
I play with this library for a moment, and I wanted bind some instance to ... name.
binder.bind("config", {"some": "example"})
then
inject.instance("config")
and it works. I like it even it is not intentional. Maybe it is worth to put some code example with such a usage?
.
Hello
I get the following error when I pip install on windows.
UnicodeDecodeError: 'cp932' codec can't decode byte 0x93 in position 471: illegal multibyte sequence
Perhaps modifying setup.py(line7~9) as follows will cure it?
def read_description():
with open('README.md', 'r', encoding='utf-8') as f:
return f.read()
or
def read_description():
with open('README.md', 'r', encoding='utf-8_sig') as f:
return f.read()
Thanks for your works.
Is it possible to register classes in the same way as @inject.autoparams
provides but programmatically inside configuration callback?
For example:
def configure(binder: Binder):
binder.bind_to_constructor(SomeClass, inject.autoparams()(SomeClass.__init__))
inject.configure(configure)
This example will throw an error:
TypeError: __init__() missing 1 required positional argument: 'self'
Decorating functions with param/params/autoparams returns a synchronous function instead of preserving the async function. The following inspect.iscoroutinefunction will fail. This causes problems when other decorators depend on inspecting the function prior to executing it.
@inject.param("val")
async def test_func(val):
return val
assert inspect.iscoroutinefunction(test_func)
Currently, if you create a binding where a binding already exists for the for the given key, an exception is thrown: inject.InjectorException: Duplicate binding, key=<class 'someValue'>
In many DI frameworks, it's common to allow binding of an existing key to a new value, the behavior for which is to replace the existing binding.
For example, Angular uses a hierarchical injector that allows children to override their parent bindings. Autofac uses the last registration/binding for a given key, value.
I have a set of binding configurations that my app uses.
I then have a separate test configuration that inherits from my app bindings. If I'm using provider functions, its simple to override them, however if I'm doing simple binding of a Class/value to a value, then I don't have easy way to override the binding.
I realize that there are workarounds for this, but I think this would be simple to implement and provide value to the framework as well as align the duplicate binding behavior to other DI frameworks.
Hi, Ivan! Thank you for creating this library. But I have some difficulties using it with peewee ORM.
Main setup:
import inject
from peewee import MySQLDatabase
database = MySQLDatabase(**config)
def injection(binder):
binder.bind(MySQLDatabase, database)
inject.configure(injection)
models
packet:
import inject
from peewee import *
class BaseModel(Model):
class Meta:
database = inject.attr(MySQLDatabase)
class User(BaseModel):
id = CharField(primary_key=True)
name = TextField(null=True)
class Meta:
table_name = 'users'
Then during insertion User.create(id='unique_id', name='John')
I caught this:
Traceback (most recent call last):
File "C:\Workspace\python\my_project\service.py", line 40, in track
User.create(id='unique_id', name='John'):
File "C:\Workspace\python\my_project\venv\lib\site-packages\peewee.py", line 6202, in insert
return ModelInsert(cls, cls._normalize_data(__data, insert))
File "C:\Workspace\python\my_project\venv\lib\site-packages\peewee.py", line 7120, in __init__
if self.model._meta.database.returning_clause:
AttributeError: '_AttributeInjection' object has no attribute 'returning_clause'
It looks like _AttributeInjection
container doesn't unwrap into MySQLDatabase
instance. Can you help me? Is it my stupidity?
Enviroment:
Thank you very much to all the developers who participated in the project, the project is doing very well, you are really awesome~
I'm missing a variant of bind_to_constructor
to register thread local instances. bind_to_constructor
registers an instance globally for all threads.
bind_to_provider
is not suitable in my case, because I don't want to create a different instance of every inject call.
Hi,
We've been making use of your library quite a lot and it's been a joy to use! The only challenge I've really run into is handling pyright/mypy errors with the @inject
decorator. The decorator of course modifies the type signature of the function/classes, but pyright, mypy, pylint etc don't really have knowledge of this so they throw warnings that certain parameters are missing.
Are there any workarounds to this? I've been trying to figure something out but so far it seems like the likely answer is "no". This is the closest microsoft/pyright#774
Other than that the only ideas I've come up is converting the injected parameters from required to optional. But that's a less than ideal solution since often time these are classes which can be expensive to instantiate, and performance matters for my application.
Best
Thank you for creating this library. It's saved me some headaches for sure.
Is there currently a way to inject a collection of implementations?
class Shape:
def draw(self):
raise NotImplementedError()
class Triangle(Shape):
def draw(self):
print('drew a triangle')
class Square(Shape):
def draw(self):
print('drew a square')
class ShapeContainer:
"""
how to inject shapes here?
"""
def __init__(self, shapes: List[Shape]):
self.shapes = shapes
def draw_all(self):
for shape in self.shapes:
shape.draw()
export Binder in .pyi
T = typing.TypeVar('T')
Binder = typing.TypeVar('Binder')
Injector = typing.TypeVar('Injector')
if we use type-hint in project,
def my_config(binder: Binder):
binder.bind(Config, load_config_file())
Apologies if this is not the right way of asking questions. Please let me know If I should ask somewhere else.
I was wondering if there is any documentation explaining the best place to use inject with a Django application. For instance, should I used the AppConfig.ready
method to centralize injection per Django app? Is there any other guideline or recommendation?
Thank you very much.
If a user forgets to bind a class to an instance, in the best case she gets an exception like TypeError
for abstract classes, typing variables, callables that require at least one argument and so on. Such messages are usually not helpful and sometimes obscuring.
In the worst case the singleton is instantiated with default parameters while it was not intended. It may lead to hard-to-debug issues.
I've recently encountered one of such issues. Also, "explicit is better than implicit".
At minimum, I would add an argument to the configure
functions to disable implicit classes instantiation.
And for the future I would suggest to gradually make implicit instantiation disabled by default, or even deprecate and then remove this functionality.
I just discover PR #17 which fixes an issue I just bumped into. Any plan for a 3.3.1 release in a near future?
I am env at windows 10 and python3.6.8,
================================================== test session starts ==================================================
platform win32 -- Python 3.6.8, pytest-5.4.1, py-1.8.1, pluggy-0.13.1
cachedir: .pytest_cache
Ran 6 tests in 0.002s
OK
and then, I want to run the module with pytest
pytest test_injector.py , that was failure with this information
init.py:3: in
import inject
..\inject_init_.py:88: in
from typing import Any, Callable, Dict, ForwardRef, Generic, Hashable, Optional, Type, TypeVar,
E ImportError: cannot import name 'ForwardRef'
================================================ short test summary info ================================================
ERROR test_injector.py
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
=================================================== 1 error in 0.08s
What I'm trying to do:
def config_inject(binder: inject.Binder) -> None:
binder.bind(Database, MongoDatabase())
binder.bind(SomeRepository, MongoSomeRepository())
inject.configure(config_inject)
MongoSomeRepository has dependency over Database, but when I'm trying to config it this way I got the error "No injector is configured". I guess it happens because at the moment the bind is being made the injector is not ready. So how do I handle this case?
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.