Skip to main content

Async dependency injection library

Project description

Async-first dependency injection library based on python type hints

Framework integrations:

Installation

Install using pip pip install aioinject

Example

import aioinject


class Database:
    def __init__(self) -> None:
        self._storage = {1: "Username"}

    def get(self, id: int) -> str | None:
        return self._storage.get(id)


class UserService:
    def __init__(self, database: Database) -> None:
        self._database = database

    def get(self, id: int) -> str:
        user = self._database.get(id)
        if user is None:
            raise ValueError
        return user


container = aioinject.Container()
container.register(aioinject.Singleton(Database))
container.register(aioinject.Singleton(UserService))


def main() -> None:
    with container.sync_context() as ctx:
        service = ctx.resolve(UserService)
        user = service.get(1)
        assert user == "Username"
        print(user)


if __name__ == "__main__":
    main()

Injecting dependencies into a function

You can inject dependencies into a function using @inject decorator, but that's usually only necessary if you're working with a framework:

import contextlib
from collections.abc import AsyncIterator
from contextlib import aclosing
from typing import Annotated

import uvicorn
from fastapi import FastAPI

from aioinject import Container, Inject, Singleton
from aioinject.ext.fastapi import AioInjectMiddleware, inject


class Service:
    pass


container = Container()
container.register(Singleton(Service))


@contextlib.asynccontextmanager
async def lifespan(_: FastAPI) -> AsyncIterator[None]:
    async with aclosing(container):
        yield


app = FastAPI(lifespan=lifespan)

app.add_middleware(AioInjectMiddleware, container=container)


@app.get("/")
@inject
async def root(service: Annotated[Service, Inject]) -> str:
    return str(service)


if __name__ == "__main__":
    uvicorn.run("main:app")

Using multiple dependencies with same type

If you have multiple implementations for the same dependency you can use typing.NewType to differentiate between them:

import dataclasses
from typing import Annotated, NewType

from aioinject import Container, providers, inject, Inject


@dataclasses.dataclass
class Client:
    name: str


GitHubClient = NewType("GitHubClient", Client)

GitLabClient = NewType("GitLabClient", Client)


def get_github_client() -> GitHubClient:
    return GitHubClient(Client(name="GitHub Client"))


def get_gitlab_client() -> GitLabClient:
    return GitLabClient(Client(name="GitLab Client"))


container = Container()
container.register(providers.Scoped(get_github_client))
container.register(providers.Scoped(get_gitlab_client))

with container.sync_context() as ctx:
    github_client = ctx.resolve(GitHubClient)
    gitlab_client = ctx.resolve(GitLabClient)

    print(github_client, gitlab_client)

Working with Resources

Often you need to initialize and close a resource (file, database connection, etc...), you can do that by using a contextlib.(async)contextmanager that would return your resource. Aioinject would automatically close them when context is closed, or when you call container.aclose() if your dependency is a Singleton.

import contextlib

from aioinject import Container, providers


class Session:
    pass


@contextlib.contextmanager
def get_session() -> Session:
    print("Startup")
    yield Session()
    print("Shutdown")


container = Container()
container.register(providers.Scoped(get_session))

with container.sync_context() as ctx:
    session = ctx.resolve(Session)  # Startup
    session = ctx.resolve(Session)  # Nothing is printed, Session is cached
# Shutdown

Async Dependencies

You can register async resolvers the same way as you do with other dependencies, all you need to change is to use Container.context instead of Container.sync_context:

import asyncio

from aioinject import Container, providers


class Service:
    pass


async def get_service() -> Service:
    await asyncio.sleep(1)
    return Service()


async def main() -> None:
    container = Container()
    container.register(providers.Scoped(get_service))

    async with container.context() as ctx:
        service = await ctx.resolve(Service)
        print(service)


if __name__ == "__main__":
    asyncio.run(main())

Providers

Scoped

Objects provided with Scoped provider would be cached within context.

Transient

Transient provider would provide different instances even if used within same context

Singleton

Singleton works as you expect - there would be only one instance of a singleton object, bound to a specific container

Object

Object provider just returns an object provided to it, e.g. Object(42)

Project details


Release history Release notifications | RSS feed

Download files

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

Source Distribution

aioinject-0.24.0.tar.gz (23.2 kB view details)

Uploaded Source

Built Distribution

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

aioinject-0.24.0-py3-none-any.whl (16.5 kB view details)

Uploaded Python 3

File details

Details for the file aioinject-0.24.0.tar.gz.

File metadata

  • Download URL: aioinject-0.24.0.tar.gz
  • Upload date:
  • Size: 23.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: pdm/2.12.2 CPython/3.11.5

File hashes

Hashes for aioinject-0.24.0.tar.gz
Algorithm Hash digest
SHA256 43f3d533d551dd235b0226e0bb7282292ab018e96dfb80363e50308a2dbd1129
MD5 adc15850c9e41da7c20bf048e912bae2
BLAKE2b-256 489f8942fb1dd99c7e850d9bf9165a6361eee4f3ba7938303bee7b41536153f7

See more details on using hashes here.

File details

Details for the file aioinject-0.24.0-py3-none-any.whl.

File metadata

  • Download URL: aioinject-0.24.0-py3-none-any.whl
  • Upload date:
  • Size: 16.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: pdm/2.12.2 CPython/3.11.5

File hashes

Hashes for aioinject-0.24.0-py3-none-any.whl
Algorithm Hash digest
SHA256 34e679f2837fcd1ad92c5ddee69d04444238062b00e3b9c2ef4a2880fd016106
MD5 d4de752694dc91bdb328ffbd716860c0
BLAKE2b-256 147deb39fffb6e18f9eaf6f22305dc381bed0dbdc2877ed5c95f111f9d5755ca

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