feat(errors): stable machine-readable codes on IngestError
Consumer tests were binding message wording (pytest.raises(match=...)) to distinguish sub-causes within one error type. Every raise site now carries a documented, stable code attribute; the registry lives in the errors.py class docstrings. Codes are stable API; message text is explicitly not. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
9e87f656fc
commit
9d296ca686
6 changed files with 468 additions and 43 deletions
|
|
@ -1,18 +1,50 @@
|
|||
"""Typed error hierarchy rooted in IngestError."""
|
||||
"""Typed error hierarchy rooted in IngestError.
|
||||
|
||||
STABILITY CONTRACT: `IngestError.code` is the machine-readable API for
|
||||
distinguishing sub-causes — consumers assert on it, never on message
|
||||
wording. Codes listed in the class docstrings below are stable across
|
||||
releases; message text is NOT stable and may improve freely.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
class IngestError(Exception):
|
||||
"""Base class for every error raised by this library."""
|
||||
"""Base class for every error raised by this library.
|
||||
|
||||
Carries a stable, machine-readable `code` naming the sub-cause (see the
|
||||
subclass docstrings for the registry). Errors constructed without an
|
||||
explicit code carry `"unspecified"`.
|
||||
"""
|
||||
|
||||
def __init__(self, message: str, *, code: str = "unspecified") -> None:
|
||||
super().__init__(message)
|
||||
self.code = code
|
||||
|
||||
|
||||
class ManifestError(IngestError):
|
||||
"""The manifest failed fail-fast schema validation (ingest-spec §4)."""
|
||||
"""The manifest failed fail-fast schema validation (ingest-spec §4).
|
||||
|
||||
Codes:
|
||||
- `manifest_unreadable` — the manifest file cannot be read
|
||||
- `manifest_invalid_json` — the bytes are not valid UTF-8 JSON
|
||||
- `manifest_version_unsupported` — manifest_version is not the integer 1
|
||||
- `manifest_schema` — a generic shape violation (wrong type, missing or
|
||||
unknown field, empty string, bad identifier, multi-line title,
|
||||
non-positive max_rows, empty extractions list)
|
||||
- `source_type_unknown` — source.type is not 'file', 'sql', or 'http'
|
||||
- `credential_embedded` — source.base_url embeds userinfo credentials
|
||||
- `extraction_id_duplicate` — two extractions share an id
|
||||
- `okf_type_reserved` — an extraction claims the reserved 'verdict' layer
|
||||
"""
|
||||
|
||||
|
||||
class RenderError(IngestError):
|
||||
"""A value cannot be rendered under the §5 body rules (never silent coercion)."""
|
||||
"""A value cannot be rendered under the §5 body rules (never silent coercion).
|
||||
|
||||
Codes:
|
||||
- `unsupported_cell_type` — a SQL cell is not integer/float/text/NULL
|
||||
"""
|
||||
|
||||
|
||||
class SourceError(IngestError):
|
||||
|
|
@ -21,14 +53,31 @@ class SourceError(IngestError):
|
|||
Covers fail-closed path-boundary violations, missing/malformed source
|
||||
content, and max_rows cap violations — always typed, never a leaked
|
||||
OSError and never silent truncation.
|
||||
|
||||
Codes:
|
||||
- `path_escape` — a path resolves outside its root directory
|
||||
- `source_root_missing` — the file-source root is not a directory
|
||||
- `source_file_missing` — the query does not resolve to a file
|
||||
- `csv_no_header` — the CSV has no header row
|
||||
- `csv_ragged_row` — a CSV row's cell count differs from the header's
|
||||
- `max_rows_exceeded` — an extraction exceeds max_rows (any source type)
|
||||
- `connection_ref_unset` — the sql connection_ref env var is not set
|
||||
- `database_missing` — the sql connection_ref points at a missing file
|
||||
- `sql_no_columns` — the sql statement produced no result columns
|
||||
- `sql_failed` — the sql statement failed at the database
|
||||
- `credential_ref_unset` — the http credential_ref env var is not set
|
||||
- `http_transport` — the http GET failed at transport or decode
|
||||
- `fence_marker_in_body` — an http body line is a code-fence marker
|
||||
"""
|
||||
|
||||
|
||||
class MaterializationError(IngestError):
|
||||
"""Materialization refused or failed (ingest-spec §5).
|
||||
|
||||
Covers an invalid ingested_at argument and the §3 collision gate (a
|
||||
generated name occupied by a file without the ingest stamp).
|
||||
Codes:
|
||||
- `ingested_at_invalid` — ingested_at is not ISO-8601 UTC with a Z suffix
|
||||
- `collision_unstamped` — the §3 collision gate: a generated name is
|
||||
occupied by a file without the ingest stamp
|
||||
"""
|
||||
|
||||
|
||||
|
|
@ -37,4 +86,7 @@ class NetworkGateError(IngestError):
|
|||
|
||||
Local-only default, no silent egress: the flag is a run argument — the
|
||||
manifest cannot grant itself network access.
|
||||
|
||||
Codes:
|
||||
- `network_opt_in_missing` — an http source without allow_network=True
|
||||
"""
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue