Readd support for decorating classes with @inject

As a bonus this provides dataclass support.
This commit is contained in:
Jakub Stasiak 2019-05-22 09:11:33 +02:00
parent da842ed281
commit 53dee44872
5 changed files with 81 additions and 5 deletions

View File

@ -15,7 +15,7 @@ matrix:
include:
- { python: "3.7", dist: xenial, sudo: true }
install:
- pip install --upgrade coveralls pytest "typing$TYPING_VERSION" "pytest-cov>=2.5.1"
- pip install --upgrade coveralls pytest "typing$TYPING_VERSION" "pytest-cov>=2.5.1" dataclasses
# mypy can't be installed on pypy
- if [[ "${TRAVIS_PYTHON_VERSION}" != "pypy"* ]] ; then pip install mypy ; fi
# Black is Python 3.6+-only

View File

@ -1,6 +1,13 @@
Injector Change Log
===================
0.16.2
------
- (Re)added support for decorating classes themselves with :func:`@inject <injector.inject>`. This is the same
as decorating their constructors. Among other things this gives us
`dataclasses <https://docs.python.org/3/library/dataclasses.html>`_ integration.
0.16.1
------

View File

@ -50,6 +50,26 @@ A Quick Example
```
Or with `dataclasses` if you like:
```python
from dataclasses import dataclass
from injector import Injector, inject
class Inner:
def __init__(self):
self.forty_two = 42
@inject
@dataclass
class Outer:
inner: Inner
injector = Injector()
outer = injector.get(Outer)
print(outer.inner.forty_two) # Prints 42
```
A Full Example
--------------

View File

@ -899,7 +899,7 @@ def provider(function):
return function
def inject(function):
def inject(constructor_or_class):
"""Decorator declaring parameters to be injected.
eg.
@ -923,15 +923,40 @@ def inject(function):
>>> a = Injector(configure).get(A)
[123, 'Bob', [1, 2, 3]]
As a convenience one can decorate a class itself:
>>> @inject
... class B:
... def __init__(self, dependency: Dependency):
... self.dependency = dependency
This is equivalent to decorating its constructor. In particular this provides integration with
`dataclasses <https://docs.python.org/3/library/dataclasses.html>`_ (the order of decorator
application is important here):
>>> @inject
... @dataclass
... class C:
... dependency: Dependency
.. note::
This decorator is to be used on class constructors. Using it on non-constructor
methods worked in the past but it was an implementation detail rather than
a design decision.
This decorator is to be used on class constructors (or, as a convenience, on classes).
Using it on non-constructor methods worked in the past but it was an implementation
detail rather than a design decision.
Third party libraries may, however, provide support for injecting dependencies
into non-constructor methods or free functions in one form or another.
.. versionchanged:: 0.16.2
(Re)added support for decorating classes with @inject.
"""
if isinstance(constructor_or_class, type) and hasattr(constructor_or_class, '__init__'):
inject(constructor_or_class.__init__)
return constructor_or_class
function = constructor_or_class
try:
bindings = _infer_injected_bindings(function)
except _BindingNotYetAvailable:

View File

@ -13,6 +13,7 @@
from contextlib import contextmanager
from typing import Any, NewType
import abc
import sys
import threading
import traceback
import warnings
@ -1393,3 +1394,26 @@ def test_newtype_integration_works():
injector = Injector([configure])
assert injector.get(UserID) == 123
@pytest.mark.skipif(sys.version_info < (3, 6), reason="Requires Python 3.6+")
def test_dataclass_integration_works():
import dataclasses
# Python 3.6+-only syntax below
exec(
"""
@inject
@dataclasses.dataclass
class Data:
name: str
""",
locals(),
globals(),
)
def configure(binder):
binder.bind(str, to='data')
injector = Injector([configure])
assert injector.get(Data).name == 'data'