File size: 1,404 Bytes
feb1b1c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

from __future__ import annotations

from collections.abc import Iterator
from typing import Protocol, runtime_checkable

from redstack.domain.errors import DomainError
from redstack.ports._types import SourceRecord


class CandidateSourceError(DomainError):
    """Cannot open or decompress the source, or a low-level IO error occurred.

    Per-line decode failures are *not* this error — they surface as
    ``SourceMalformed`` records so the pipeline can apply skip-vs-abort policy.
    """


@runtime_checkable
class CandidateSourcePort(Protocol):
    """Lazy, single-pass, order-preserving stream of raw candidate records."""

    def stream(self) -> Iterator[SourceRecord]:
        """Yield records lazily in file order, one at a time.

        Each ``SourceOk`` carries the undecoded record plus ``source_index``
        (enumeration order) and ``line_no``; each ``SourceMalformed`` carries
        ``line_no`` and an error string. Calling ``stream`` again starts a
        fresh, independent pass (a new iterator).

        Raises:
            CandidateSourceError: the source cannot be opened or decompressed,
                or an IO error occurs mid-stream.
        """
        ...

    def count(self) -> int | None:
        """Return the record count, or ``None`` if unknown without a full pass."""
        ...


__all__: tuple[str, ...] = ("CandidateSourceError", "CandidateSourcePort")