Skip to main content

No project description provided

Project description

Ninject 🥷

PyPI - Version PyPI - Python Version License: MIT

Ninject uses modern Python features to provide a simple and performant dependency injection framework.

Installation

pip install ninject

Basic Usage

import ninject as n
from dataclasses import dataclass


# Define a type to be used as a dependency

@dataclass
class Config:
    greeting: str
    recipient: str


# Create a context

context = n.Context()


# Add a provider to the context

@context.provides
def provide_config() -> Config:
    return Config("Hello", "World")


# Injec the dependency into a function

@n.inject
def make_message(*, config: Config = n.inject.ed) -> str:
    return f"{config.greeting}, {config.recipient}!"


# Run the function with the context

with context:
    assert make_message() == "Hello, World!"

Types of Providers

A provider is one of the following

  • A function that returns a value
  • A generator that yields a single value
  • A context manager class that yields a value
  • An async function that returns a value
  • An async generator that yields a single value
  • An async context manager class that yields a value
@context.provides
def sync_function() -> Message:
    return Message("Hello, World!")


@context.provides
def sync_generator() -> Message:
    try:
        yield Message("Hello, World!")
    finally:
        pass


@context.provides
class SyncContextManager:
    def __enter__(self) -> Message:
        return Message("Hello, World!")

    def __exit__(self, *args) -> None:
        pass


@context.provides
async def async_function() -> Message:
    return Message("Hello, World!")


@context.provides
async def async_generator() -> Message:
    try:
        yield Message("Hello, World!")
    finally:
        pass


@context.provides
class AsyncContextManager:
    async def __aenter__(self) -> Message:
        return Message("Hello, World!")

    async def __aexit__(self, *args) -> None:
        pass

Providing Distinct Types

It's important to provide easily distinguishable types. In the case of built-in types, you can use NewType to define a new subtype. In the example below, Greeting and Recipient are both distinct str subtypes recognized by Ninject:

from typing import NewType
import ninject as n

Greeting = NewType("Greeting", str)
Recipient = NewType("Recipient", str)


contest = n.Context()


@context.provides
def provide_greeting() -> Greeting:
    return Greeting("Hello")


@context.provides
def provide_recipient() -> Recipient:
    return Recipient("World")

This way, you can use the built-in type as a dependency:

@context.provides
def provide_message(*, greeting: Greeting = inject.ed, recipient: Recipient = inject.ed) -> str:
    return f"{greeting}, {recipient}!"

Providing Static Values

To do this you can use the let context:

from dataclasses import dataclass
import ninject as n


@dataclass
class Config:
    greeting: str
    recipient: str


@n.inject
def make_message(*, config: Config = n.inject.ed) -> str:
    return f"{config.greeting}, {config.recipient}!"


with n.let(Config(greeting="Hello", recipient="World")):
    assert make_message() == "Hello, World!"

When a type alias or NewType is used to define a dependency, pass the type and the value separately:

from typing import NewType
import ninject as n

Greeting = NewType("Greeting", str)
Recipient = NewType("Recipient", str)


@n.inject
def make_message(*, config: Config = n.inject.ed) -> str:
    return f"{config.greeting}, {config.recipient}!"


with (
    n.let(Greeting, "Hello"),
    n.let(Recipient, "World"),
):
    assert make_message() == "Hello, World!"

Providers with Dependencies

Providers can have their own dependencies:

from dataclasses import dataclass
from typing import NewType
import ninject as n


@dataclass
class Config:
    greeting: str
    recipient: str


Message = Dependency("Message", str)

context = n.Context()


@context.provides
def provide_config() -> Greeting:
    return Config("Hello", "World")


@context.provides
def provide_message(*, config: Config = n.inject.ed) -> Message:
    return Message(f"{greeting}, {recipient}!")


@n.inject
def print_message(*, message: Message = n.inject.ed):
    print(message)


if __name__ == "__main__":
    with context:
        print_message()

The output will be:

Hello, World!

Providing Multiple Dependencies

A single provider can supply multiple dependencies by returning a tuple:

from ninject import Context, Dependency, inject

Greeting = Dependency("Greeting", str)
Recipient = Dependency("Recipient", str)
MessageContent = tuple[Greeting, Recipient]


@inject
def print_message(*, greeting: Greeting = inject.ed, recipient: Recipient = inject.ed):
    print(f"{greeting}, {recipient}!")


context = Context()


@context.provides
def provide_message_content() -> MessageContent:
    return "Hello", "World"


if __name__ == "__main__":
    with context:
        print_message()

You may also depend on the tuple, in this case MessageContent, directly:

from ninject import Context, Dependency, inject

Greeting = Dependency("Greeting", str)
Recipient = Dependency("Recipient", str)
MessageContent = tuple[Greeting, Recipient]


@inject
def print_message(*, message_content: MessageContent = inject.ed):  # TypeError!
    greeting, recipient = message_content
    print(f"{greeting}, {recipient}!")


context = Context()


@context.provides(MessageContent)
def provide_message_content() -> dict:
    return {"greeting": "Hello", "recipient": "World"}


if __name__ == "__main__":
    with context:
        print_message()

Providing Dependencies Concurrently

Ninject does not execute async providers concurrently since doing so can add a substantial amount of overhead to async function calls if it's unnecessary. If you want to satisfy dependencies concurrently you can leverage the ability to provide multiple dependencies at once. With that in mind, you can use asyncio.gather to run several async functions concurrently before returning the dependencies:

import asyncio
from ninject import Context, Dependency, inject

Greeting = Dependency("Greeting", str)
Recipient = Dependency("Recipient", str)
MessageContent = tuple[Greeting, Recipient]

@inject
async def print_message(
    *, greeting: Greeting = inject.ed, recipient: Recipient = inject.ed
):
    print(f"{greeting}, {recipient}!")


context = Context()


async def get_message() -> str:
    return "Hello"


async def get_recipient() -> str:
    return "World"


async def provide_message_content() -> MessageContent:
    return tuple(await asyncio.gather(get_message(), get_recipient()))


if __name__ == "__main__":
    with context:
        asyncio.run(print_message())

Mixing Async and Sync Providers

To mix async and sync providers, the highest order dependent function must be async. So, in the example below, that highest order dependent async function is print_message. The fact that print_message is async is what allows the sync provide_message function to depend on the async provide_recipient function:

import asyncio
from ninject import Context, Dependency, inject

Greeting = Dependency("Greeting", str)
Recipient = Dependency("Recipient", str)

context = Context()


@context.provides(Recipient)
async def provide_recipient() -> str:
    return "World"


@context.provides(Message)
def provide_message(*, recipient: Recipient = inject.ed) -> str:
    return f"Hello, {recipient}!"


@inject
async def print_message(*, message: Message = inject.ed):
    print(message)


if __name__ == "__main__":
    with context:
        asyncio.run(print_message())

If print_message were sync, then the following error would be raised:

RuntimeError: Cannot use an async context manager in a sync context

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

ninject-0.0.3.tar.gz (14.7 kB view details)

Uploaded Source

Built Distribution

ninject-0.0.3-py3-none-any.whl (12.0 kB view details)

Uploaded Python 3

File details

Details for the file ninject-0.0.3.tar.gz.

File metadata

  • Download URL: ninject-0.0.3.tar.gz
  • Upload date:
  • Size: 14.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: python-httpx/0.27.0

File hashes

Hashes for ninject-0.0.3.tar.gz
Algorithm Hash digest
SHA256 008a18bea2374b6273920b5297f1ecb6c9931310ef8e3958824232905ce57ce8
MD5 c270c61fafbe08d33e7aa35a9bf5a033
BLAKE2b-256 1c9eb68bace74158f02ec5a2d0d89650713cfc8e4fb3428d95d5f2c6cd459477

See more details on using hashes here.

File details

Details for the file ninject-0.0.3-py3-none-any.whl.

File metadata

  • Download URL: ninject-0.0.3-py3-none-any.whl
  • Upload date:
  • Size: 12.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: python-httpx/0.27.0

File hashes

Hashes for ninject-0.0.3-py3-none-any.whl
Algorithm Hash digest
SHA256 e3b7aa80920ec44c33af86f47146fe86a55c315f95efcf5369422785365f748a
MD5 85f829c3c74193dd48c776fdc9561bb6
BLAKE2b-256 5968884aa7e9cdb9791dade470b545df1e55d2ba1e507d2ce62f14408477021b

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