feat(errors): register the four converter codes
Registered before any step raises them, so no later step invents a spelling:
- `extractor_binary_missing` -- the converter binary is absent (distinct
from the extra not being installed)
- `extractor_binary_version` -- present, but not the pinned version
- `extractor_convert_error` -- the converter failed on this file
- `extractor_empty_conversion` -- the converter returned no text
Also widened the `extractor_extra_missing` type list, which still read
"pdf/docx/xlsx".
Denominators recounted after the change rather than carried from a note --
the stale 49/48 figure is what made the recount a step requirement:
code bullets in errors.py 50 -> 54
distinct codes 49 -> 53 (one code documented twice)
test definitions in this suite 56 -> 57 (one parametrized definition,
four cases -- one per code)
The four tests assert only what is true at this step: the code is documented
and an ExtractionError carries it. The resolver and the seam replace each with
a behavioural raise-site test; a code that never gains one stays visible here
as a test that still only reads a docstring.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
5f524f3902
commit
d584186837
2 changed files with 37 additions and 1 deletions
|
|
@ -83,7 +83,7 @@ class ExtractionError(IngestError):
|
||||||
Codes:
|
Codes:
|
||||||
- `extractor_unknown` — no extractor is registered for the file extension
|
- `extractor_unknown` — no extractor is registered for the file extension
|
||||||
- `extractor_extra_missing` — a `[extract]`-gated binary type (pdf/docx/
|
- `extractor_extra_missing` — a `[extract]`-gated binary type (pdf/docx/
|
||||||
xlsx) was given but the optional extra is not installed
|
xlsx/pptx/odt/rtf) was given but the optional extra is not installed
|
||||||
- `extractor_decode_error` — a text-type file's bytes are not valid UTF-8
|
- `extractor_decode_error` — a text-type file's bytes are not valid UTF-8
|
||||||
- `extractor_empty_csv` — a CSV has no header row
|
- `extractor_empty_csv` — a CSV has no header row
|
||||||
- `extractor_empty_pdf` — a PDF yielded no text on any page (a scanned or
|
- `extractor_empty_pdf` — a PDF yielded no text on any page (a scanned or
|
||||||
|
|
@ -91,6 +91,18 @@ class ExtractionError(IngestError):
|
||||||
which would be the silent skip this registry exists to prevent
|
which would be the silent skip this registry exists to prevent
|
||||||
- `extractor_pdf_error` — the PDF parser failed on the file's bytes; the
|
- `extractor_pdf_error` — the PDF parser failed on the file's bytes; the
|
||||||
third-party exception is wrapped, never leaked
|
third-party exception is wrapped, never leaked
|
||||||
|
- `extractor_binary_missing` — the converter binary is absent; distinct
|
||||||
|
from the extra not being installed, because the wheel can be present
|
||||||
|
while the binary it should carry is not
|
||||||
|
- `extractor_binary_version` — the converter binary is present but is not
|
||||||
|
the pinned version; refused rather than used, because extraction is
|
||||||
|
deterministic only within one converter version and a byte-pinned
|
||||||
|
fixture cannot tell "different version" from "defect"
|
||||||
|
- `extractor_convert_error` — the converter failed on this file's bytes;
|
||||||
|
the third-party failure is wrapped, never leaked
|
||||||
|
- `extractor_empty_conversion` — the converter returned no text; refused
|
||||||
|
rather than persisted as an empty concept, for the same reason as
|
||||||
|
`extractor_empty_pdf`
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -356,6 +356,30 @@ def test_extractor_extra_missing(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
assert code_of(excinfo) == "extractor_extra_missing"
|
assert code_of(excinfo) == "extractor_extra_missing"
|
||||||
|
|
||||||
|
|
||||||
|
# The four converter codes are registered here BEFORE anything raises them, so
|
||||||
|
# no later step invents a spelling. Until the converter lands (the resolver in
|
||||||
|
# `_pandoc.py`, then the seam in `extract.py`), the falsifiable claim is exactly
|
||||||
|
# this: the code is documented in the registry and an `ExtractionError` carries
|
||||||
|
# it unchanged. Those steps replace each of these with a behavioural raise-site
|
||||||
|
# test; a code that never gains one would show up here as a test that still
|
||||||
|
# only reads a docstring.
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"code",
|
||||||
|
[
|
||||||
|
"extractor_binary_missing",
|
||||||
|
"extractor_binary_version",
|
||||||
|
"extractor_convert_error",
|
||||||
|
"extractor_empty_conversion",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_converter_code_is_registered_and_carried(code: str) -> None:
|
||||||
|
registry = ExtractionError.__doc__ or ""
|
||||||
|
assert f"`{code}`" in registry, f"{code} is not documented in the registry"
|
||||||
|
assert ExtractionError("x", code=code).code == code
|
||||||
|
|
||||||
|
|
||||||
@requires_extract
|
@requires_extract
|
||||||
def test_extractor_empty_pdf() -> None:
|
def test_extractor_empty_pdf() -> None:
|
||||||
with pytest.raises(ExtractionError) as excinfo:
|
with pytest.raises(ExtractionError) as excinfo:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue