diff --git a/dependency_injector/injections.py b/dependency_injector/injections.py index 6833c41c..c6e44b60 100644 --- a/dependency_injector/injections.py +++ b/dependency_injector/injections.py @@ -77,10 +77,10 @@ class Method(NamedInjection): def inject(*args, **kwargs): """Dependency injection decorator. - :type injection: Injection :return: (callable) -> (callable) """ - injections = _parse_kwargs_injections(args, kwargs) + arg_injections = _parse_args_injections(args) + kwarg_injections = _parse_kwargs_injections(args, kwargs) def decorator(callback_or_cls): """Dependency injection decorator.""" @@ -99,17 +99,20 @@ def inject(*args, **kwargs): callback = callback_or_cls if hasattr(callback, 'injections'): - callback.injections += injections + callback.args += arg_injections + callback.kwargs += kwarg_injections + callback.injections += arg_injections + kwarg_injections return callback @six.wraps(callback) def decorated(*args, **kwargs): """Decorated with dependency injection callback.""" - return callback(*args, - **_get_injectable_kwargs(kwargs, - decorated.injections)) + return callback(*_get_injectable_args(args, decorated.args), + **_get_injectable_kwargs(kwargs, decorated.kwargs)) - decorated.injections = injections + decorated.args = arg_injections + decorated.kwargs = kwarg_injections + decorated.injections = arg_injections + kwarg_injections return decorated return decorator diff --git a/docs/advanced_usage/index.rst b/docs/advanced_usage/index.rst index bc010888..c2133e63 100644 --- a/docs/advanced_usage/index.rst +++ b/docs/advanced_usage/index.rst @@ -11,10 +11,25 @@ Current section of documentation describes advanced usage of injections. It *patches* decorated callable in such way that dependency injection will be done during every call of decorated callable. -``@di.inject()`` decorator takes keyword argument, that will be injected -during every next call of decorated callback with the same name. Any Python -object will be injected *as is*, except ``di.Provider``'s, which will be -called to provide injectable values. +``di.inject()`` takes a various number of positional and keyword arguments +that are used as decorated callable injections. Every time, when +``di.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 ``di.Provider``). Providers +will be called every time, when injection needs to be done. For example, +if injectable value of injection is a ``di.Factory``, it will provide new one +instance (as a result of its call) every time, when injection needs to be done. + +``di.inject()`` behaviour with context positional and keyword arguments is +very like a standard Python ``functools.partial``: + +- Positional context arguments will be appended after ``di.inject()`` + positional injections. +- Keyword context arguments have priority on ``di.inject()`` keyword + injections and will be merged over them. Example: diff --git a/docs/main/changelog.rst b/docs/main/changelog.rst index 3a3af526..0a6ccaf2 100644 --- a/docs/main/changelog.rst +++ b/docs/main/changelog.rst @@ -16,7 +16,8 @@ Development version - Add images for catalog "Writing catalogs" and "Operating with catalogs" examples. - Add functionality for using positional argument injections with - ``di.Factory``, ``di.Singleton`` and ``di.Callable`` providers. + ``di.Factory``, ``di.Singleton``, ``di.Callable`` providers and + ``di.inject`` decorator. - Add functionality for decorating classes with ``@di.inject``. - Add ``di.Singleton.injections`` attribute that represents a tuple of all ``di.Singleton`` injections (including args, kwargs, attributes and methods). diff --git a/examples/advanced_usage/inject_flask.py b/examples/advanced_usage/inject_flask.py index 5b1ae58f..ecb6edc9 100644 --- a/examples/advanced_usage/inject_flask.py +++ b/examples/advanced_usage/inject_flask.py @@ -5,8 +5,8 @@ import flask import dependency_injector as di -database = di.Singleton(sqlite3.Connection, - database=':memory:', +database = di.Singleton(sqlite3.connect, + ':memory:', timeout=30, detect_types=True, isolation_level='EXCLUSIVE') @@ -15,7 +15,7 @@ app = flask.Flask(__name__) @app.route('/') -@di.inject(database=database) +@di.inject(database) def hello(database): """Example Flask view.""" one = database.execute('SELECT 1').fetchone()[0] diff --git a/examples/advanced_usage/inject_simple.py b/examples/advanced_usage/inject_simple.py index 2a7fc468..dce9c9fb 100644 --- a/examples/advanced_usage/inject_simple.py +++ b/examples/advanced_usage/inject_simple.py @@ -6,11 +6,22 @@ import dependency_injector as di dependency_injector_factory = di.Factory(object) +# Example of using `di.inject()` decorator keyword argument injections: @di.inject(new_object=dependency_injector_factory) @di.inject(some_setting=1334) -def example_callback(new_object, some_setting): +def example_callback1(new_object, some_setting): """Example callback that does some asserts for input args.""" assert isinstance(new_object, object) assert some_setting == 1334 -example_callback() + +# Example of using `di.inject()` decorator with positional argument injections: +@di.inject(dependency_injector_factory, 1334) +def example_callback2(new_object, some_setting): + """Example callback that does some asserts for input args.""" + assert isinstance(new_object, object) + assert some_setting == 1334 + + +example_callback1() +example_callback2() diff --git a/tests/test_injections.py b/tests/test_injections.py index d52ebf0b..cf5dee81 100644 --- a/tests/test_injections.py +++ b/tests/test_injections.py @@ -75,8 +75,89 @@ class MethodTests(unittest.TestCase): class InjectTests(unittest.TestCase): """Inject decorator test cases.""" - def test_decorated(self): - """Test `inject()` decorated callback.""" + def test_decorated_args(self): + """Test `inject()` decoration with args.""" + provider1 = di.Factory(object) + provider2 = di.Factory(list) + + @di.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_extended_syntax(self): + """Test `inject()` decoration with args.""" + provider1 = di.Factory(object) + provider2 = di.Factory(list) + + @di.inject(di.Arg(provider1), di.Arg(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 = di.Factory(object) + provider2 = di.Factory(list) + + @di.inject(provider2) + @di.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 = di.Factory(object) + provider2 = di.Factory(list) + + @di.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 = di.Factory(object) provider2 = di.Factory(list) diff --git a/tests/test_providers.py b/tests/test_providers.py index 3eecde77..2ce35bba 100644 --- a/tests/test_providers.py +++ b/tests/test_providers.py @@ -9,12 +9,7 @@ class Example(object): def __init__(self, init_arg1=None, init_arg2=None, init_arg3=None, init_arg4=None): - """Initializer. - - :param init_arg1: - :param init_arg2: - :return: - """ + """Initializer.""" self.init_arg1 = init_arg1 self.init_arg2 = init_arg2 self.init_arg3 = init_arg3