Skip to main content

A small dependency injection Python library

Project description

Tidi

Test Package version Documentation Supported Python versions

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 in
  • tidi.Injected[DependencyClass] - a type alias, wrapping typing.Annotated, that indicates that a keyword argument should be injected
  • tidi.register(dependency_instance) - a function that registers an object to be available for injection as a dependency
  • tidi.Provider(get_dependency_function) - a wrapper class around a function that will be called to provide a dependency
  • tidi.UNSET - a sentinel object to indicate that a dependency should be loaded from the registry
  • tidi.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,

# search-handbag.py

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? ")
    # ⬇️ registered `Handbag` instance gets injected ✨
    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.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

tidi-0.3.0.tar.gz (9.9 kB view details)

Uploaded Source

Built Distribution

tidi-0.3.0-py3-none-any.whl (9.8 kB view details)

Uploaded Python 3

File details

Details for the file tidi-0.3.0.tar.gz.

File metadata

  • Download URL: tidi-0.3.0.tar.gz
  • Upload date:
  • Size: 9.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/5.1.0 CPython/3.9.19

File hashes

Hashes for tidi-0.3.0.tar.gz
Algorithm Hash digest
SHA256 2c5e0d8d556a6a7ec4e1df318b44ef1afcf8f5d77a11593796704d85e2ee43a6
MD5 410f60512fa6300ce2a75ce0214b39fb
BLAKE2b-256 582655ab377b5ba341aee81c3ec68a3528b7932fbdd3ec82cb0e1ee4dc81b975

See more details on using hashes here.

File details

Details for the file tidi-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: tidi-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 9.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/5.1.0 CPython/3.9.19

File hashes

Hashes for tidi-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 ba7f023b03bc491a5e2b49ff7eef743755f04b420a74f171e7636c9ee21a043f
MD5 983ff6292f4da5ce3968bfe18cd4b483
BLAKE2b-256 b09963166293a0a55c82b9572cc0e8d9039d012796de095dc979600f8374e584

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page