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-error1.x: the exit-code values are preserved (invcti.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
ExceptionType→ExitCode;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 tovcti.error.errors. system_errormoved tovcti.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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
db61ca107635a24d908e61521d308a9d3be0a528ce758978ac403eb952035f6a
|
|
| MD5 |
d06e2213174aeb0719f2a63cec398bda
|
|
| BLAKE2b-256 |
5f61a029ab8aeebdfeaf39488146e749e5388ea6d13d954585d4884129f419e4
|
Provenance
The following attestation bundles were made for vcti_error-2.0.0.tar.gz:
Publisher:
release.yml on vcollab/vcti-python-error
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
vcti_error-2.0.0.tar.gz -
Subject digest:
db61ca107635a24d908e61521d308a9d3be0a528ce758978ac403eb952035f6a - Sigstore transparency entry: 2194791641
- Sigstore integration time:
-
Permalink:
vcollab/vcti-python-error@ec047aa0de016c0e91f0624e6c4dfadbf73c67bb -
Branch / Tag:
refs/tags/v2.0.0 - Owner: https://github.com/vcollab
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@ec047aa0de016c0e91f0624e6c4dfadbf73c67bb -
Trigger Event:
push
-
Statement type:
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ffe00f2a540e619b3082328b6d87da584d08b7c84305fcc4b8074830e64b0b32
|
|
| MD5 |
26f8ea9fb101dc7148cda984d76ae880
|
|
| BLAKE2b-256 |
b83b36fb850246799646f0c6d157773e76f593bf0fa7508eb9fd5d816af8dd19
|
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
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
vcti_error-2.0.0-py3-none-any.whl -
Subject digest:
ffe00f2a540e619b3082328b6d87da584d08b7c84305fcc4b8074830e64b0b32 - Sigstore transparency entry: 2194791643
- Sigstore integration time:
-
Permalink:
vcollab/vcti-python-error@ec047aa0de016c0e91f0624e6c4dfadbf73c67bb -
Branch / Tag:
refs/tags/v2.0.0 - Owner: https://github.com/vcollab
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@ec047aa0de016c0e91f0624e6c4dfadbf73c67bb -
Trigger Event:
push
-
Statement type: