Skip to main content

Common dependency injection utilities for mountaineer & friends

Project description

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 callable
  • kwargs: seeded values that should already exist in the dependency graph
  • request: request-like object for request-aware resolution
  • path: route template used to infer path parameters
  • dependency_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.

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

mountaineer_di-0.2.1.tar.gz (46.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

mountaineer_di-0.2.1-py3-none-any.whl (12.5 kB view details)

Uploaded Python 3

File details

Details for the file mountaineer_di-0.2.1.tar.gz.

File metadata

  • Download URL: mountaineer_di-0.2.1.tar.gz
  • Upload date:
  • Size: 46.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for mountaineer_di-0.2.1.tar.gz
Algorithm Hash digest
SHA256 56406f73b2b16209b8ac2ddf95e60404ecb4c98c281e16fbefb26359464cf6d1
MD5 d39f3bef2557b0191011c9f85ea6ff80
BLAKE2b-256 61f29482135ed6efde1eaaa903bdd7c953df65c6c8288f5a90e9501dc0562958

See more details on using hashes here.

Provenance

The following attestation bundles were made for mountaineer_di-0.2.1.tar.gz:

Publisher: ci.yml on piercefreeman/mountaineer-di

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file mountaineer_di-0.2.1-py3-none-any.whl.

File metadata

  • Download URL: mountaineer_di-0.2.1-py3-none-any.whl
  • Upload date:
  • Size: 12.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for mountaineer_di-0.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 1c9157ef878b4277106753ee9209c0c4bd31bd0761f27e127842c5eb15fdc144
MD5 23f7bb26363b6b96613f69ccd665a549
BLAKE2b-256 ef7d6cad2a62913c4ef88948e16b6017656630d19cc874b96f7c9ad4ef28ac37

See more details on using hashes here.

Provenance

The following attestation bundles were made for mountaineer_di-0.2.1-py3-none-any.whl:

Publisher: ci.yml on piercefreeman/mountaineer-di

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page