Skip to main content

Minimalistic Dependency Injection for Python

Project description

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

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

dimi-1.3.0.tar.gz (18.0 kB view details)

Uploaded Source

Built Distribution

dimi-1.3.0-py3-none-any.whl (11.9 kB view details)

Uploaded Python 3

File details

Details for the file dimi-1.3.0.tar.gz.

File metadata

  • Download URL: dimi-1.3.0.tar.gz
  • Upload date:
  • Size: 18.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/5.1.1 CPython/3.12.7

File hashes

Hashes for dimi-1.3.0.tar.gz
Algorithm Hash digest
SHA256 a88f6baf14f7e5f58c21b085c06366c3fb23135c41039a873d0b5548671ca49b
MD5 fde8fb01cf7883757a099b95bae92dd9
BLAKE2b-256 465eafc2d1107d655da735086b313226e7cde1b237b08c5cc0b0fc0b2e31c984

See more details on using hashes here.

File details

Details for the file dimi-1.3.0-py3-none-any.whl.

File metadata

  • Download URL: dimi-1.3.0-py3-none-any.whl
  • Upload date:
  • Size: 11.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/5.1.1 CPython/3.12.7

File hashes

Hashes for dimi-1.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 aee127690295090197650b15cb921cb617ea23fea4d4610ea327d2642dabfaec
MD5 b178a54ca9fe934563dd12b642b533ab
BLAKE2b-256 90bff16e8416e3de529a31143b1ea8c9a2130738541219bb1d565b1f8ba5665a

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