From 7f58d16758bdc400fbca387c465237283b04cf2f Mon Sep 17 00:00:00 2001 From: stumpylog <797416+stumpylog@users.noreply.github.com> Date: Wed, 22 Jul 2026 15:07:24 -0700 Subject: [PATCH] Fix: make ExportSink a real ABC per design spec The implementation plan for Task 2 diverged from the design spec (export-sink-architecture-design.md), leaving ExportSink as a plain class with NotImplementedError bodies instead of the specified AbstractContextManager subclass. Use abc.ABC + @abstractmethod so a concrete sink missing a required method fails at instantiation rather than at first call. --- src/documents/export/sinks.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/documents/export/sinks.py b/src/documents/export/sinks.py index 53eb6f414..b9fe55263 100644 --- a/src/documents/export/sinks.py +++ b/src/documents/export/sinks.py @@ -1,7 +1,9 @@ from __future__ import annotations +import abc import hashlib import json +from contextlib import AbstractContextManager from contextlib import contextmanager from typing import TYPE_CHECKING @@ -51,7 +53,7 @@ class StreamingManifestWriter: self._file.write("\n]") -class ExportSink: +class ExportSink(AbstractContextManager, abc.ABC): """Destination for a document export. The command declares export contents via three verbs; the sink decides how to @@ -65,31 +67,32 @@ class ExportSink: failed run leaves a complete-looking artifact. """ + @abc.abstractmethod def add_file( self, source: Path, arcname: str, *, checksum: str | None = None, - ) -> None: - raise NotImplementedError # pragma: no cover + ) -> None: ... - def add_json(self, content: list | dict, arcname: str) -> None: - raise NotImplementedError # pragma: no cover + @abc.abstractmethod + def add_json(self, content: list | dict, arcname: str) -> None: ... + @abc.abstractmethod def stream(self, arcname: str): # -> contextmanager yielding TextIO - raise NotImplementedError # pragma: no cover + ... def _open(self) -> None: """Hook called on context entry. Override as needed.""" + @abc.abstractmethod def _finalize(self) -> None: """Commit on clean exit.""" - raise NotImplementedError # pragma: no cover + @abc.abstractmethod def _abort(self) -> None: """Roll back on exception.""" - raise NotImplementedError # pragma: no cover def __enter__(self) -> ExportSink: self._open()