Export
This content is for 0.1. Switch to the latest version for up-to-date documentation.
Art. 15 export — collect a subject’s data into a structured bundle.
ExportBundle
Section titled “ExportBundle”class ExportBundle(BaseModel): generated_at: datetime incomplete_sources: tuple[str, ...] = () records: tuple[ExportRecord, ...] = () schema_version: int = MANIFEST_SCHEMA_VERSION subject_id: str = Field(min_length=1)Everything held on one subject, with the required Art. 15 metadata.
Fields:
- generated_at (
datetime): When the bundle was assembled (UTC). - incomplete_sources (
tuple[str, ...]): Sources that failed during collection — surfaced loudly rather than silently omitted. - records (
tuple[ExportRecord, ...]): Every exported value, grouped by consumers as they wish. - schema_version (
int): Manifest schema version the bundle was built under. - subject_id (
str): The identifier the export was requested for.
Exporter
Section titled “Exporter”class Exporter: def __init__(data_map: DataMap, graph: SubjectGraph, metadata: MetaData, audit_sink: AuditSink, registry: ResolverRegistry | None = None) -> NoneCollects all of a subject’s personal data into one structured bundle.
Walks the data map for local data and fans out to registered resolvers
for external systems. Resolver failures never silently shrink the
bundle — they are recorded in incomplete_sources.
Exporter.export_subject
Section titled “Exporter.export_subject”def export_subject(session: Session, subject_id: str, *, refs: tuple[SubjectRef, ...] = ()) -> ExportBundleCollect everything held on one subject (Art. 15).
Each ref is routed to the resolver whose name equals the ref’s
kind (ADR 0008). A registered resolver with no matching ref is
skipped — “the subject has no identity in that system” is a
complete answer, recorded in the EXPORT_COMPLETED payload’s
skipped_resolvers, not in incomplete_sources. A resolver
call that fails puts the resolver’s name in incomplete_sources
instead of failing the export. A local database failure propagates
after EXPORT_REQUESTED was appended — a requested-but-never-
completed trail is the abandonment marker. Input validation
(subject id coercion, ref-kind matching) raises before
EXPORT_REQUESTED is appended: a malformed call never became a
data-subject request, so it deliberately leaves no audit trace.
Blocking call; resolver fan-out runs on an internal event loop, so
it must not be invoked on a running event-loop thread — in async
web apps dispatch via a threadpool (e.g. FastAPI’s
run_in_threadpool). See ADR 0006.
Args:
- session (
Session): An open database session; reads only, never writes. - subject_id (
str): Identifier on the subject table (seeSubjectLink). - refs (
tuple[SubjectRef, ...]): External-system references for resolver fan-out.
Returns:
ExportBundle— The structured bundle including Art. 15 metadata (purposes,ExportBundle— legal bases, retention reasons).
Raises:
SubjectResolutionError— Ifsubject_idcannot be coerced to the subject id column’s type.ResolverError— If a ref’skindmatches no registered resolver — a typo must not silently drop an external source from the answer.
ExportRecord
Section titled “ExportRecord”class ExportRecord(BaseModel): category: PiiCategory field: str = Field(min_length=1) legal_basis: LegalBasis | None = None purpose: str | None = None retention_reason: str | None = None source: str = Field(min_length=1) value: object = NoneOne exported value with its Art. 15 metadata.
Fields:
- category (
PiiCategory): PII category of the value. - field (
str): The field within the source. - legal_basis (
LegalBasis | None): Why the data is held, if declared. - purpose (
str | None): Processing purpose, if declared. - retention_reason (
str | None): The legal duty keeping the value, if any. - source (
str): Where the value came from — a table name or resolver name. - value (
object): The value itself, JSON-encoded.