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.

Decorator pattern using nested Injector - cannot achieve shared instance between nested scopes

See original GitHub issue

Hello, I’m trying to have a shared instance of an object between a Decorator and it’s decoree - real world example would be to have a Transaction object in a Repository and f.e. TransactionalDecorator which would open/close that transaction. @proofit404 in one of the issues you’ve shown how decorators could be built using nested Injectors, the problem however is, that you’ve used objects in it (not classes or @value - so no factory providers). I’m struggling to achieve a shared instance with classes.

Minimum viable example (note the comments):

from dependencies import Injector, this


class Connection:
    pass


class Transaction:
    def __init__(self, connection):
        self.connection = connection


class Repository:
    def __init__(self, transaction):
        self.transaction = transaction


class Handler:
    def __init__(self, repository, transaction):
        self.repository = repository
        self.transaction = transaction


class Decorator:
    def __init__(self, decoree, transaction):
        self.decoree = decoree
        self.transaction = transaction

# pretty much resembles my usecase - `transaction` shall be shared between Decorator && Handler && Handler.repository
class DecoratorIoC(Injector):
    handler = this.DecoratedHandler.handler
    transaction = Transaction
    repository = Repository
    connection = Connection()   # on purpose already an instance (in my usecase provided from outer-scope by Injector(**kwargs))

    class DecoratedHandler(Injector):
        handler = Decorator
        decoree = this.Handler.handler
        transaction = (this << 1).transaction

        class Handler(Injector):
            handler = Handler
            repository = (this << 2).repository
            transaction = (this << 1).transaction


decorator_handler = DecoratorIoC.handler
try:
    assert decorator_handler.transaction is decorator_handler.decoree.transaction is decorator_handler.decoree.repository.transaction
except AssertionError:
    pass

# a different approach, where dependencies are provided from the nested scope to the outer Injector instances
class NoOuterScopeDecoratorIoC(Injector):
    handler = this.DecoratedHandler.handler
    connection = Connection()  # on purpose already an instance

    class DecoratedHandler(Injector):
        handler = Decorator
        decoree = this.Handler.handler
        transaction = this.Handler.transaction

        class Handler(Injector):
            handler = Handler
            repository = Repository
            transaction = Transaction
            connection = (this << 2).connection


decorator_handler = NoOuterScopeDecoratorIoC.handler
try:
    assert decorator_handler.transaction is decorator_handler.decoree.transaction is decorator_handler.decoree.repository.transaction
except AssertionError:
    pass

# simplified example - just to show that one level of nesting is enough to not be able to achieve the expected (at least by me) behavior
class IoCSimple(Injector):
    handler = this.Handler.handler
    transaction = Transaction
    repository = Repository
    connection = Connection

    class Handler(Injector):
        handler = Handler
        repository = (this << 1).repository
        transaction = (this << 1).transaction


handler = IoCSimple.handler
try:
    assert handler.transaction is handler.repository.transaction  # throws
except AssertionError:
    pass

# example just to show that without nesting it works
class DummyThis(Injector):
    handler = Handler
    connection = Connection
    repository = Repository
    transaction = this.transaction_aliased
    transaction_aliased = Transaction


dummy_this = DummyThis.handler
assert dummy_this.transaction is dummy_this.repository.transaction

in before - I cannot use caching or singletons, because each built Handler should have its own Transaction object.

Any thoughts? Thanks in advance, and thanks for all your work @proofit404 !

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Comments:9 (7 by maintainers)

github_iconTop GitHub Comments

1reaction
proofit404commented, Jun 21, 2021

The problem here is that each nested injector would create an independent injector subclass. Each time dependency resolver tries to access it’s attribute it will create an independent State object.

1reaction
proofit404commented, Jun 21, 2021

That’s a bug, can confirm.

Thanks for the report!

Fix on the way 🎉

Have a good day 🍸 🌴

Read more comments on GitHub >

github_iconTop Results From Across the Web

Classes can't depend on nested injectors. · Issue #479 · proofit404 ...
Use this objects to point to specific dependencies inside nested injectors. ... Decorator pattern using nested Injector - cannot achieve shared instance ......
Read more >
Dependency injection guidelines - .NET | Microsoft Learn
Learn various dependency injection guidelines and best practices for .NET application development.
Read more >
Injection scopes | NestJS - A progressive Node.js framework
Hint Using singleton scope is recommended for most use cases. Sharing providers across consumers and across requests means that an instance can be...
Read more >
Hierarchical injectors - Angular
Hierarchical dependency injection enables you to share dependencies between different parts of the application only when and if you need to.
Read more >
c# - .NET Core IServiceScopeFactory.CreateScope() vs ...
In my case, the injection is for a IHostedService/BackgroundService which requires it's own scope separate from ASP.NET default. It would be ...
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