question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Injection on a decorator

See original GitHub issue

Hi, I really like this package and am using it in production. I was tying to mess around with decorators because I want to add side effects to a function, adding a secret_number in the example. I was expecting decorated_function_1 to work but it didn’t and I can’t wrap my head around it. Is this an expected behavior? Could injection be supported like in my_decorator_1?

I also added few examples of things I tried and only decorated_function_4 actually works.

from functools import wraps

from dependency_injector import containers, providers
from dependency_injector.wiring import Provide, inject


class Container(containers.DeclarativeContainer):
    config = providers.Configuration()


@inject
def my_decorator_1(func, secret_number: int = Provide[Container.config.secret_number]):
    @wraps(func)
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        return result + secret_number
    return wrapper

@inject
def my_decorator_2(secret_number: int = Provide[Container.config.secret_number]):
    def inner_decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            result = func(*args, **kwargs)
            return result + secret_number
        return wrapper
    return inner_decorator

def my_decorator_3():
    @inject
    def inner_decorator(func, secret_number: int = Provide[Container.config.secret_number]):
        @wraps(func)
        def wrapper(*args, **kwargs):
            result = func(*args, **kwargs)
            return result + secret_number
        return wrapper
    return inner_decorator

def my_decorator_4(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        secret_number = kwargs['secret_number']
        return result + secret_number
    return wrapper


@my_decorator_1
def decorated_function_1():
    return 42

@inject
@my_decorator_1
def decorated_function_1a():
    return 42

@my_decorator_2()
def decorated_function_2():
    return 42

@my_decorator_3()
def decorated_function_3():
    return 42

@inject
@my_decorator_3()
def decorated_function_3a():
    return 42

@inject
@my_decorator_4
def decorated_function_4(secret_number: int = Provide[Container.config.secret_number]):
    return 42

@my_decorator_4
@inject
def decorated_function_4a(secret_number: int = Provide[Container.config.secret_number]):
    return 42


def main():
    test_funcs = [
        decorated_function_1,
        decorated_function_1a,
        decorated_function_2, 
        decorated_function_3, 
        decorated_function_3a,
        decorated_function_4, 
        decorated_function_4a, 
    ]
    for test_f in test_funcs:
        try:
            result = test_f()
            print(f"Function {test_f} returned {result}")
        except Exception as exc:
            print(f"Function {test_f} raised {exc.__class__.__name__} '{exc}'")

if __name__ == '__main__':
    import sys

    container = Container()
    container.init_resources()
    container.config.secret_number.from_env("SECRET_INT", 24)
    container.wire(modules=[sys.modules[__name__]])

    main()

The output is:

Function <function decorated_function_1 at 0x7f93d8a4d310> raised TypeError 'unsupported operand type(s) for +: 'int' and 'Provide''
Function <function decorated_function_1a at 0x7f93d8a4d4c0> raised TypeError 'unsupported operand type(s) for +: 'int' and 'Provide''
Function <function decorated_function_2 at 0x7f93d8a4d670> raised TypeError 'unsupported operand type(s) for +: 'int' and 'Provide''
Function <function decorated_function_3 at 0x7f93d8a4d820> raised TypeError 'unsupported operand type(s) for +: 'int' and 'Provide''
Function <function decorated_function_3a at 0x7f93d8a4daf0> raised TypeError 'unsupported operand type(s) for +: 'int' and 'Provide''
Function <function decorated_function_4 at 0x7f93d8a4dca0> returned 66
Function <function decorated_function_4a at 0x7f93d8a4de50> raised KeyError ''secret_number''

Issue Analytics

  • State:open
  • Created 2 years ago
  • Reactions:3
  • Comments:9 (6 by maintainers)

github_iconTop GitHub Comments

3reactions
fabiocerqueiracommented, Jan 29, 2022
from functools import wraps

from dependency_injector import containers, providers
from dependency_injector.wiring import Provide, inject


class Reporter:
    def info(self, msg):
        print(f"*** [INFO] {msg}")


class Container(containers.DeclarativeContainer):
    conf = providers.Configuration()
    reporter = providers.Singleton(Reporter)


def tagger(tag):
    def inner(func):
        # @wraps(func)  # <-- using wraps break inject
        @inject
        def tagger_wrapper(text, sep=Provide[Container.conf.sep], **kwargs):
            result = func(text, **kwargs)
            return f"{result}{sep}({tag}[{func.__name__}])"

        return tagger_wrapper

    return inner


def logger(prefix):
    def inner(func):
        # @wraps(func)  # <-- using wraps break inject
        @inject
        def logger_wrapper(text, reporter=Provide[Container.reporter], **kwargs):
            result = func(text, **kwargs)
            reporter.info(f"{prefix} - {func.__name__}('{text}') was called")
            return result

        return logger_wrapper

    return inner


def helper(tag, prefix):
    def inner(func):
        @tagger(tag)
        @wraps(func)  # <-- if remove this double wraps it breaks the function name
        @logger(prefix)
        @inject
        @wraps(func)
        def helper_wrapper(text, **kwargs):
            result = func(text, **kwargs)
            return result

        return helper_wrapper

    return inner


@helper("my_tag", "|my_prefix|")
def my_example(text):
    return text


if __name__ == "__main__":
    container = Container()
    container.wire(modules=[__name__])
    container.conf.from_dict({"sep": " / "})
    print(my_example("hi!"))

Running the example above we get the expected behaviour

*** [INFO] |my_prefix| - my_example('hi!') was called
hi! / (my_tag[my_example])

If you try to remove this double @wraps call in helper the function name breaks:

*** [INFO] |my_prefix| - my_example('hi!') was called
hi! / (my_tag[logger_wrapper])
              ^^^^^^^^^^^^^^^^

if I move the @wraps to the decorator(this I think should be the correct way) it breaks @inject

Traceback (most recent call last):
  File "example.py", line 69, in <module>
    print(my_example("hi!"))
  File "/home/fabio/.pyenv/versions/.../dependency_injector/wiring.py", line 612, in _patched
    result = fn(*args, **to_inject)
  File "example.py", line 22, in tagger_wrapper
    result = func(text, **kwargs)
  File "/home/fabio/.pyenv/versions/.../dependency_injector/wiring.py", line 612, in _patched
    result = fn(*args, **to_inject)
  File "example.py", line 36, in logger_wrapper
    reporter.info(f"{prefix} - {func.__name__}('{text}') was called")
AttributeError: 'Provide' object has no attribute 'info'
1reaction
rmk135commented, Jul 24, 2022

I have the fix! Working on merging it to develop.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Dependency Injection using Decorators | by Chidume Nnamdi
Injection is the passing of a dependency to a dependent object. ... These decorator functions (@Inject, @Injectable) uses the Reflect library to add ......
Read more >
Angular & Dependency Injection: tricks with Decorators
Angular & Dependency Injection: tricks with Decorators ... The Self Decorator limits the search for the dependency to the current injector.
Read more >
Dependency Injection Decorators | LoopBack Documentation
The @inject decorator allows you to inject dependencies bound to any implementation of the Context object, such as an Application instance or a...
Read more >
TypeScript class decorators incl. Dependency Injection example
Use a parameter decorator to mark the classes to be injected (here: @inject() ; commonly done in the constructor of that class, called ......
Read more >
Angular | Inject service into decorator - Stack Overflow
Decorators are a TypeScript feature that work outside of Angular's dependency injection system. The only solution that I know of is to ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found