Skip to main content

vcti-error

Shared exception classes for vcti packages, plus exception-to-exit-code contracts: the mechanics and the VCollab vocabularies.

Overview

vcti-error serves two purposes. It is the home of the shared exception classes of the vcti family (vcti.error.errors): common failure kinds such as LicenseError and ConfigError, defined once so every vcti package and application raises and catches the same class objects — no exit-code machinery involved. It also provides the exception-to-exit-code contract story: when a Python script runs as a subprocess, the one value the caller can reliably branch on is the process exit code, and making that integer mean the same thing to both sides requires an agreed vocabulary — a contract. vcti.error.mechanics validates, composes, and resolves such contracts (plain read-only Mapping[type[BaseException], int] data), and vcti.error.contract.* publishes the VCollab vocabularies as pure mappings from the shared classes to codes. The two purposes compose but don't require each other, and any team can define its own contract on the same mechanics.

Installation

pip install vcti-error

Upgrading from vcti-error 1.x: the exit-code values are preserved (in vcti.error.contract.legacy), but the Python API changed — see Migration below.

In requirements.txt

vcti-error>=2.0.0

In pyproject.toml dependencies

dependencies = [
    "vcti-error>=2.0.0",
]

Quick Start

Resolve an escaped exception to its exit code, using the codes existing applications already emit (the legacy contract):

from vcti.error.contract.legacy import exit_code

def main() -> int:
    try:
        run()
        return 0
    except Exception as err:
        return exit_code(err)      # MRO-aware; unmapped -> 1

Raise the shared exception classes — from any vcti package or application, with or without exit-code contracts:

from vcti.error.errors import ConfigError

raise ConfigError("missing required key 'output_dir'")

Composing a contract leaf with an application's own codes:

from vcti.error.contract import legacy
from vcti.error.mechanics import combine, resolve_code
from myapp.errors import CODE_MAP as APP_CODES   # your contract

CODE_MAP = combine(legacy.CODE_MAP, APP_CODES)

def main() -> int:
    try:
        run()
        return 0
    except Exception as err:
        return resolve_code(err, CODE_MAP, default=legacy.ExitCode.UNSPECIFIED)

Using the mechanics alone — a contract is a dict:

from vcti.error.mechanics import resolve_code, validate

CODE_MAP = {Exception: 1, FileNotFoundError: 2}
validate(CODE_MAP)     # fail fast at startup

sys.exit(resolve_code(err, CODE_MAP, default=1))

Errno-correct OS errors for programmatic raises:

from vcti.error.mechanics import system_error

raise system_error(FileNotFoundError, "config.yaml")
# FileNotFoundError(ENOENT, "No such file or directory", "config.yaml")
# -> err.errno / err.strerror / err.filename all set, as if the OS raised it

The three parts

Package Role
vcti.error.errors The shared exception classes of the vcti family (LicenseError, ConfigError, ...). Usable entirely on their own — no codes, no mechanics.
vcti.error.mechanics Validate, compose, and resolve exit-code mappings. No vocabulary, no classes.
vcti.error.contract.* The vocabularies: pure mappings from the shared classes to codes.

The contract leaves

Leaf Status Claim Contents
vcti.error.contract.legacy append-only — released codes never change; may still gain new ones 1–5 The exit codes existing applications already emit: UNSPECIFIED(1), FILE_NOT_FOUND(2), LICENSE_ERROR(3), KEY_ERROR(4), VARIABLE_UNDEFINED(5)
vcti.error.contract.base_draft draft — mutable, no promises 1–63 The candidate vocabulary for new applications (11 codes today), fully independent of legacy. Becomes base — append-only — when renamed at freeze

The two leaves are independent: they share no obligations, and nothing ties their code values together — an application speaks one contract, not both. Every leaf is just an ExitCode enum and a CODE_MAP; its exit_code() and exception_class() resolvers are produced by bind(CODE_MAP, default=...), so every leaf behaves identically and no leaf reimplements resolution. The exception classes a leaf maps live in vcti.error.errors, never in the leaf itself.

Leaf lifecycle: a leaf named <name>_draft is mutable and promises nothing; all other leaves are append-only (a released code is never reassigned or removed, though new codes may still be added within the claim). legacy is not a frozen snapshot — it grows if the existing applications it serves grow. Production code should never import a _draft leaf. See docs/design.md.

Other teams can publish their own contract leaves (or standalone contract packages) on the same mechanics — see docs/extending.md.


The mechanics (vcti.error.mechanics)

Name Kind Purpose
CodeMap type alias Mapping[type[BaseException], int] — a contract
validate(code_map, *, claim) function Range/type checks; claim enforces a contract's declared range
combine(*code_maps) function Read-only merge; disagreement → CodeCollisionError
resolve_code(exc, code_map, *, default) function MRO-aware exception → code resolution; default is caller-supplied
resolve_class(code, code_map, *, default=Exception) function Reverse: code → exception class (one class if several share a code)
bind(code_map, *, default) function Returns a contract's bound (exit_code, exception_class) pair
system_error(cls, *args) function Errno-correct OS-exception construction (MRO-aware)
ERRNO_MAP constant Read-only OS-exception → errno table system_error resolves against
MAX_EXIT_CODE constant 255 (POSIX truncates exit codes modulo 256)
CodeMapError exception Base; CodeRangeError, CodeCollisionError

Rules baked into validate: codes are 1–255 (0 is success), stay ≤125 in practice (126+ collides with shell/signal conventions), and bool codes are rejected. The mechanics hold no state, pre-register nothing, and define no codes — not even "unspecified"; vocabulary belongs to the contract leaves.


Migration from vcti-error 1.x

The exit-code values are unchanged — callers see no difference. The Python surface changed at 2.0.0:

# before (1.x)
# from vcti.error import error_code, ExceptionType, LicenseError

# after
from vcti.error.contract.legacy import ExitCode, exit_code
from vcti.error.errors import LicenseError
  • ExceptionTypeExitCode; error_code()exit_code().
  • exception_class(name) (case-insensitive string) → exception_class(code) (exit-code int → exception class). The name-based lookup is gone.
  • Exception classes (LicenseError, VariableUndefined) moved to vcti.error.errors.
  • system_error moved to vcti.error.mechanics.
  • Resolution is now MRO-aware: exception subclasses classify to their parent's code instead of falling back to 1.

Dependencies

None. Standard library only.


Documentation

If you want to… Read
Get started using the package Quick Start above
Understand the architecture and the design decisions docs/design.md
Navigate and understand the source docs/source-guide.md
See practical, real-world usage docs/patterns.md
Extend the library (add a shared class or a contract) docs/extending.md
Look up a specific function or type docs/api.md

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

vcti_error-2.0.1.tar.gz (19.9 kB view details)

Uploaded Source

Built Distribution

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

vcti_error-2.0.1-py3-none-any.whl (15.6 kB view details)

Uploaded Python 3

File details

Details for the file vcti_error-2.0.1.tar.gz.

File metadata

  • Download URL: vcti_error-2.0.1.tar.gz
  • Upload date:
  • Size: 19.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for vcti_error-2.0.1.tar.gz
Algorithm Hash digest
SHA256 c5e909188b1a721e77b9ce537358bb763c530b49cb1ca4d557006a5fb42e42f7
MD5 8b643c2a305522c91141a4a2527cb878
BLAKE2b-256 a2933f6d4c80c87d1b24b1f719737877fa6188d71a0757856c1c70d681b322ef

See more details on using hashes here.

Provenance

The following attestation bundles were made for vcti_error-2.0.1.tar.gz:

Publisher: release.yml on vcollab/vcti-python-error

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

File details

Details for the file vcti_error-2.0.1-py3-none-any.whl.

File metadata

  • Download URL: vcti_error-2.0.1-py3-none-any.whl
  • Upload date:
  • Size: 15.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for vcti_error-2.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 29e715cf533d6feee4e05ed1c920da086496e982f26971a5da27fe573d978525
MD5 878c6bdee33e9a45e37b6d41a6ef8796
BLAKE2b-256 e2887302e2979946577a0025f601e97e51ac19f28321bdc795b1708eda8ef424

See more details on using hashes here.

Provenance

The following attestation bundles were made for vcti_error-2.0.1-py3-none-any.whl:

Publisher: release.yml on vcollab/vcti-python-error

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

Release history Release notifications | RSS feed

This release

2.0.1 This release

2 files

2.0.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page