Dependency Injection for FP
Project description
Partial Injector
A dependency injection container for Python designed around functional programming. It uses functools.partial to wire dependencies into functions, making it a natural fit for FP-style code where services are plain functions rather than class instances.
Requirements
Python 3.14+
Installation
pip install partial-injector
Quick Start
from partial_injector.partial_container import Container
def get_multiplier() -> int:
return 6
def get_answer(get_multiplier: callable) -> int:
return get_multiplier() * 7
container = Container()
container.register_singleton(get_multiplier)
container.register_singleton(get_answer)
container.build()
answer = container.resolve(get_answer)
print(answer()) # 42
The container inspects get_answer's parameter annotations, finds get_multiplier is registered, and creates a partial that pre-fills it.
Core Concepts
Registration Keys
Every registration has a key used to look it up later. By default the registered object itself (function, class, or instance) is the key. Pass key= to use something else — a type, a string, or a TypeAlias.
container.register_singleton(42, key=int)
container.register_singleton("hello", key="greeting")
Registration Types
| Method | Behaviour |
|---|---|
register_singleton |
Built once at build() time; every resolve() returns the same object. |
register_transient |
A fresh deep copy (or new partial) is produced on each resolve(). |
register_singleton_factory |
Calls a factory at build() time; result is cached like a singleton. |
register_transient_factory |
Calls the factory on every resolve(). |
Build and Resolve
container.build() # wire all dependencies; no new registrations allowed after this
value = container.resolve(key)
Dependency Injection into Functions
The container resolves function parameters by matching each parameter's type annotation (or parameter name) against registered keys:
from typing import Callable
type Greeter = Callable[[str], str]
def make_greeter(prefix: str) -> Greeter:
def greet(name: str) -> str:
return f"{prefix}, {name}!"
return greet
container = Container()
container.register_singleton("Hello", key=str)
container.register_singleton_factory(make_greeter, key=Greeter)
container.build()
greeter = container.resolve(Greeter)
print(greeter("World")) # Hello, World!
Parameters that cannot be resolved are left unbound — they become arguments the caller supplies.
FromContainer
FromContainer lets one registration derive its value from another, optionally via a selector lambda:
from partial_injector.partial_container import Container, FromContainer
from dataclasses import dataclass
@dataclass
class Config:
db_url: str
debug: bool
container = Container()
container.register_singleton(Config(db_url="postgresql://localhost/mydb", debug=True), key=Config)
container.register_singleton(FromContainer(Config, lambda c: c.db_url), key="db_url")
container.register_singleton(FromContainer(Config, lambda c: c.debug), key="debug")
container.build()
db_url = container.resolve("db_url") # "postgresql://localhost/mydb"
FromContainer can also be passed as a factory_args or factory_kwargs value, and it will be resolved at that point.
Multiple Registrations for the Same Key
Registering more than one object under the same key groups them automatically. Resolve the group as list[Key]:
from typing import Callable
type Validator = Callable[[str], bool]
def not_empty(value: str) -> bool:
return len(value) > 0
def no_spaces(value: str) -> bool:
return " " not in value
container = Container()
container.register_singleton(not_empty, key=Validator)
container.register_singleton(no_spaces, key=Validator)
container.build()
validators = container.resolve(list[Validator])
all_pass = all(v("hello") for v in validators) # True
If only one of several registrations survives its condition, it can still be resolved as a single item by the bare key.
Conditional Registrations
Every register_* method accepts condition, condition_args, condition_kwargs, and throw_if_condition_not_satisfied. The condition is a callable that returns bool; if it returns False the registration is skipped.
import os
container = Container()
container.register_singleton(
RealDatabase(),
key="db",
condition=lambda: os.getenv("ENV") == "production",
)
container.register_singleton(
FakeDatabase(),
key="db",
condition=lambda: os.getenv("ENV") != "production",
)
container.build()
Set throw_if_condition_not_satisfied=True to raise PartialContainerException when no registration for a key passes its condition.
For TRANSIENT and TRANSIENT_FACTORY registrations the condition is evaluated lazily at each resolve() call rather than at build() time.
inject_returns
When a registered function returns another function, set inject_returns=True to wire that returned function's dependencies too:
from typing import Callable
type Handler = Callable[[str], str]
type HandlerFactory = Callable[[], Handler]
def make_handler(logger: Callable[[str], None]) -> Handler:
def handle(message: str) -> str:
logger(message)
return f"handled: {message}"
return handle
container = Container()
container.register_singleton(print, key=Callable[[str], None])
container.register_singleton(make_handler, key=HandlerFactory, inject_returns=True)
container.build()
factory = container.resolve(HandlerFactory)
handler = factory() # returns handle with logger already injected
handler("ping") # prints "ping", returns "handled: ping"
This works recursively and supports async functions.
inject_items
Register a list of objects and set inject_items=True to have each element processed through the container's injection logic individually:
type Processor = Callable[[int], int]
def double(factor: int, value: int) -> int:
return value * factor
container = Container()
container.register_singleton(2, key=int)
container.register_singleton([double, double], key=list[Processor], inject_items=True)
container.build()
processors = container.resolve(list[Processor])
results = [p(3) for p in processors] # [6, 6]
Error Handling
All errors raise PartialContainerException from partial_injector.error_handling.
| Situation | Message |
|---|---|
Register after build() |
"Container already built" |
resolve() before build() |
"Container not built" |
| Key not registered | "Object with key <key> not registered" |
| Key not built (condition failed, no throw) | key absent from resolved result |
Key not built, throw_if_condition_not_satisfied=True |
"No object with key <key> was built because the built condition has not been met." |
| Unresolvable parameter before a resolvable one | "Cannot build partial function without registered parameter <name>:<type>" |
License
MIT
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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file partial_injector-3.3.0.tar.gz.
File metadata
- Download URL: partial_injector-3.3.0.tar.gz
- Upload date:
- Size: 12.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.13 {"installer":{"name":"uv","version":"0.11.13","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e1f9ab2c3415d7cceaa9caff9dcb091161c43438e7c7ea86e92ae008b8a9f315
|
|
| MD5 |
2d3c8571d0971a8a53de6b202e769b7e
|
|
| BLAKE2b-256 |
b079849375b8337483979322ad81532e0e458f122ae5aebb8292c437170b8f32
|
File details
Details for the file partial_injector-3.3.0-py3-none-any.whl.
File metadata
- Download URL: partial_injector-3.3.0-py3-none-any.whl
- Upload date:
- Size: 9.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.13 {"installer":{"name":"uv","version":"0.11.13","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
11bf5b8d1e778ae7843c35d325483f62b4510f5601c2d3898c250cb7c9e3d6d4
|
|
| MD5 |
60a0837b9fb53b249a4630145f358183
|
|
| BLAKE2b-256 |
25eac1f5915b108b66b0bd52692606057129d4628e4717145a56d9476c14fdda
|