Skip to main content

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

Project description

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

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

vcti_error-2.0.0.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.0-py3-none-any.whl (15.6 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: vcti_error-2.0.0.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.0.tar.gz
Algorithm Hash digest
SHA256 db61ca107635a24d908e61521d308a9d3be0a528ce758978ac403eb952035f6a
MD5 d06e2213174aeb0719f2a63cec398bda
BLAKE2b-256 5f61a029ab8aeebdfeaf39488146e749e5388ea6d13d954585d4884129f419e4

See more details on using hashes here.

Provenance

The following attestation bundles were made for vcti_error-2.0.0.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.0-py3-none-any.whl.

File metadata

  • Download URL: vcti_error-2.0.0-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.0-py3-none-any.whl
Algorithm Hash digest
SHA256 ffe00f2a540e619b3082328b6d87da584d08b7c84305fcc4b8074830e64b0b32
MD5 26f8ea9fb101dc7148cda984d76ae880
BLAKE2b-256 b83b36fb850246799646f0c6d157773e76f593bf0fa7508eb9fd5d816af8dd19

See more details on using hashes here.

Provenance

The following attestation bundles were made for vcti_error-2.0.0-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.

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