Skip to content

API Reference

Auto-generated from source using mkdocstrings.

deadline_budget

Request deadline budget tracking for distributed orchestrations.

BudgetContext

Convenient wrapper around DeadlineBudget with per-call timeout caps.

Encapsulates deadline budget and per-call timeout configuration for cleaner orchestration code.

Example

Define caps for each specific call in the operation

call_caps = { "identity_create_user": 3.0, "verification_verify_code": 2.0, "credential_set_password": 3.0, } ctx = BudgetContext.create(total_seconds=10.0, call_caps=call_caps)

Get timeout for specific call (with configured cap)

timeout = ctx.timeout_for_call("identity_create_user") await identity_client.create_user(timeout=timeout)

Get timeout for unconfigured call (uses remaining budget)

timeout = ctx.timeout_for_call("identity_get_user") await identity_client.get_user(timeout=timeout)

Source code in deadline_budget/context.py
class BudgetContext:
    """Convenient wrapper around DeadlineBudget with per-call timeout caps.

    Encapsulates deadline budget and per-call timeout configuration for cleaner orchestration code.

    Example:
        # Define caps for each specific call in the operation
        call_caps = {
            "identity_create_user": 3.0,
            "verification_verify_code": 2.0,
            "credential_set_password": 3.0,
        }
        ctx = BudgetContext.create(total_seconds=10.0, call_caps=call_caps)

        # Get timeout for specific call (with configured cap)
        timeout = ctx.timeout_for_call("identity_create_user")
        await identity_client.create_user(timeout=timeout)

        # Get timeout for unconfigured call (uses remaining budget)
        timeout = ctx.timeout_for_call("identity_get_user")
        await identity_client.get_user(timeout=timeout)
    """

    def __init__(self, budget: DeadlineBudget, call_caps: dict[str, float]) -> None:
        """Initialize context with a budget and per-call caps.

        Args:
            budget: The underlying DeadlineBudget instance.
            call_caps: Mapping of call names to their timeout caps in seconds.
                If a call is not in this dict, it will use remaining budget without cap.
        """
        self._budget: DeadlineBudget = budget
        self._call_caps: dict[str, float] = call_caps

    @classmethod
    def create(
        cls,
        total_seconds: float,
        call_caps: dict[str, float] | None = None,
        *,
        min_timeout: float = 0.1,
        safety_margin: float = 0.0,
    ) -> BudgetContext:
        """Create a new budget context with the given parameters.

        Args:
            total_seconds: Total budget for this request in seconds.
            call_caps: Mapping of call names to their timeout caps (optional).
            min_timeout: Minimum timeout to return (default 0.1s).
            safety_margin: Safety margin reserved at the end (default 0.0s).

        Returns:
            A new BudgetContext instance.
        """
        budget = DeadlineBudget(
            total_seconds=total_seconds,
            min_timeout=min_timeout,
            safety_margin=safety_margin,
        )
        return cls(budget=budget, call_caps=call_caps or {})

    def timeout_for_call(self, call_name: str, reserve_for_next: float = 0.0) -> float:
        """Compute timeout for the next downstream call.

        If call_name has a configured cap, applies it. Otherwise uses remaining budget.

        Args:
            call_name: Name of the call (e.g., "identity_create_user", "verification_verify_code").
            reserve_for_next: Reserve this many seconds for subsequent steps.

        Returns:
            Computed timeout in seconds, bounded by [min_timeout, call_cap] if cap exists,
            or [min_timeout, remaining] if no cap configured.

        Raises:
            DeadlineExceededError: If remaining budget is already exhausted.
        """
        cap = self._call_caps.get(call_name)
        return self._budget.timeout_for(cap=cap, reserve_for_next=reserve_for_next)

    def check_expired(self) -> None:
        """Raise DeadlineExceededError if budget is exhausted.

        Raises:
            DeadlineExceededError: If the deadline has passed.
        """
        self._budget.check_expired()

    def remaining(self) -> float:
        """Return remaining time budget in seconds.

        Returns negative value if deadline already exceeded.
        """
        return self._budget.remaining()

    def elapsed(self) -> float:
        """Return elapsed time since budget start in seconds."""
        return self._budget.elapsed()

    def expired(self) -> bool:
        """Check if budget is exhausted."""
        return self._budget.expired()

    @property
    def budget(self) -> DeadlineBudget:
        """Access to underlying budget for advanced use cases."""
        return self._budget

    @property
    def call_caps(self) -> dict[str, float]:
        """Access to configured call caps."""
        return self._call_caps

budget property

Access to underlying budget for advanced use cases.

call_caps property

Access to configured call caps.

__init__(budget, call_caps)

Initialize context with a budget and per-call caps.

Parameters:

Name Type Description Default
budget DeadlineBudget

The underlying DeadlineBudget instance.

required
call_caps dict[str, float]

Mapping of call names to their timeout caps in seconds. If a call is not in this dict, it will use remaining budget without cap.

required
Source code in deadline_budget/context.py
def __init__(self, budget: DeadlineBudget, call_caps: dict[str, float]) -> None:
    """Initialize context with a budget and per-call caps.

    Args:
        budget: The underlying DeadlineBudget instance.
        call_caps: Mapping of call names to their timeout caps in seconds.
            If a call is not in this dict, it will use remaining budget without cap.
    """
    self._budget: DeadlineBudget = budget
    self._call_caps: dict[str, float] = call_caps

check_expired()

Raise DeadlineExceededError if budget is exhausted.

Raises:

Type Description
DeadlineExceededError

If the deadline has passed.

Source code in deadline_budget/context.py
def check_expired(self) -> None:
    """Raise DeadlineExceededError if budget is exhausted.

    Raises:
        DeadlineExceededError: If the deadline has passed.
    """
    self._budget.check_expired()

create(total_seconds, call_caps=None, *, min_timeout=0.1, safety_margin=0.0) classmethod

Create a new budget context with the given parameters.

Parameters:

Name Type Description Default
total_seconds float

Total budget for this request in seconds.

required
call_caps dict[str, float] | None

Mapping of call names to their timeout caps (optional).

None
min_timeout float

Minimum timeout to return (default 0.1s).

0.1
safety_margin float

Safety margin reserved at the end (default 0.0s).

0.0

Returns:

Type Description
BudgetContext

A new BudgetContext instance.

Source code in deadline_budget/context.py
@classmethod
def create(
    cls,
    total_seconds: float,
    call_caps: dict[str, float] | None = None,
    *,
    min_timeout: float = 0.1,
    safety_margin: float = 0.0,
) -> BudgetContext:
    """Create a new budget context with the given parameters.

    Args:
        total_seconds: Total budget for this request in seconds.
        call_caps: Mapping of call names to their timeout caps (optional).
        min_timeout: Minimum timeout to return (default 0.1s).
        safety_margin: Safety margin reserved at the end (default 0.0s).

    Returns:
        A new BudgetContext instance.
    """
    budget = DeadlineBudget(
        total_seconds=total_seconds,
        min_timeout=min_timeout,
        safety_margin=safety_margin,
    )
    return cls(budget=budget, call_caps=call_caps or {})

elapsed()

Return elapsed time since budget start in seconds.

Source code in deadline_budget/context.py
def elapsed(self) -> float:
    """Return elapsed time since budget start in seconds."""
    return self._budget.elapsed()

expired()

Check if budget is exhausted.

Source code in deadline_budget/context.py
def expired(self) -> bool:
    """Check if budget is exhausted."""
    return self._budget.expired()

remaining()

Return remaining time budget in seconds.

Returns negative value if deadline already exceeded.

Source code in deadline_budget/context.py
def remaining(self) -> float:
    """Return remaining time budget in seconds.

    Returns negative value if deadline already exceeded.
    """
    return self._budget.remaining()

timeout_for_call(call_name, reserve_for_next=0.0)

Compute timeout for the next downstream call.

If call_name has a configured cap, applies it. Otherwise uses remaining budget.

Parameters:

Name Type Description Default
call_name str

Name of the call (e.g., "identity_create_user", "verification_verify_code").

required
reserve_for_next float

Reserve this many seconds for subsequent steps.

0.0

Returns:

Type Description
float

Computed timeout in seconds, bounded by [min_timeout, call_cap] if cap exists,

float

or [min_timeout, remaining] if no cap configured.

Raises:

Type Description
DeadlineExceededError

If remaining budget is already exhausted.

Source code in deadline_budget/context.py
def timeout_for_call(self, call_name: str, reserve_for_next: float = 0.0) -> float:
    """Compute timeout for the next downstream call.

    If call_name has a configured cap, applies it. Otherwise uses remaining budget.

    Args:
        call_name: Name of the call (e.g., "identity_create_user", "verification_verify_code").
        reserve_for_next: Reserve this many seconds for subsequent steps.

    Returns:
        Computed timeout in seconds, bounded by [min_timeout, call_cap] if cap exists,
        or [min_timeout, remaining] if no cap configured.

    Raises:
        DeadlineExceededError: If remaining budget is already exhausted.
    """
    cap = self._call_caps.get(call_name)
    return self._budget.timeout_for(cap=cap, reserve_for_next=reserve_for_next)

DeadlineBudget

Tracks request deadline budget using monotonic time.

Example

budget = DeadlineBudget(total_seconds=9.5) timeout1 = budget.timeout_for(cap=5.0) await some_call(timeout=timeout1) timeout2 = budget.timeout_for(cap=5.0) await another_call(timeout=timeout2) if budget.expired(): raise DeadlineExceededError(...)

Source code in deadline_budget/budget.py
class DeadlineBudget:
    """Tracks request deadline budget using monotonic time.

    Example:
        budget = DeadlineBudget(total_seconds=9.5)
        timeout1 = budget.timeout_for(cap=5.0)
        await some_call(timeout=timeout1)
        timeout2 = budget.timeout_for(cap=5.0)
        await another_call(timeout=timeout2)
        if budget.expired():
            raise DeadlineExceededError(...)
    """

    def __init__(
        self,
        total_seconds: float,
        *,
        min_timeout: float = 0.1,
        safety_margin: float = 0.0,
    ) -> None:
        """Initialize deadline budget.

        Args:
            total_seconds: Total budget for this request in seconds.
            min_timeout: Minimum timeout to return from timeout_for (default 0.1s).
            safety_margin: Safety margin reserved at the end (subtracted from total).
        """
        if total_seconds <= 0:
            raise ValueError("total_seconds must be positive")
        if min_timeout < 0:
            raise ValueError("min_timeout must be non-negative")
        if safety_margin < 0:
            raise ValueError("safety_margin must be non-negative")
        if safety_margin >= total_seconds:
            raise ValueError("safety_margin must be less than total_seconds")

        self._total_seconds = total_seconds - safety_margin
        self._min_timeout = min_timeout
        self._started_at = time.monotonic()

    def remaining(self) -> float:
        """Return remaining time budget in seconds.

        Returns negative value if deadline already exceeded.
        """
        elapsed = time.monotonic() - self._started_at
        return self._total_seconds - elapsed

    def elapsed(self) -> float:
        """Return elapsed time since budget start in seconds."""
        return time.monotonic() - self._started_at

    def expired(self) -> bool:
        """Check if budget is exhausted."""
        return self.remaining() <= 0

    def timeout_for(
        self,
        cap: float | None = None,
        min_timeout: float | None = None,
        reserve_for_next: float = 0.0,
    ) -> float:
        """Compute timeout for the next downstream call.

        Args:
            cap: Maximum allowed timeout for this call (service-level cap).
            min_timeout: Minimum timeout override (default: use budget min_timeout).
            reserve_for_next: Reserve this many seconds for subsequent steps.

        Returns:
            Computed timeout in seconds, bounded by [min_timeout, cap].

        Raises:
            DeadlineExceededError: If remaining budget is already exhausted.
        """
        remaining = self.remaining()
        if remaining <= 0:
            raise DeadlineExceededError(
                budget_seconds=self._total_seconds,
                elapsed_seconds=self.elapsed(),
            )

        effective_min = min_timeout if min_timeout is not None else self._min_timeout
        available = max(remaining - reserve_for_next, effective_min)

        if cap is not None:
            return min(available, cap)
        return available

    def check_expired(self) -> None:
        """Raise DeadlineExceededError if budget is exhausted."""
        if self.expired():
            raise DeadlineExceededError(
                budget_seconds=self._total_seconds,
                elapsed_seconds=self.elapsed(),
            )

    @property
    def total_seconds(self) -> float:
        """Return total budget in seconds."""
        return self._total_seconds

total_seconds property

Return total budget in seconds.

__init__(total_seconds, *, min_timeout=0.1, safety_margin=0.0)

Initialize deadline budget.

Parameters:

Name Type Description Default
total_seconds float

Total budget for this request in seconds.

required
min_timeout float

Minimum timeout to return from timeout_for (default 0.1s).

0.1
safety_margin float

Safety margin reserved at the end (subtracted from total).

0.0
Source code in deadline_budget/budget.py
def __init__(
    self,
    total_seconds: float,
    *,
    min_timeout: float = 0.1,
    safety_margin: float = 0.0,
) -> None:
    """Initialize deadline budget.

    Args:
        total_seconds: Total budget for this request in seconds.
        min_timeout: Minimum timeout to return from timeout_for (default 0.1s).
        safety_margin: Safety margin reserved at the end (subtracted from total).
    """
    if total_seconds <= 0:
        raise ValueError("total_seconds must be positive")
    if min_timeout < 0:
        raise ValueError("min_timeout must be non-negative")
    if safety_margin < 0:
        raise ValueError("safety_margin must be non-negative")
    if safety_margin >= total_seconds:
        raise ValueError("safety_margin must be less than total_seconds")

    self._total_seconds = total_seconds - safety_margin
    self._min_timeout = min_timeout
    self._started_at = time.monotonic()

check_expired()

Raise DeadlineExceededError if budget is exhausted.

Source code in deadline_budget/budget.py
def check_expired(self) -> None:
    """Raise DeadlineExceededError if budget is exhausted."""
    if self.expired():
        raise DeadlineExceededError(
            budget_seconds=self._total_seconds,
            elapsed_seconds=self.elapsed(),
        )

elapsed()

Return elapsed time since budget start in seconds.

Source code in deadline_budget/budget.py
def elapsed(self) -> float:
    """Return elapsed time since budget start in seconds."""
    return time.monotonic() - self._started_at

expired()

Check if budget is exhausted.

Source code in deadline_budget/budget.py
def expired(self) -> bool:
    """Check if budget is exhausted."""
    return self.remaining() <= 0

remaining()

Return remaining time budget in seconds.

Returns negative value if deadline already exceeded.

Source code in deadline_budget/budget.py
def remaining(self) -> float:
    """Return remaining time budget in seconds.

    Returns negative value if deadline already exceeded.
    """
    elapsed = time.monotonic() - self._started_at
    return self._total_seconds - elapsed

timeout_for(cap=None, min_timeout=None, reserve_for_next=0.0)

Compute timeout for the next downstream call.

Parameters:

Name Type Description Default
cap float | None

Maximum allowed timeout for this call (service-level cap).

None
min_timeout float | None

Minimum timeout override (default: use budget min_timeout).

None
reserve_for_next float

Reserve this many seconds for subsequent steps.

0.0

Returns:

Type Description
float

Computed timeout in seconds, bounded by [min_timeout, cap].

Raises:

Type Description
DeadlineExceededError

If remaining budget is already exhausted.

Source code in deadline_budget/budget.py
def timeout_for(
    self,
    cap: float | None = None,
    min_timeout: float | None = None,
    reserve_for_next: float = 0.0,
) -> float:
    """Compute timeout for the next downstream call.

    Args:
        cap: Maximum allowed timeout for this call (service-level cap).
        min_timeout: Minimum timeout override (default: use budget min_timeout).
        reserve_for_next: Reserve this many seconds for subsequent steps.

    Returns:
        Computed timeout in seconds, bounded by [min_timeout, cap].

    Raises:
        DeadlineExceededError: If remaining budget is already exhausted.
    """
    remaining = self.remaining()
    if remaining <= 0:
        raise DeadlineExceededError(
            budget_seconds=self._total_seconds,
            elapsed_seconds=self.elapsed(),
        )

    effective_min = min_timeout if min_timeout is not None else self._min_timeout
    available = max(remaining - reserve_for_next, effective_min)

    if cap is not None:
        return min(available, cap)
    return available

DeadlineExceededError

Bases: Exception

Raised when request deadline budget is exhausted.

Source code in deadline_budget/errors.py
class DeadlineExceededError(Exception):
    """Raised when request deadline budget is exhausted."""

    def __init__(self, budget_seconds: float, elapsed_seconds: float) -> None:
        self.budget_seconds = budget_seconds
        self.elapsed_seconds = elapsed_seconds
        super().__init__(f"Request deadline exceeded: {elapsed_seconds:.2f}s elapsed, budget was {budget_seconds:.2f}s")