Skip to main content

Dead simple dependency injection for python.

Project description

diny

PyPI

Dead simple dependency injection for Python.

pip install diny

Works with your existing code

Drop in @singleton / @inject and delete your glue code:

+from diny import inject, singleton
+
+@singleton
 class Config:
     def __init__(self):
         self.url = "postgres://localhost"

+@singleton
 class Database:
     def __init__(self, config: Config):
         self.conn = connect(config.url)

+@inject
 def list_users(db: Database):
     return db.query("SELECT * FROM users")

-# Lots of lines of glue code
-config = ...
-db = ...
-
-list_users(db)
+list_users()   # Config and Database built on first call, cached after

The cache is process-wide until you scope it.

Decorators

@singleton caches one instance per scope. @factory builds a fresh one at each site. @inject resolves a function's typed deps on call.

from diny import inject, singleton, factory
from uuid import uuid4

@singleton
class Database:
    def __init__(self, config: Config): ...

@factory
class RequestId:
    def __init__(self): self.value = uuid4()

@inject
def handler(db: Database, req: RequestId):
    # db is cached across calls; req is fresh every time
    ...

Plain params pass through untouched:

@inject
def get_user(user_id: int, db: Database):
    return db.query("SELECT * FROM users WHERE id = %s", user_id)

get_user(42)

Custom providers

For types you don't own or that need custom construction, use @provider(Type):

from diny import provider

@provider(Database)
def make_db(config: Config):
    return PostgresDB(config.url, pool_size=10)

# Now Database is auto-injected using make_db — no provide() needed
list_users()

The provider function's own typed parameters are injected too. Scope is determined by the call site (Singleton[T] / Factory[T]), defaulting to singleton. provide() overrides @provider within its scope.

Annotations

For classes you don't own — or to override a class's default scope at one call site — use Singleton[T] / Factory[T]:

from diny import Singleton, Factory

@inject
def handler(
    client: Singleton[ThirdPartyClient],   # cached
    id_a:   Factory[RequestId],            # fresh
    id_b:   Factory[RequestId],            # fresh, different instance
):
    ...

Site annotations beat class decorators — Singleton[RequestId] forces singleton even if RequestId is @factory.

Undecorated classes without a site annotation are passed through to the caller. Nothing is auto-injected behind your back.

Scoped overrides

Open a scope with provide() to override any dependency — classes, instances, or functions. This overrides both @singleton/@factory and @provider registrations within the scope:

from diny import provide

class FakeDatabase(Database):
    def __init__(self, config: Config):
        self.fake = True

with provide(Database=FakeDatabase):
    list_users()                     # uses FakeDatabase

with provide(Config(url="test://")):
    list_users()                     # uses this Config instance

Scopes nest. Use inherit=True to keep the parent's cached instances while overriding specific deps:

with provide(Config(url="prod")):
    handler()                          # prod config, fresh database

    with provide(Database=AdminDB, inherit=True):
        handler()                      # admin database, same config instance

    with provide(Config(url="test")):
        handler()                      # test config, fresh everything

Async

from diny import inject, aprovide

@inject
async def handler(db: Database):
    return await db.fetch_all()

async def main():
    async with aprovide():
        await handler()

Async provider functions work inside aprovide:

async def make_pool(config: Config):
    return await asyncpg.create_pool(config.url)

async with aprovide(Pool=make_pool):
    await handler()

Project details


Download files

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

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

diny-0.3.0-py3-none-any.whl (7.2 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: diny-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 7.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.8 {"installer":{"name":"uv","version":"0.11.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for diny-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 020a99587ed6b5f2c1640209eb8bbe464f2fc5789aa8572897acc3d2aee58baa
MD5 19eb85d722c27904c7a0c1f823c767d8
BLAKE2b-256 102fa9151f21ef136d70e489bc39d538fe1d3288ee2ee0ee7df642c8197492ad

See more details on using hashes here.

Supported by

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