Remove @inject decorator

This commit is contained in:
Roman Mogilatov 2016-10-30 13:19:39 +02:00
parent d1b1196d6d
commit c8a5db7691
7 changed files with 5 additions and 439 deletions

View File

@ -1,113 +0,0 @@
"""Dependency injector injections module."""
import warnings
import six
from dependency_injector.providers.base import (
_parse_positional_injections,
_parse_keyword_injections,
)
from dependency_injector import utils
from dependency_injector import errors
def inject(*args, **kwargs):
"""Dependency injection decorator.
.. warning::
:py:func:`inject` decorator has been deprecated since version 2.2.0.
Usage of :py:func:`inject` decorator can lead to bad design and could
be considered as anti-pattern.
:py:func:`inject` decorator can be used for making inline dependency
injections. It patches decorated callable in such way that dependency
injection will be done during every call of decorated callable.
:py:func:`inject` decorator supports different syntaxes of passing
injections:
.. code-block:: python
# Positional arguments injections:
@inject(1, 2)
def some_function(arg1, arg2):
pass
# Keyword arguments injections:
@inject(arg1=1)
@inject(arg2=2)
def some_function(arg1, arg2):
pass
# Keyword arguments injections into class init:
@inject(arg1=1)
@inject(arg2=2)
class SomeClass(object):
def __init__(self, arg1, arg2):
pass
.. deprecated:: 2.2.0
Usage of :py:func:`inject` decorator can lead to bad design and could
be considered as anti-pattern.
:param args: Tuple of context positional arguments.
:type args: tuple[object]
:param kwargs: Dictionary of context keyword arguments.
:type kwargs: dict[str, object]
:return: Class / callable decorator
:rtype: (callable) -> (type | callable)
"""
warnings.warn(message='Call to a deprecated decorator - @{0}.{1}'
.format(inject.__module__, inject.__name__),
category=DeprecationWarning,
stacklevel=2)
arg_injections = _parse_positional_injections(args)
kwarg_injections = _parse_keyword_injections(kwargs)
def decorator(callback_or_cls):
"""Dependency injection decorator."""
if isinstance(callback_or_cls, six.class_types):
cls = callback_or_cls
cls_init = utils.fetch_cls_init(cls)
if not cls_init:
raise errors.Error(
'Class {0}.{1} has no __init__() '.format(cls.__module__,
cls.__name__) +
'method and could not be decorated with @inject decorator')
cls.__init__ = decorator(cls_init)
return cls
callback = callback_or_cls
if hasattr(callback, '__INJECT_DECORATED__'):
callback.args += arg_injections
callback.kwargs.update(kwarg_injections)
return callback
@six.wraps(callback)
def decorated(*args, **kwargs):
"""Decorated with dependency injection callback."""
if decorated.args:
args = tuple(arg.provide_injection()
for arg in decorated.args) + args
for name, arg in six.iteritems(decorated.kwargs):
if name not in kwargs:
kwargs[name] = arg.provide_injection()
return callback(*args, **kwargs)
decorated.__INJECT_DECORATED__ = True
decorated.origin = callback
decorated.args = arg_injections
decorated.kwargs = kwarg_injections
return decorated
return decorator

View File

@ -1,6 +1,5 @@
"""Dependency injector utils module.""" """Dependency injector utils module."""
import sys
import copy as _copy import copy as _copy
import types import types
import threading import threading
@ -16,12 +15,6 @@ GLOBAL_LOCK = threading.RLock()
:type: :py:class:`threading.RLock` :type: :py:class:`threading.RLock`
""" """
_IS_PYPY = '__pypy__' in sys.builtin_module_names
if _IS_PYPY or six.PY3: # pragma: no cover
_OBJECT_INIT = six.get_unbound_function(object.__init__)
else: # pragma: no cover
_OBJECT_INIT = None
if six.PY2: # pragma: no cover if six.PY2: # pragma: no cover
_copy._deepcopy_dispatch[types.MethodType] = \ _copy._deepcopy_dispatch[types.MethodType] = \
lambda obj, memo: type(obj)(obj.im_func, lambda obj, memo: type(obj)(obj.im_func,
@ -90,24 +83,6 @@ def represent_provider(provider, provides):
address=hex(id(provider))) address=hex(id(provider)))
def fetch_cls_init(cls):
"""Return reference to the class.__init__() method if it is defined.
:param cls: Class instance
:type cls: type
:return: Reference to the class.__init__() if any, or None otherwise.
:rtype: unbound method | None
"""
try:
cls_init = six.get_unbound_function(cls.__init__)
assert cls_init is not _OBJECT_INIT
except (AttributeError, AssertionError):
return None
else:
return cls_init
def deepcopy(instance, memo=None): def deepcopy(instance, memo=None):
"""Make full copy of instance.""" """Make full copy of instance."""
return _copy.deepcopy(instance, memo) return _copy.deepcopy(instance, memo)

View File

@ -1,70 +0,0 @@
Advanced usage
==============
Current section of documentation describes advanced usage of
*Dependency Injector*.
@inject decorator
-----------------
.. currentmodule:: dependency_injector.injections
.. warning::
:py:func:`inject` decorator has been deprecated since version 2.2.0.
:py:func:`inject` decorator is a part of
:py:mod:`dependency_injector.injections` module.
:py:func:`inject` decorator can be used for making *inline* dependency
injections. It *patches* decorated callable in such way that dependency
injection will be done during every call of decorated callable.
:py:func:`inject` takes a various number of positional and keyword arguments
that are used as decorated callable injections. Every time, when
:py:func:`inject` is called, positional and keyword argument injections would
be passed as an callable arguments.
Such behaviour is very similar to the standard Python ``functools.partial``
object, except of one thing: all injectable values are provided
*"as is"*, except of providers (subclasses of
:py:class:`dependency_injector.providers.Provider`). Providers
will be called every time, when injection needs to be done. For example,
if injectable value of injection is a
:py:class:`dependency_injector.providers.Factory`, it will provide new one
instance (as a result of its call) every time, when injection needs to be done.
:py:func:`inject` behaviour with context positional and keyword arguments is
very like a standard Python ``functools.partial``:
- Positional context arguments will be appended after :py:func:`inject`
positional injections.
- Keyword context arguments have priority on :py:func:`inject` keyword
injections and will be merged over them.
Example:
.. literalinclude:: ../../examples/advanced_usage/inject_simple.py
:language: python
:linenos:
Example of usage :py:func:`inject` decorator with Flask:
.. literalinclude:: ../../examples/advanced_usage/inject_flask.py
:language: python
:linenos:
@inject decorator with classes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
:py:func:`inject` could be applied for classes. In such case, it will look for
class ``__init__()`` method and pass injection to it. If decorated class has
no ``__init__()`` method, appropriate
:py:exc:`dependency_injector.errors.Error` will be raised.
Example of usage :py:func:`inject` with Flask class-based view:
.. literalinclude:: ../../examples/advanced_usage/inject_flask_class_based.py
:language: python
:linenos:

View File

@ -1,7 +0,0 @@
``dependency_injector.injections``
----------------------------------
.. automodule:: dependency_injector.injections
:members:
:inherited-members:
:show-inheritance:

View File

@ -9,7 +9,10 @@ follows `Semantic versioning`_
Development version Development version
------------------- -------------------
- No features. - Remove ``@inject`` decorator.
.. - No features.
2.2.10 2.2.10
------ ------

View File

@ -2,7 +2,7 @@
import unittest2 as unittest import unittest2 as unittest
from dependency_injector import providers, injections, utils, errors from dependency_injector import providers, utils, errors
class Example(object): class Example(object):
@ -731,26 +731,6 @@ class DelegatedThreadLocalSingletonTests(unittest.TestCase):
class FactoryAsDecoratorTests(unittest.TestCase): class FactoryAsDecoratorTests(unittest.TestCase):
"""Factory as decorator tests.""" """Factory as decorator tests."""
def test_decoration(self):
"""Test decoration of some class with Factory provider."""
@providers.Factory
class AuthService(object):
"""Auth service."""
@providers.Factory
@injections.inject(auth_service=AuthService)
class UsersService(object):
"""Users service."""
def __init__(self, auth_service):
"""Initializer."""
self.auth_service = auth_service
users_service = UsersService()
self.assertIsInstance(users_service, UsersService.cls)
self.assertIsInstance(users_service.auth_service, AuthService.cls)
def test_decoration_and_overriding(self): def test_decoration_and_overriding(self):
"""Test decoration of some class with Factory provider.""" """Test decoration of some class with Factory provider."""
@providers.Factory @providers.Factory

View File

@ -1,202 +0,0 @@
"""Dependency injector injections unittests."""
import warnings
import unittest2 as unittest
from dependency_injector import injections
from dependency_injector import providers
from dependency_injector import errors
class InjectTests(unittest.TestCase):
"""Inject decorator test cases."""
def test_decorated_args(self):
"""Test `inject()` decoration with args."""
provider1 = providers.Factory(object)
provider2 = providers.Factory(list)
@injections.inject(provider1, provider2)
def test(a, b):
return a, b
a1, b1 = test()
a2, b2 = test()
self.assertIsInstance(a1, object)
self.assertIsInstance(a2, object)
self.assertIsNot(a1, a2)
self.assertIsInstance(b1, list)
self.assertIsInstance(b2, list)
self.assertIsNot(b1, b2)
def test_decorated_args_several_times(self):
"""Test `inject()` decoration with args several times."""
provider1 = providers.Factory(object)
provider2 = providers.Factory(list)
@injections.inject(provider2)
@injections.inject(provider1)
def test(a, b):
return a, b
a1, b1 = test()
a2, b2 = test()
self.assertIsInstance(a1, object)
self.assertIsInstance(a2, object)
self.assertIsNot(a1, a2)
self.assertIsInstance(b1, list)
self.assertIsInstance(b2, list)
self.assertIsNot(b1, b2)
def test_decorated_context_args(self):
"""Test `inject()` decoration with context args."""
provider1 = providers.Factory(object)
provider2 = providers.Factory(list)
@injections.inject(provider1)
def test(a, b):
return a, b
a1, b1 = test(provider2())
a2, b2 = test(provider2())
self.assertIsInstance(a1, object)
self.assertIsInstance(a2, object)
self.assertIsNot(a1, a2)
self.assertIsInstance(b1, list)
self.assertIsInstance(b2, list)
self.assertIsNot(b1, b2)
def test_decorated_kwargs(self):
"""Test `inject()` decoration with kwargs."""
provider1 = providers.Factory(object)
provider2 = providers.Factory(list)
@injections.inject(a=provider1)
@injections.inject(b=provider2)
def test(a, b):
return a, b
a1, b1 = test()
a2, b2 = test()
self.assertIsInstance(a1, object)
self.assertIsInstance(a2, object)
self.assertIsNot(a1, a2)
self.assertIsInstance(b1, list)
self.assertIsInstance(b2, list)
self.assertIsNot(b1, b2)
def test_decorated_kwargs_priority(self):
"""Test `inject()` decorated callback kwargs priority."""
provider1 = providers.Factory(object)
provider2 = providers.Factory(list)
object_a = object()
@injections.inject(a=provider1)
@injections.inject(b=provider2)
def test(a, b):
return a, b
a1, b1 = test(a=object_a)
a2, b2 = test(a=object_a)
self.assertIsInstance(a1, object)
self.assertIsInstance(a2, object)
self.assertIs(a1, object_a)
self.assertIs(a2, object_a)
self.assertIsInstance(b1, list)
self.assertIsInstance(b2, list)
self.assertIsNot(b1, b2)
def test_decorated_with_args(self):
"""Test `inject()` decorated callback with args."""
provider = providers.Factory(list)
object_a = object()
@injections.inject(b=provider)
def test(a, b):
return a, b
a1, b1 = test(object_a)
a2, b2 = test(object_a)
self.assertIsInstance(a1, object)
self.assertIsInstance(a2, object)
self.assertIs(a1, object_a)
self.assertIs(a2, object_a)
self.assertIsInstance(b1, list)
self.assertIsInstance(b2, list)
self.assertIsNot(b1, b2)
def test_decorate_class_method(self):
"""Test `inject()` decorator with class method."""
class Test(object):
"""Test class."""
@injections.inject(arg1=123)
@injections.inject(arg2=456)
def some_method(self, arg1, arg2):
"""Some test method."""
return arg1, arg2
test_object = Test()
arg1, arg2 = test_object.some_method()
self.assertEquals(arg1, 123)
self.assertEquals(arg2, 456)
def test_decorate_class_with_init(self):
"""Test `inject()` decorator that decorate class with __init__."""
@injections.inject(arg1=123)
@injections.inject(arg2=456)
class Test(object):
"""Test class."""
def __init__(self, arg1, arg2):
"""Init."""
self.arg1 = arg1
self.arg2 = arg2
test_object = Test()
self.assertEquals(test_object.arg1, 123)
self.assertEquals(test_object.arg2, 456)
def test_decorate_class_without_init(self):
"""Test `inject()` decorator that decorate class without __init__."""
with self.assertRaises(errors.Error):
@injections.inject(arg1=123)
class Test(object):
"""Test class."""
class InjectDeprecationTests(unittest.TestCase):
"""Deprecation of `@inject()` tests."""
def test_deprecation_warning_on_usage(self):
"""Test that DeprecationWarning is produced when `@inject` is used."""
with warnings.catch_warnings(record=True) as caught_warnings:
warnings.simplefilter('always')
@injections.inject(1)
def _example(arg):
pass
warnings.simplefilter('default')
self.assertEquals(len(caught_warnings), 1)
self.assertEquals(caught_warnings[-1].category, DeprecationWarning)
self.assertIn('Call to a deprecated decorator',
str(caught_warnings[-1].message))
self.assertIn('@dependency_injector.injections.inject',
str(caught_warnings[-1].message))