mountaineer-di
Common dependency injection utilities for Mountaineer and related projects,
with pydantic as the only hard dependency.
This package provides a robust set of defining and injecting function dependencies.
It works on its own, and when FastAPI is
installed it can also interoperate with fastapi.Depends(...) and
request-bound parameters in the same dependency graph.
Installation
Install the package with uv:
uv add mountaineer-di
What It Does
mountaineer-di lets you declare dependencies on normal Python callables and
resolve them outside a framework request cycle.
It supports:
- Native
Depends(...)markers - Nested dependency graphs
- Seeded caller-provided kwargs
- Async and sync dependencies
- Generator and context-manager dependency lifecycles
- FastAPI request/query/path/header/cookie/body extraction when FastAPI is installed
- Runtime dependency overrides
- Callable-level dependency overrides via
@dependency_override(...)
Quick Start
Use Depends(...) to declare dependencies, then call the resolver and invoke
the target with the returned kwargs:
from typing import Annotated
from mountaineer_di import Depends, provide_dependencies
def get_prefix() -> str:
return "hello"
def get_message(prefix: str = Depends(get_prefix)) -> str:
return f"{prefix} world"
async def handler(message: Annotated[str, Depends(get_message)]) -> str:
return message
async with provide_dependencies(handler) as kwargs:
result = await handler(**kwargs)
print(result) # hello world
The resolver is an async context manager because generator dependencies and
returned context managers stay alive until the async with block exits.
Resolver Entry Points
There are two public ways to resolve a callable:
provide_dependencies(...)
This is the primary entry point:
async with provide_dependencies(
handler,
{"prefix": "hi"},
request=request,
path="/items/{item_id}",
dependency_overrides={original_dep: override_dep},
) as kwargs:
result = await handler(**kwargs)
Use it when you want the generic parameter names:
func: target callablekwargs: seeded values that should already exist in the dependency graphrequest: request-like object for request-aware resolutionpath: route template used to infer path parametersdependency_overrides: per-call override mapping
Native Usage
Seeded kwargs are available to nested dependencies before the handler runs:
from mountaineer_di import Depends, provide_dependencies
def get_message(prefix: str) -> str:
return f"{prefix} world"
async def handler(prefix: str, message: str = Depends(get_message)) -> str:
return message
async with provide_dependencies(handler, {"prefix": "seeded"}) as kwargs:
result = await handler(**kwargs)
print(result) # seeded world
Request-Bound Resolution
When FastAPI and Starlette are installed, the resolver can populate request parameters and FastAPI field markers:
from fastapi import Query, Request
from mountaineer_di import Depends, get_function_dependencies
def get_token(request: Request) -> str:
return request.headers["x-token"]
async def handler(
item_id: int,
q: str = Query(),
token: str = Depends(get_token),
) -> tuple[int, str, str]:
return (item_id, q, token)
async with get_function_dependencies(
callable=handler,
request=request,
url="/items/{item_id}",
) as kwargs:
result = await handler(**kwargs)
If the request contains GET /items/7?q=test, result becomes:
(7, "test", "<x-token header>")
FastAPI Interop
You can mix native and FastAPI dependency markers in the same graph:
from fastapi import Depends as FastAPIDepends, Request
from mountaineer_di import Depends, get_function_dependencies
def get_user_agent(request: Request) -> str | None:
return request.headers.get("user-agent")
def get_context(
user_agent: str | None = FastAPIDepends(get_user_agent),
) -> str:
return user_agent or "unknown"
async def task(context: str = Depends(get_context)) -> str:
return context
async with get_function_dependencies(
callable=task,
request=request,
) as kwargs:
result = await task(**kwargs)
Dependency Overrides
There are two ways to override dependencies.
Per-call overrides
Pass a dependency_overrides mapping when resolving a callable:
from mountaineer_di import Depends, provide_dependencies
def get_prefix() -> str:
return "original"
def get_message(prefix: str = Depends(get_prefix)) -> str:
return f"value:{prefix}"
def mocked_prefix() -> str:
return "mocked"
async with provide_dependencies(
get_message,
dependency_overrides={get_prefix: mocked_prefix},
) as kwargs:
result = get_message(**kwargs)
print(result) # value:mocked
Callable-level overrides
Use @dependency_override(...) when a specific callable should always resolve
with a local override. Stack multiple decorators to attach multiple local
overrides:
from mountaineer_di import Depends, dependency_override, provide_dependencies
def require_valid_user() -> str:
return "request-user"
def get_billing_user_from_request() -> str:
return "billing-user"
@dependency_override(require_valid_user, get_billing_user_from_request)
async def bill_for_metered_type(
user: str = Depends(require_valid_user),
) -> str:
return user
async with provide_dependencies(bill_for_metered_type) as kwargs:
result = await bill_for_metered_type(**kwargs)
print(result) # billing-user
Callable-level overrides are merged with per-call overrides. If the same dependency appears in both places, the explicit per-call override wins.
Development
Development commands are available through the repo Makefile, with lint,
ci-lint, lint-ruff, lint-ty, and test targets following the same
pattern as sibling Mountaineer repositories.
Metadata
Release files for mountaineer-di 0.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| mountaineer_di-0.3.0.tar.gz | 46.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| mountaineer_di-0.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 59.0 kB
Release files / mountaineer_di-0.3.0.tar.gz
| Download URL | mountaineer_di-0.3.0.tar.gz |
|---|---|
| Size | 46.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
b0e95cff4b9e3d0afd9063c7508b9a13aad447b6d158a740fc85afd99d159147
|
|
BLAKE2b-256 checksum How to use checksums |
5a9f4220d35915f063ff284243cf33406a4f6b1f7f1c2e9edb8158edb890e113
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 31, 2026.
Transparency logRelease files / mountaineer_di-0.3.0-py3-none-any.whl
| Download URL | mountaineer_di-0.3.0-py3-none-any.whl |
|---|---|
| Size | 12.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
42481ab3a62dcd6082a983214b7ed7d957a17914ed2297d519a3369fa32ef2a4
|
|
BLAKE2b-256 checksum How to use checksums |
f0567ced07ef849899bd1c89e8bf2ed9cc269e04e877a988a934d0d73ec9fdf6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 31, 2026.
Transparency log