Async-first dependency injection library based on python type hints

Overview

Dependency Depression

Async-first dependency injection library based on python type hints

Quickstart

First let's create a class we would be injecting:

class Test:
    pass

Then we should create instance of container and register our Test class in it, we would use Callable provider that would simply call our class, since classes are also callables!

from dependency_depression import Depression, Callable

container = Depression()
container.register(Test, Callable(Test))

Then we should create a context and resolve our class from it:

with container.sync_context() as ctx:
    ctx.resolve(Test)
    # < __main__.Test>

Injecting

To mark parameters for injection mark them with typing.Annotated and Inject marker

from typing import Annotated
from dependency_depression import Callable, Depression, Inject


def create_number() -> int:
    return 42


def create_str(number: Annotated[int, Inject]) -> str:
    return str(number)

container = Depression()
container.register(str, Callable(create_str))
container.register(int, Callable(create_number))

with container.sync_context() as ctx:
    string = ctx.resolve(str)
    print(string, type(string))
    # 42 
   

Providers

When creating a provider you should specify the type it returns, but it can be inferred from class type or function return type:

from dependency_depression import Callable

provider = Callable(int)
# Is the same as Callable(factory=int, impl=int)
assert provider.provide_sync() == 0

Example using factory function, impl is inferred from return type hint:

from dependency_depression import Callable


def create_foo() -> str:
    return "foo"


provider = Callable(create_foo)
assert provider.provide_sync() == "foo"
assert provider.impl is str

This all comes into play when you have multiple implementations for base class and want to retrieve individual providers from a container,
let's register two concrete classes under same interface:

from dependency_depression import Depression, Callable


class Base:
    pass


class ConcreteA(Base):
    pass


class ConcreteB(Base):
    pass


container = Depression()
container.register(Base, Callable(ConcreteA))
container.register(Base, Callable(ConcreteB))

with container.sync_context() as ctx:
    a = ctx.resolve(Base, ConcreteA)  # <__main__.ConcreteA>
    b = ctx.resolve(Base, ConcreteB)  # <__main__.ConcreteB>
    
    # This would raise an error since we have two classes registered as `Base`
    ctx.resolve(Base)

If you have multiple classes registered under same interface you can specify concrete class using Impl marker:

from typing import Annotated
from dependency_depression import Inject, Impl


class Injectee:
    def __init__(
        self,
        a: Annotated[Base, Inject, Impl[ConcreteA]],
        b: Annotated[Base, Inject, Impl[ConcreteB]],
    ):
        pass

You can also just register concrete classes instead:

container.register(ConcreteA, Callable(ConcreteA))
container.register(ConcreteB, Callable(ConcreteB))

class Injectee:
    def __init__(
        self,
        a: Annotated[ConcreteA, Inject],
        b: Annotated[ConcreteB, Inject],
    ):
        pass

Generics

Dependency Depression can also be used with Generics:

T: raise NotImplementedError class UserRepository(IRepository[User]): def get(self, identity: int) -> User: return User(id=identity, username="Username") class ItemRepository(IRepository[Item]): def get(self, identity: int) -> Item: return Item(id=identity, title="Title") class Injectee: def __init__( self, user_repository: Annotated[IRepository[User], Inject], item_repository: Annotated[IRepository[Item], Inject], ): self.user_repository = user_repository self.item_repository = item_repository container = Depression() container.register(IRepository[User], Callable(UserRepository)) container.register(IRepository[Item], Callable(ItemRepository)) container.register(Injectee, Callable(Injectee)) with container.sync_context() as ctx: injectee = ctx.resolve(Injectee) injectee.user_repository # < __main__.UserRepository> injectee.item_repository # <__main__.ItemRepository>">
import dataclasses
from typing import Generic, TypeVar, Annotated

from dependency_depression import Inject, Depression, Callable

T = TypeVar("T")


@dataclasses.dataclass
class User:
    id: int
    username: str


@dataclasses.dataclass
class Item:
    id: int
    title: str


class IRepository(Generic[T]):
    def get(self, identity: int) -> T:
        raise NotImplementedError


class UserRepository(IRepository[User]):
    def get(self, identity: int) -> User:
        return User(id=identity, username="Username")

    
class ItemRepository(IRepository[Item]):
    def get(self, identity: int) -> Item:
        return Item(id=identity, title="Title")

    
class Injectee:
    def __init__(
        self,
        user_repository: Annotated[IRepository[User], Inject],
        item_repository: Annotated[IRepository[Item], Inject],
    ):
        self.user_repository = user_repository
        self.item_repository = item_repository


container = Depression()
container.register(IRepository[User], Callable(UserRepository))
container.register(IRepository[Item], Callable(ItemRepository))
container.register(Injectee, Callable(Injectee))

with container.sync_context() as ctx:
    injectee = ctx.resolve(Injectee)
    injectee.user_repository
    # < __main__.UserRepository>
    injectee.item_repository
    # <__main__.ItemRepository>

Context

Context as meant to be used within application or request scope, it keeps instances cache and an ExitStack to close all resources.

Cache

Context keeps cache of all instances, so they won't be created again, unless use_cache=False or NoCache is used.

In this example passing use_cache=False would cause context to create instance of Test again, however it wouldn't be cached:

from dependency_depression import Callable, Depression


class Test:
    pass


container = Depression()
container.register(Test, Callable(Test))

with container.sync_context() as ctx:
    first = ctx.resolve(Test)
    
    assert first is not ctx.resolve(Test, use_cache=False)
    # first is still cached in context
    assert first is ctx.resolve(Test)

Closing resources using context managers

Context would also use functions decorated with contextlib.contextmanager or contextlib.asyncontextmanager, but it won't use other instances of ContextManager.
Note that you're not passing impl parameter should specify return type using Iterable, Generator or their async counterparts - AsyncIterableand AsyncGenerator:

import contextlib
from typing import Iterable

from dependency_depression import Depression, Callable


@contextlib.contextmanager
def contextmanager() -> Iterable[int]:
    yield 42


class ContextManager:
    def __enter__(self):
        # This would never be called
        raise ValueError

    def __exit__(self, exc_type, exc_val, exc_tb):
        pass


container = Depression()

# Without return type hint you can specify impl parameter:
# container.register(int, Callable(contextmanager, int))
container.register(int, Callable(contextmanager))
container.register(ContextManager, Callable(ContextManager))

with container.sync_context() as ctx:
    number = ctx.resolve(int)  # 42
    ctx_manager = ctx.resolve(ContextManager) # __enter__ would not be called
    with ctx_manager:
        ...
        # Oops, ValueError raised

In case you need to manage lifecycle of your objects you should wrap them in a context manager:

import contextlib
from typing import AsyncGenerator

from dependency_depression import Callable, Depression
from sqlalchemy.ext.asyncio import AsyncSession


@contextlib.asynccontextmanager
async def get_session() -> AsyncGenerator[AsyncSession, None]:
    session = AsyncSession()
    async with session:
        try:
            yield session
        except Exception:
            await session.rollback()
            raise

container = Depression()
container.register(AsyncSession, Callable(AsyncSession))

@Inject decorator

@inject decorator allows you to automatically inject parameters into functions:

from typing import Annotated

from dependency_depression import Callable, Depression, Inject, inject


@inject
def injectee(number: Annotated[int, Inject]):
    return number


container = Depression()
container.register(int, Callable(int))

with container.sync_context():
    print(injectee())
    # 0

Without active context number parameter would not be injected:

injectee()
# TypeError: injectee() missing 1 required positional argument: 'number'

But you still can use your function just fine

print(injectee(42))

You can pass parameters even if you have an active context:

with container.sync_context():
    print(injectee())  # 0, injected
    print(injectee(42))  # 42, provided by user

Usage with Asyncio

Dependency Depression can be used in async context, just use context instead of sync_context:

import asyncio

from dependency_depression import Callable, Depression


async def get_number() -> int:
    await asyncio.sleep(1)
    return 42


async def main():
    container = Depression()
    container.register(int, Callable(get_number))
    async with container.context() as ctx:
        number = await ctx.resolve(int)
        assert number == 42


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

Async context also supports both sync and async context managers and factory functions.

Owner
Doctor
Doctor
Online HackerRank problem solving challenges

LinkedListHackerRank Online HackerRank problem solving challenges This challenge is part of a tutorial track by MyCodeSchool You are given the pointer

Sefineh Tesfa 1 Nov 21, 2021
A service to display a quick summary of a project on GitHub.

A service to display a quick summary of a project on GitHub. Usage 📖 Paste the code below with details filled in as specified below into your Readme.

Rohit V 8 Dec 06, 2022
Jannik Ramrath 1 Feb 05, 2022
The Ultimate Widevine Content Ripper (KEY Extract + Download + Decrypt) is REBORN

NARROWVINE-REBORN ** UPDATE 21.12.01 ** As expected Google patched its ChromeCDM Whitebox exploit by Satsuoni with a force-update on the ChromeCDM. Th

Vank0n 104 Dec 07, 2022
Just a little benchmark for scrapper PC's

PopMark Just a little benchmark for scrapper PC's This benchmark is for old computer that dont support other benchmark because of support. Like lack o

Garry 1 Nov 24, 2021
Adansons Base is a data management tool that organizes metadata of unstructured data and creates and organizes datasets.

Adansons Base is a data management tool that organizes metadata of unstructured data and creates and organizes datasets. It makes dataset creation more effective and helps find essential insights fro

Adansons Inc 27 Oct 22, 2022
Utility to play with ADCS, allows to request tickets and collect information about related objects

certi Utility to play with ADCS, allows to request tickets and collect information about related objects. Basically, it's the impacket copy of Certify

Eloy 185 Dec 29, 2022
Project of the MSEC_LDD . group

HackathonJuntionXHN Project of team MSEC_LQĐ What did we do? Building application to generate whitelist regex for Web application firewall How to setu

Nguyễn Mạnh Cường 0 Dec 19, 2021
Tracking development of the Class Schedule Siri Shortcut, an iOS program that checks the type of school day and tells you class scheduling.

Class Schedule Shortcut Tracking development of the Class Schedule Siri Shortcut, an iOS program that checks the type of school day and tells you clas

3 Jun 28, 2022
This is some simple code to scrape vistbook's system to get an overview of the different cabins availability.

DNT_cabin_availability_system This is some simple code to scrape visbook's system to get an overview of the different cabins availability. The system

Andreas Lorentzen 1 Sep 25, 2022
BinCat is an innovative login system, with which the account you register will be more secure.

BinCat is an innovative login system, with which the account you register will be more secure. This project is inspired by a conventional token system.

Hipotesi 2 May 22, 2022
NeoInterface - Neo4j made easy for Python programmers!

Neointerface - Neo4j made easy for Python programmers! A Python interface to use the Neo4j graph database, and simplify its use. class NeoInterface: C

15 Dec 15, 2022
Junos PyEZ is a Python library to remotely manage/automate Junos devices.

The repo is under active development. If you take a clone, you are getting the latest, and perhaps not entirely stable code. DOCUMENTATION Official Do

Juniper Networks 623 Dec 10, 2022
Badge-Link-Creater 'For more beautiful profiles.'

Badge-Link-Creater 'For more beautiful profiles.' Ready Badges Prepares the codes of the previously prepared badges for you. Note Click here for more

Mücahit Gündüz 9 Oct 19, 2022
Repo to store back end infrastructure for Message in a Bottle

Message in a Bottle Backend API RESTful API for Message in a Bottle frontend application consumption. About The Project • Tools Used • Local Set Up •

4 Dec 05, 2021
适用于HoshinoBot下的雀魂插件。可进行近期对局查询、查询个人数据等功能,更多功能正在扩展

Majsoul_bot This is a Majsoul plugin for HoshinoBot 这是一个HoshinoBot的雀魂相关插件 本项目目前正在扩展,后续会扩展更多功能,敬请期待 前言 项目地址:https://github.com/DaiShengSheng/Majsoul_bo

黛笙笙 33 Dec 14, 2022
Find all social media accounts with a username!

Aliens_eye FIND ALL SOCIAL MEDIA ACCOUNTS WITH A USERNAME! OSINT To install: Open terminal and type: git clone https://github.com/BLINKING-IDIOT/Alien

Aaron Thomas 84 Dec 28, 2022
poro is a LCU interface to change some lol's options.

poro is a LCU interface to change some lol's options. with this program you can: change your profile icon change your profiel background image ch

João Dematte 2 Jan 05, 2022
Earth centric orbit propagation tool. Built from scratch in python.

Orbit-Propogator Earth centric orbit propagation tool. Built from scratch in python. Functionality includes: tracking sattelite location over time plo

Adam Klein 1 Mar 13, 2022
Osintgram by Datalux but i fixed some errors i found and made it look cleaner

OSINTgram-V2 OSINTgram-V2 is made from Osintgram which is made by Datalux originally but i took the script and fixed some errors i found and made the

2 Feb 02, 2022