Skip to content

Special Dependency Types

Clean_ioc allows you to inject special dependencies into your objects that give you access to the current dependency graph being resolved. This is a powerful feature that enables you to create objects based on the parent dependency or search the current resolved objects. When using these feature it is strongly advised to make sure you do not hold reference to any of these objects after the resolving has completed, this basically means if tou use the objects in a constructor or factory make sure they aren't stored in the returned object.

Dependency Context

The DependencyContext type allows you to access the the parent type of the caller via the dependency graph. One example of where this becomes useful is when injecting something like a logger, you can get information about the loggers parent to add extra context

class Client:
    def __init__(self, logger: logging.Logger):
        self.logger = logger

    def do_a_thing(self):
        self.logger.info('Doing a thing')

def logger_fac(context: DependencyContext):
    module = context.parent.implementation.__module__
    return logging.getLogger(module)


container = Container()
container.register(Client)
container.register(logging.Logger, factory=logger_fac, lifespan=Lifespan.transient)
client = container.resolve(Client)

Using DependencyContext

If using DependencyContext on your dependency you should use a lifespan of transient, because any other lifespan will create only use the parent of the first resolved instance. Also when using the dependency context make sure don't add it the objects state, discard if it within the factory or constructor.

Current Graph

The CurrentGraph object allows you resolve from the current dependency graph being constructed. This is useful if you want to make sure that two different registered objects can be forced to be the same object, this is handy when doing interface segregation. You can use the inbuilt use_from_current_graph or use_from_current_graph_async factories to do this. These functions are defined in clean_ioc.factories

class Sender(Protocol):
    def send(self, message): ...


class BatchSender(Protocol):
    def send_batch(self, messages): ...

class MySender(Sender, BatchSender):
    def send_batch(self, messages):
        # SEND MANY THINGS
        pass

    def send(self, message):
        # SEND SOMETHING
        pass

class Client:
    def __init__(self, sender: Sender, batch_sender: BatchSender):
        self.sender = sender
        self.batch_sender = batch_sender

container = Container()
container.register(Sender, MySender)
container.register(BatchSender, factory=use_from_current_graph(MySender))
container.register(Client)


client = container.resolve(Client)

client.sender is client.batch_sender # True

Order does not matter

In the above scenario the order of resolving Sender and BatchSender does not matter. It will work the same regardless of which one is resolved first

Using CurrentGraph

If using CurrentGraph on your dependency you should use a lifespan of transient, because any other lifespan will create only use the parent of the first resolved instance. Also when using the CurrentGraph make sure don't add it the objects state, only use within the factory or constructor of your object.