Skip to content

Usage

There are a few main ways it's intended to be used.

Examples of usage

Inject a dependency that has been put in a registry

import tidi

@tidi.inject
def run(auth: tidi.Injected[AuthService] = tidi.UNSET):
    if auth.is_super_user():
        logger.info(f"Running with elevated permissions.")
    ...

if __name__ == "__main__":
    tidi.register(AuthService())

    ...

    run()  # 🪄 `AuthService` injected into `run` ✨

Objects can be registered & retrieved via their abstractions

import tidi

@tidi.inject
def watch(content: tidi.Injected[MediaABC] = tidi.UNSET):
    content.play()  # `MediaABC` interface used by type checkers 🤝
    ...

if __name__ == "__main__":
    # register a `Film` object to be accessed as an abstract `MediaABC` type
    django_unchained = Film(isan="0000-0003-6795-0000-O-0000-0000-2")
    tidi.register(django_unchained, type_=MediaABC)

    ...

    watch()  # 🪄 `django_unchained` injected into `watch` ✨

Inject a dependency provided by a function

import tidi

def get_secret() -> str:
    return "wshwshwhswhs"

@tidi.inject
def run(secret: tidi.Injected[str] = tidi.Provider(get_secret)):
    logger.info(f"don't tell anyone this but, {secret}")

if __name__ == "__main__":
    run()  # 🪄 result from `get_secret()` injected into `run` ✨

Inject a dependency provided by a context manager

import contextlib
import typing as t

import tidi

@contextlib.contextmanager
def open_sesame() -> t.Iterator[str]:
    logger.info("abracadabra")
    try:
        yield "please"
    finally:
        logger.info("alakazam")

@tidi.inject
def run(magic_word: tidi.Injected[str] = tidi.Provider(open_sesame)):
    logger.info(f"the magic word is {magic_word}")

if __name__ == "__main__":
    run()  # 🪄 result from `open_sesame()` injected into `run` ✨

Or provided by instantiating a class

import tidi

class SnackRepo():
    def __init__(self, db_string = "fridge"):
        self.db_string = db_string

    def get_snack(self):
        return f"snack, out of the {self.db_string}"

@tidi.inject
def eat_snack(repo: tidi.Injected[SnackRepo] = tidi.Provider(SnackRepo)):
    logger.info(f"eating {repo.get_snack()}")

if __name__ == "__main__":
    eat_snack()  # 🪄 a new `SnackRepo` injected into `run` ✨

The injection also works with classes, either into the constructor

import tidi

@tidi.inject
class Algorithm:
    def __init__(self, config: tidi.Injected[Config] = tidi.UNSET):
        self.tolerance = config.tolerance

    def run(self, input_value: float) -> float:
        return input_value - input_value * self.tolerance

if __name__ == "__main__":
    tidi.register(Config(tolerance=0.1))
    alg = Algorithm()  # 🪄 `Config` injected into new `Algorithm` instance ✨
    alg.run(10.0)

Or into their methods

import tidi

class Job:
    def __init__(self, name):
        self.name = name

    @tidi.inject
    def run(self, db: tidi.Injected[DBConnection] = tidi.Provider(get_db_conn)):
        logger.info(f"run job {self.name} using database {db.name}")

if __name__ == "__main__":
    job = Job("new job")
    job.run()  # 🪄 `DBConnection` injected into `job.run` ✨

And to inject dependencies into a dataclass, use the field_factory

from dataclasses import dataclass, field

import tidi

@dataclass
class Output:
    value: float
    created_by: User = field(default_factory=tidi.field_factory(User))

def process_next_job():
    return Output(value=1)  # 🪄 `User` injected into `Output.created_by` ✨

if __name__ == "__main__":
    current_user = User.from_env_credentials()
    tidi.register(current_user)
    output = process_next_job()