Giter VIP home page Giter VIP logo

python-inject's People

Contributors

45deg avatar alefnula avatar alex-grover avatar cselvaraj avatar dbalabka avatar didrocks avatar enforcer avatar espenalbert avatar fhdufhdu avatar fzyukio avatar hf-kklein avatar ivankorobkov avatar jaimewyant avatar johan-westin-private avatar marceloavan avatar markhobson avatar nikitsv avatar nikordaris avatar o-fedorov avatar panki avatar peick avatar samiur avatar scharf avatar sukonnik-illia avatar tirkarthi avatar unnonouno avatar wisepotato avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

python-inject's Issues

Hot update configuration is not supported

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

Better type dependency injection

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)

[Question] Positional injection?

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)

Duplicate Binding Exception - Overwrite Bindings

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.

inject.attr Incompatible Python 3.8

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.

Injector configuration

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.

Error in case if class overrides __bool__

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 

Usage of python-inject in a web app: How to mix (static) "once in a lifetime" bindings and dynamic `bind_to_provider`?

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:

  1. bindings at startup
  2. bind_to_provider at a completely different place in the code

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 don't account for hashable keys

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.

Feature request: support dynamic binding configuration

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.

__future__ breaks DI.

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

[Feature Request/Question] Asyncio Support

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 :)

Type hints are not compatible with mypy

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

Problem of binding a constructor which is a member function

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, ???)

Django: inject.InjectorException: Injector is already configured (proposal)

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 :)

Inject dependency to class that will be injected

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

Type-hints are separated from implementation

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.

PyPi releases - can't run tests

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.

No ascii char in README.md result in install 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.

Not helpful error messages when argument is missing

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.

Inject creates multiple objects when a singleton was requested...

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.

Type hint broken for inject.attr when using ABCMeta

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:

Click to expand
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 :)

Type hints mismatch?

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.

binding to a "name"

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?

pip install fails on windowsOS

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.

Register classes programmatically

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'

inspect.iscoroutinefunction fails after inject wraps

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)

Overwrite existing bindings

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.

An example use case:

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.

AttributeError: '_AttributeInjection' object has no attribute 'returning_clause' with peewee ORM

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:

  • Python 3.7.4
  • Inject==4.1.1
  • peewee==3.13.1

Thanks

Thanks

Thank you very much to all the developers who participated in the project, the project is doing very well, you are really awesome~

thread local instances similar to bind_to_constructor

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.

mypy and type inference with @inject decorators

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

Question: Injecting a List

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

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())

Django configuration

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.

Implicit class instantiation is error-prone

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.

support_pytest_issues

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

I want to run the test "test_injector,py" as python -m unittest test_injector Its work normal as the follow info:
test_injector.py:48: DeprecationWarning: Please use assertRaisesRegex instead.
injector.get_instance, int)
...

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

How to inject dependencies that have dependencies

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?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.