Skip to content

API Reference

View the Source (~220 lines)


Classes

TinyDiCtx

The dependency injection context.

@dataclass
class TinyDiCtx:
    value_map: dict[str, Any]
    fn_map: dict[Callable[..., Any], Callable[..., Any]]
    validator: TypeValidator | None
    depends_types: tuple[type, ...] = (Depends,)
Attribute Purpose
value_map Values to inject by parameter name
fn_map Function substitutions (for testing/mocking)
validator Optional type validator (e.g., Pydantic)
depends_types Tuple of Depends-like classes to recognize

Methods

call_fn

async def call_fn(fn, fn_map=None, validator=None, depends_types=None, **kwargs) -> Any

Call a function with dependency injection. This is the main entry point.

# Basic call
result = await ctx.call_fn(my_function)

# With value injection
result = await ctx.call_fn(my_function, request_id=123)

# With dependency substitution (testing)
result = await ctx.call_fn(my_function, fn_map={real_db: mock_db})

# With validation
result = await ctx.call_fn(my_function, validator=my_validator)

# With multi-framework Depends recognition
from fastapi import Depends as FastApiDepends
result = await ctx.call_fn(my_function, depends_types=(Depends, FastApiDepends))

Parameters are merged with the context's existing maps. Cleanup of yield-based dependencies runs automatically.

with_maps

def with_maps(fn_map=..., validator=..., depends_types=..., **kwargs) -> TinyDiCtx

Create a new context with merged maps. The original context is not modified.

# Create a derived context
test_ctx = empty_di_ctx.with_maps(
    fn_map={real_db: mock_db},
    validator=pydantic_validator,
)

# Use the derived context
result = await test_ctx.call_fn(my_function, request_id=123)

# Create a context that recognizes multiple Depends classes
from fastapi import Depends as FastApiDepends
multi_ctx = empty_di_ctx.with_maps(
    depends_types=(Depends, FastApiDepends)
)

Depends

Marks a parameter as a dependency.

@dataclass
class Depends:
    dependency: Callable[..., Any] | None = None
    use_cache: bool = True
    scope: Any = None  # FastAPI compatibility; ignored
Attribute Purpose Default
dependency The callable to invoke None (infer from type)
use_cache Cache result within a call_fn invocation True
scope FastAPI compatibility (ignored by tiny-fastapi-di) None

Usage

# Explicit callable
def endpoint(db = Depends(get_db)): ...

# Infer from type
def endpoint(db: Annotated[Database, Depends()]): ...

# Disable cache
def endpoint(db = Depends(get_db, use_cache=False)): ...

TypeValidator (Protocol)

Protocol for custom validators.

@runtime_checkable
class TypeValidator(Protocol):
    def validate(self, type_: type, value: Any) -> Any: ...

Implement this protocol to add custom validation or coercion. See Pydantic Integration for an example.


Instances

empty_di_ctx

Pre-configured empty context with no value_map, fn_map, or validator.

from tiny_fastapi_di import empty_di_ctx

result = await empty_di_ctx.call_fn(my_function)

pydantic_di_ctx

Context with Pydantic validation (requires tiny-fastapi-di[pydantic]).

from tiny_fastapi_di.pydantic import pydantic_di_ctx

result = await pydantic_di_ctx.call_fn(my_function, user={"name": "Alice"})

Exceptions

All exceptions include actionable error messages.

RecursionError

Raised when circular dependencies are detected.

RecursionError: Circular dependency detected: get_user() is already being resolved.
Check the dependency chain for cycles.

TypeError

Raised when a dependency cannot be resolved.

TypeError: No value provided for required argument 'request_id'.
Provide via call_fn(**kwargs), Depends() default, or parameter default.

TypeError: Depends() for parameter 'db' has no callable.
Provide Depends(callable) or use Annotated[Type, Depends()] with a type annotation.

TypeError: Depends() requires a callable, got int: 42

FastAPI's Security Class

If you need FastAPI-compatible Security with OAuth2 scopes metadata, you can implement it yourself:

from dataclasses import dataclass, field
from tiny_fastapi_di import Depends

@dataclass
class Security(Depends):
    """Depends subclass with OAuth2 scopes metadata."""
    scopes: list[str] = field(default_factory=list)

# Usage
async def endpoint(user = Security(get_user, scopes=["read", "write"])):
    return user

The scopes field is metadata only. Enforcement is your responsibility, exactly as in FastAPI.