Skip to content
Development documentation. The PyPI package predates these APIs. Install from GitHub instead: pip install 'increment @ git+https://github.com/kylejcaron/increment.git' Keep any extras requested by the guide, such as increment[dashboard].

Errors

IdentificationGate

Overlap/positivity policy for an observational comparison.

Defaults refuse rather than silently proceed when propensities are extreme (overlap="trim" opts in to dropping poor overlap instead). min_propensity bounds a symmetric band: a fitted propensity outside [min_propensity, 1 - min_propensity] trips.

IdentificationError(message, code, context)

Identification gate refused: the data cannot support the requested estimand.

Every call site names its own stable dotted code and carries structured context; there is no generic default.

CapabilityError

Raised when a source cannot honestly provide a requested capability.

Typed errors and refusal specifications describe invalid definitions, unsupported requests, and wire-format failures.

An arm-lift readout with no observed treatment arm raises InvalidRequestError with code readout.arms.no_treatment. Its context identifies control_group and observed_arms; supply at least one non-control arm to estimate a contrast. This does not prevent reading arm-level counts or running an otherwise defined allocation diagnostic.

Heterogeneity estimation checks its identification contract before reading outcomes. InvalidRequestError codes and their context:

  • cate.identification.randomized_only (caller, mechanism): estimate_cate needs a randomized source.
  • cate.identification.unsupported_mechanism (caller, mechanism, supported): the design is neither randomized nor observational.
  • cate.identification.unsupported_missing_policy (caller, missing): an observational design must declare missing="refuse" for this path.
  • cate.identification.unsupported_max_smd (caller, max_smd): the observational max_smd balance gate is not implemented for this path.
CodedError(message, code, context)

A public error carrying a stable code and immutable call context.

CodedModel, CodedValidationMixin and unwrap_coded are public from increment.errors, not the root increment namespace.

CodedModel and every definition model in increment.semantics.models (which use CodedValidationMixin) surface coded validator refusals from direct construction and direct model_validate, model_validate_json, and model_validate_strings calls: a declaration refused by one of its own validators raises its DefinitionError with its code. Pydantic schema boundaries such as TypeAdapter and an ordinary BaseModel containing one of these models still raise ValidationError: Pydantic captures the refusal because CodedError intentionally remains compatible with ValueError. Recover the coded refusal explicitly at those boundaries:

Only CodedModel (including Definitions) also translates declared-field Field constraint failures: they raise InvalidRequestError (a ValueError), with a model.field.* code, rather than pydantic.ValidationError. Code that catches validation failures from these models must catch (pydantic.ValidationError, increment.errors.InvalidRequestError) (or ValueError) rather than replacing one with the other. Uncoded-validator, root-level, and nested non-coded-model failures remain ValidationError, as do plain field constraints on the other definition models.

from pydantic import TypeAdapter, ValidationError
from increment.errors import DefinitionError, unwrap_coded
from increment.semantics.models import Definitions
try:
TypeAdapter(Definitions).validate_python({"day_boundary": "EST"})
except ValidationError as exc:
try:
unwrap_coded(exc)
except DefinitionError as coded:
refusal = {"code": coded.code, "context": dict(coded.context)}
else:
raise
CodedModel(data)

Mixin that translates attributable pydantic field failures to coded errors.

Direct construction and model_validate* calls surface InvalidRequestError for known field failures, while cross-field validators and unknown pydantic failure kinds remain ValidationError.

DefinitionError(message, code, context)

A definition YAML parse, model validation, or reference failed.

Carries message, the file or directory path given to the loader, and a copied sources mapping for duplicate-name errors.

InvalidRequestError

Raised when a public request is invalid.

RefusalSpec(code, error_type, render, template, keys)

Stable code, exception type, and renderer for one refusal.

Exactly one of render (a callable, for text that needs computation) or template (a str.format string, for plain interpolation) must be set. keys is the template’s own {placeholder} names, unioned with any explicitly declared extras; a render-only spec’s keys stays empty — its own call signature is its drift guard.

RETIRED_CODES

Lookup only: maps a retired refusal code to its canonical replacement, to a tuple of replacements when one code was split into several, or to None if the code was deleted with no replacement (a genuinely dead registration). Every retired/merged/deleted code in this codebase gets exactly one entry here in the same change that removes it from the live registry. A CodedError instance’s own .code never changes after construction — this map is for callers who catch an old code and need to migrate, and for tests/test_retired_codes.py’s enforcement.

UnsupportedRequestError

Raised when a recognized request is not supported.

WireFormatError

Raised when serialized source data has an invalid wire format.

refuse(spec, context)

Render one refusal and raise its declared coded error type.

refusals(error_type, entries)

Build a code -> RefusalSpec registry. Each entry is a plain str.format template, a (template, extra_keys) pair (for a template whose caller-supplied keys are not all used as placeholders), or a prebuilt RefusalSpec (passed through unchanged, its own code must match the registry key).

raiser(registry)

Build a module’s _raise(code, **context) from its registry.

unwrap_coded(exc)

Re-raise the first coded refusal buried in a pydantic ValidationError.

Use this at schema boundaries such as TypeAdapter or an ordinary BaseModel containing a CodedModel. Returns (does nothing) if no coded error is found, so the caller can re-raise the original ValidationError unchanged.

Library advisories are instances of IncrementWarning (or IncrementRuntimeWarning/IncrementDeprecationWarning, which also satisfy filterwarnings/pytest.warns matched against RuntimeWarning/ DeprecationWarning), carrying a stable .code and immutable .context alongside the free-text message — the warning counterpart to CodedError. Filter or assert on .code rather than message text:

import warnings
from increment.errors import IncrementWarning
with warnings.catch_warnings(record=True) as caught:
warnings.simplefilter("always")
... # a call that may emit a library advisory
codes = {w.message.code for w in caught if isinstance(w.message, IncrementWarning)}
IncrementWarning(message, code, context)

A public warning carrying a stable code and immutable call context.

Mirrors CodedError: .code is fixed at construction and .context is an immutable snapshot. A category that must also match RuntimeWarning/DeprecationWarning for existing filterwarnings calls should multiply-inherit from this class and that category (see IncrementRuntimeWarning, IncrementDeprecationWarning) rather than replacing it.

IncrementRuntimeWarning

A coded warning that also satisfies filterwarnings(category=RuntimeWarning).

IncrementDeprecationWarning

A coded warning that also satisfies filterwarnings(category=DeprecationWarning).

WarningSpec(code, warning_type, render)

Stable code, warning type, and renderer for one coded warning.

warn(spec, stacklevel, skip_file_prefixes, context)

Render one coded warning and emit it via warnings.warn.

context is a single explicit mapping (never **kwargs) so a local _warn(code, /, **context) forwarding helper can pass it straight through as context=context without a keyword-splat colliding with stacklevel/skip_file_prefixes — the shape a type checker cannot otherwise rule out through a **kwargs: object splat.

stacklevel means the same thing it would at a direct warnings.warn call: this function adds its own frame to warnings.warn’s count internally (stacklevel + 1), so a caller need not account for going through warn() itself. A local _warn(code, /, *, stacklevel=2, **context) helper that forwards to this function should do the same (warn(spec, stacklevel=stacklevel + 1, context=context)) to absorb its own frame — callers at the original warnings.warn sites then keep their original numeric stacklevel literal unchanged.