Skip to main content

dimi - Minimalistic Dependency Injection for Python

CI Coverage Python version

“Dependency Injection” is a 25-dollar term for a 5-cent concept.

James Shore

Tired of complex abstractions? Tired of things like "to do dependency injection let's first dive into providers, scopes and binders"?

Just want dead simple syntax for injecting dependencies and mocking them inside tests?

Say no more!

Installation

pip install dimi

Getting Started

from dimi import Container
from typing import Annotated
from flask import Flask, jsonify

# DI container instance stores all the dependencies you place inside
di = Container()


# dependency is a function, async function or a class
# it can be added to the container via the following decorator:

@di.dependency
def get_weather_service_url():
    return 'https://awesome-weather-provider.com/api'


# Dependency may have sub-dependencies defined via typing.Annotated
# For dimi mostly the second part of Annotated matters, the first part is for type checkers

@di.dependency
class WeatherService:
    def __init__(self, url: Annotated[str, get_weather_service_url])
        self.url = url

    def get_weather(self, city): ...


app = Flask(__name__)

# Annotated[MyCls, ...] is the shortcut for Annotated[MyCls, MyCls]

@app.route("/api/weather/<city>")
@di.inject
def get_weather(city: str, weather_service: Annotated[WeatherService, ...]):
    return jsonify(weather_service.get_weather())

The DI container also supports dict-like way of retrieving the dependencies:

weather_service = di[WeatherService]
print(weather_service.get_weather('london'))

OK, but where is the profit?

Someone may ask, "Why not just do this instead?"

# the same API handle but w/o DI

@app.route("/api/weather/<city>")
def get_weather(city: str):
    return jsonify(
        WeatherService('https://awesome-weather-provider.com/api').get_weather(city)
    )

The simplest argument for DI is easier testing.

How would you test the non-DI API handle above? I guess something nasty with monkey patches.

But with DI in action everything becomes much simpler and more obvious:

from myapp import di  # app-wide instance of the Container
from myapp.services import WeatherService


def test_weather_api_handle(test_client):
    class MockWeatherService:
        def get_weather(self, city):
            return {'temperature': 30.5, 'wind_speed': 3.1, 'city': city}

    # override() preserves and restores DI container state after exit from context manager
    with di.override({WeatherService: MockWeatherService}):
        weather = test_client.get('/api/weather/london').json()
        assert weather == {'temperature': 30.5, 'wind_speed': 3.1, 'city': 'london'}

Key Benefits

  • Simple and concise API:
    • Ask for dependencies via Annotated[SomeType, some_callable] type annotation
    • Add callables to the DI container via @di.dependency
    • Do injections via @di.inject
    • override DI contents via di.override()
  • Explicit dependencies wiring. No magic, no complicated or hidden rules. You define each (sub-)dependency explicitly inside Annotated[]
  • Auto sub-dependencies resolving. A may depend on B which may depend on C which may depend on D and E and so on. All of this will be correctly tied together and resolved at the time of a function call.
  • Optional scopes. Just use @di.dependency(scope=Singleton) to cache first call of a function for the lifetime of the app.
  • Async support
  • Thread safety

Docs

Want to know more? Welcome to the docs

Metadata

Release files for dimi 1.5.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for dimi 1.5.0
File Size Uploaded
dimi-1.5.0.tar.gz 19.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for dimi 1.5.0
File Interpreter ABI Platform
dimi-1.5.0-py3-none-any.whl Python 3 none any Details

Total release size: 32.1 kB

Release files / dimi-1.5.0.tar.gz

Download URL dimi-1.5.0.tar.gz
Size 19.0 kB
Tags Source
SHA-256 checksum
How to use checksums
789f72d601b1dda0aa275439fbfecf3ef27da2251807a1f28202baddb2bf0d3c
BLAKE2b-256 checksum
How to use checksums
c8ffbebe30d1c28d8b38d21ec64745799ab6ff2c90ab943be1258c3b7b13e74c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.12

Release files / dimi-1.5.0-py3-none-any.whl

Download URL dimi-1.5.0-py3-none-any.whl
Size 13.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
aa9f55bd6ef14e8eba5feab926bea593b33cef44835da80e72a4fcf8c561b2ec
BLAKE2b-256 checksum
How to use checksums
8593259ca2d0821a4daf29bc34016707e745ac3bdf7721b9b73324a540b58984
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.12

Release history Release notifications | RSS feed

This release

1.5.0 This release

2 release files

1.4.0

2 release files

1.3.0

2 release files

1.2.0

2 release files

1.1.3

2 release files

1.1.2

2 release files

1.1.1

2 release files

1.1.0

2 release files

1.0.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page