"""A profile may name a per-suffix renderer. Arm E, capability only. No domain-aware renderer is written here -- that stays a Non-Goal, and naming it as unassigned is part of the point. What this pins is the seam: a profile CAN name one, the default is identity, and the layering direction does not move to make it possible. The layering matters more than it looks. `extract.py` is the extraction registry and `profiles.py` is the contract layer; if extraction had to import profiles to find a renderer, the dependency would run backwards and the registry would stop being usable on its own. Instead `extract_text` takes a callable it knows nothing about, and `inbox.py` -- which already holds the profile -- is what resolves a name to a function. """ from __future__ import annotations import warnings from pathlib import Path import pytest from llm_ingestion_okf.extract import extract_text from llm_ingestion_okf.profiles import DEFAULT, SEGMENTED_V1, BundleProfile FIXTURES = Path(__file__).parent / "fixtures" def _shout(text: str) -> str: """A synthetic renderer: recognisable in output, dependent on nothing. Modelled on the `_SYNTHETIC` instrument used elsewhere -- a real renderer would prove that a real renderer works; this proves the HOOK is called, and called with the extracted text. """ return f"<<{text}>>" def test_a_profile_may_carry_renderers_and_absence_is_none() -> None: """`None` means the profile does not have the capability, never "off". That is the established shape here: `segmentation` reads the same way, and every downstream check is `is not None`. A boolean flag would make "capability absent" and "capability present but disabled" the same value. """ assert DEFAULT.renderers is None assert SEGMENTED_V1.renderers is None assert "renderers" in {f for f in BundleProfile.__dataclass_fields__} def test_every_existing_profile_constructs_unchanged() -> None: """The field is defaulted, so no existing constructor moved.""" from llm_ingestion_okf import profiles as profiles_module for name in dir(profiles_module): value = getattr(profiles_module, name) if isinstance(value, BundleProfile): assert value.renderers is None, f"{name} gained a renderer unasked" def test_extraction_without_a_renderer_is_byte_identical( tmp_path: Path, ) -> None: """The default is identity, asserted per suffix rather than once. A hook whose default is not identity would move every golden in the repo, which is the failure this pins. """ cases = { "note.md": b"# Title\n\nBody\n", "note.txt": b"plain text\n", "data.json": b'{"a": 1}\n', "page.html": b"
hello
", } for name, payload in cases.items(): assert extract_text(name, payload) == extract_text(name, payload, renderer=None) def test_a_named_renderer_is_what_reaches_the_caller() -> None: text = extract_text("note.md", b"# Title\n\nBody\n", renderer=_shout) assert text == "<<# Title\n\nBody\n>>" def test_the_renderer_runs_after_extraction_not_instead_of_it() -> None: """It receives EXTRACTED text, not raw bytes. A renderer handed the raw bytes would have to re-implement extraction, and the two would drift. The csv case makes the ordering visible: the renderer sees the rendered markdown table, not the comma-separated source. """ seen: list[str] = [] def capture(text: str) -> str: seen.append(text) return text extract_text("rows.csv", b"a,b\n1,2\n", renderer=capture) assert seen and "|" in seen[0], "the renderer did not receive extracted text" assert "a,b" not in seen[0].split("\n")[0] or "|" in seen[0] def test_a_renderer_applies_to_binary_types_too() -> None: data = (FIXTURES / "two-line-krav.docx").read_bytes() with warnings.catch_warnings(): warnings.simplefilter("ignore") text = extract_text("krav.docx", data, renderer=_shout) assert text.startswith("<<") and text.endswith(">>") def test_the_extraction_registry_does_not_import_the_profile_layer() -> None: """The layering direction, asserted rather than intended. This is the constraint that shaped the design: the hook is a callable parameter precisely so extraction never needs to know what a profile is. """ source = ( Path(__file__).resolve().parents[1] / "src" / "llm_ingestion_okf" / "extract.py" ).read_text(encoding="utf-8") assert "profiles" not in source, "extract.py must not reach into the profile layer" def test_renderer_is_keyword_only() -> None: """Positional call sites stay source-compatible, which is the repo's rule for every new public parameter.""" with pytest.raises(TypeError): extract_text("note.md", b"x", _shout) # type: ignore[misc]