Tidi
A small dependency injection Python library.
Inspired by Kent Tong's
disl and
FastAPI's Depends
.
Motivation
I found myself wanting to learn more about how dependency injection can be done
in a pythonic way, with type-hinting, so had the itch to develop (yet another)
library for it and share it as open source 🧑💻✌️
Read more about the motivation in this Medium article.
Primary API
The top level import of tidi
provides everything needed it's primary intended
use.
@tidi.inject
- a decorator that will replace certain keyword arguments
with dependencies, based on their type & if they haven't been passed intidi.Injected[DependencyClass]
- a type alias, wrapping typing.Annotated,
that indicates that a keyword argument should be injectedtidi.register(dependency_instance)
- a function that registers an object to
be available for injection as a dependencytidi.Provider(get_dependency_function)
- a wrapper class around a function
that will be called to provide a dependencytidi.UNSET
- a sentinel object to indicate that a dependency should be
loaded from the registrytidi.field_factory(DependencyClass)
- a helper function for injecting
dependencies into dataclass fields
Example of use
Consider a micro-sized interactive CLI that lets a user choose a handbag then
search through it,
import tidi
from handbags import Handbag, HandbagItem, load_handbag
@tidi.inject
def dig_through_handbag(
item_type: str,
handbag: tidi.Injected[Handbag] = tidi.UNSET,
) -> HandbagItem | None:
return handbag.get_items_by_type(item_type).first(default=None)
def init_handbag():
selected_handbag = input("Select a handbag: ")
tidi.register(load_handbag(selected_handbag))
def run_search():
item_type = input("What are you looking for? ")
item = dig_through_handbag(item_type)
if item is None:
print("Uh oh, can't find it 🤷♀️, try again")
run_search()
else:
print(f"We're in luck! Here's your {item.name} 😎")
if __name__ == "__main__":
init_handbag()
run_search()
Running it looks something like this,
$ python search-handbag
Select a handbag: BCBGMAXAZRIA
What are you looking for? Nail file
Uh oh, can't find it 🤷♀️, try again
What are you looking for? Lip balm
We're in luck! Here's your Blistex 😎
We can see Dependency Injection happening hear to achieve Inversion of Control
and obey the Law of Demeter.
dig_through_handbag
isn't responsible for creating a Handbag
and doesn't
require its caller to know about it, rather a Handbag
is injected ✨init_handbag
creates the Handbag
, but doesn't need to return it. An
example of separating the app initialisation from the main logic.run_search
doesn't need to know about anything that it doesn't use (in this
case the Handbag
), obeying the Law of Demeter.
When testing,
- a mock
Handbag
could be passed in as a keyword argument to test
dig_through_handbag
, and - patching
dig_through_handbag
with a stub could be done to test run_search
with no requirement for a mock Handbag
.
More examples
You can find some executable examples in the demo/
directory of the repo.
Also see the Usage documentation for
more examples.
Interested in contributing?
Feel free to create an issue or author a PR 😊
For the latter, check out the CONTRIBUTING guide for a quick
start on development.