Skip to content

Dependency Injection

Inject Dependencies in your services.

Dependency Injection (DI) is a design pattern in programming that allows components or functions to declare their dependencies rather than creating them internally. This makes the code more modular, testable, and maintainable.

This framework provides a simple but powerful dependency injection system inspired by the used in the FastAPI library. It works with function-based dependency resolution and handles both standard and generator-based dependencies. It also supports overriding dependencies, allowing you to define your logic using interfaces and override them with concrete implementations later in the code.

Example:

# src/services/training.py
from torch import cuda
from torchsystem import Depends
from torchsystem.services import Service
from sqlalchemyl.orm import Session

service = Service() # You will know more about services in the next sections.
                    # For now, just think of it as a decorator that allows you
                    # to inject dependencies in your functions.

def device() -> str:
    return 'cuda' if cuda.is_available() else 'cpu'

def trainer(): 
    raise NotImplementedError('Trainer not implemented')

def db_session() -> Session:
    ...

@service.hander
def train(model, trainer=Depends(trainer), device=Depends(device), db=Depends(db_session)):
    ...

In the example above, the train function has three dependencies: trainer, device, and db. The Depends class is used to declare these dependencies. When the train function is called, the dependencies are resolved and passed to the function automatically. As you may have noticed, the last two dependencies are not implemented, they are dependencies that can be overrided when plugging the infrastructure in the application layer.

# src/app.py
from src.services import training

def trainer():
    return MaybeLightningTrainer()# Maybe you want to use PyTorch Lightning, all it's up to you.

def db_session():
    session = sessionmaker()
    session.begin()
    try:
        yield session
    finally:
        session.close() # Clean up resources

training.service.dependency_overrides[training.trainer] = trainer
training.service.dependency_overrides[training.db_session] = db_session

In the example above, we override the trainer and db_session dependencies with concrete implementations. Note that the db_session dependency is a generator-based dependency, which allows you to clean up resources after the dependency is used. There are a lot of situations where you need to clean up resources after using a dependency, for example when working with distributed training, databases, filesystems, tensorboard, etc.

You will see more examples of dependency injection in the next sections, dependency injection is a core concept in this framework and is used extensively in the compiler ,services, consumers or subscribers.