API reference — core¶
The framework-free core: the source registry, configuration, the orchestrator-agnostic pipeline helpers and the I14Y reconciliation logic.
registry ¶
Plugin registry for catalog sources.
Each source describes what to extract (a dlt source factory) and how to map each of its resources to an I14Y DCAT model (a transform per resource).
TransformFn ¶
Bases: Protocol
Contract for mapping a raw record to an I14Y DCAT model.
children carries dlt-flattened 1:n child tables keyed by the
child field name (e.g. "tags" → ["foo", "bar"]). Sources
that don't use children can ignore the argument.
lookups is the per-run Lookups view over the source's
cross-reference tables (plus pipeline-injected synthetic tables like
the published-ID maps); one instance is shared across all records of
a run so its memoized indexes amortise.
The return value is either the typed Pydantic model alone, or a
(model, extras) tuple. extras is a JSON-serialisable dict
carrying sidecar payloads that the I14Y model itself can't hold —
e.g. code-list entries, which I14Y publishes via a separate bulk
endpoint after the parent concept is created. Extras are stored
alongside the transformed record and replayed by sync as a
post-create/update follow-up call.
Source code in src/metadataswiss_connector/registry.py
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | |
ResourceSpec
dataclass
¶
How one dlt resource is mapped + published to I14Y.
Attributes:
| Name | Type | Description |
|---|---|---|
transform |
TransformFn
|
Function mapping a raw record to an I14Y input model. |
kind |
ResourceKind
|
Which I14Y entity the model targets — controls sync routing. |
lookups |
tuple[str, ...]
|
Names of other raw dlt resources (in the same source)
that this resource's transform reads via the |
sibling_parent |
str | None
|
Name of the extract dlt resource that this
resource's sibling child tables resolve from. dlt names
their parent-ref column |
Source code in src/metadataswiss_connector/registry.py
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 | |
CatalogSource
dataclass
¶
A registered catalog source.
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
Stable identifier; also used as dlt pipeline name. |
dlt_source_factory |
Callable[[], DltSource]
|
Zero-arg callable returning a configured
|
resources |
dict[str, ResourceSpec]
|
Maps dlt resource name → ResourceSpec (transform + kind). |
publisher |
str | None
|
Optional publisher override. If |
drain_extract_skips |
Callable[[], list[dict]] | None
|
Optional zero-arg callable returning (and
clearing) the items the source skipped during the last
extract (e.g. endpoints answering with server-side errors).
Each dict must match the |
Source code in src/metadataswiss_connector/registry.py
86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 | |
run_source ¶
run_source(
source: CatalogSource,
*,
publisher: str,
destination=None
) -> None
Extract + transform a single source end-to-end (no publish).
Source code in src/metadataswiss_connector/registry.py
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 | |
config ¶
Framework-free configuration objects and env accessors.
These live outside of the orchestrator so the connector can be used as a plain library (CLI, tests, notebooks). All environment access of the connector is gathered here (the dlt source credentials are the exception — dlt injects those itself); the accessors read at call time, so containerised deployments and tests can override via the environment without import-order concerns.
I14YConfig ¶
Bases: BaseModel
Connection + publisher config for the I14Y Partner API.
Source code in src/metadataswiss_connector/config.py
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 | |
state_dir ¶
state_dir() -> Path
Directory holding the per-resource remote-ID state files.
Override via CONNECTOR_DATA_DIR to put them on a persistent
volume in a containerised deployment; defaults to data/state/
in the working directory for local use.
Source code in src/metadataswiss_connector/config.py
17 18 19 20 21 22 23 24 | |
duckdb_path ¶
duckdb_path() -> str
Location of the local DuckDB warehouse.
Override via DUCKDB_PATH to point at a persistent volume in a
containerised deployment; defaults to a file in the working
directory for local/CLI use.
Source code in src/metadataswiss_connector/config.py
27 28 29 30 31 32 33 34 | |
dataspot_web_base ¶
dataspot_web_base() -> tuple[str, str] | None
(base_url, database_name) of the dataspot UI, or None if unset.
Reuses the dlt source-connection env vars SOURCES__DATASPOT__*
so deep links (e.g. in alert emails) point at the same instance the
data was extracted from.
Source code in src/metadataswiss_connector/config.py
37 38 39 40 41 42 43 44 45 46 47 48 | |
pipeline ¶
Framework-free per-source / per-resource pipeline helpers.
These wrap the three logical steps (extract, transform, publish) so they can be invoked from the CLI and from an orchestrator (e.g. Dagster) alike. The CLI runs them in source-wide loops; Dagster materialises one resource at a time.
state_path ¶
state_path(source_name: str, resource_name: str) -> Path
One state file per (source, resource) pair (under config.state_dir()).
Source code in src/metadataswiss_connector/pipeline.py
29 30 31 32 33 | |
extract_source ¶
extract_source(source: CatalogSource) -> None
Run the dlt extract for an entire source (all resources at once).
Source code in src/metadataswiss_connector/pipeline.py
36 37 38 39 40 41 42 43 44 | |
published_id_lookups ¶
published_id_lookups(
source: CatalogSource,
) -> dict[str, list[dict]]
Synthetic lookup tables exposing each resource's published I14Y ids.
One table per resource of source (omitted while nothing is
published yet), named via published_ids_table and carrying rows
{"source_id": ..., "i14y_id": ...} from the resource's sync
state. Injected into transforms so they can reference already-
published sibling records (e.g. a dataservice's servesDatasets)
without reading sync state themselves.
Resolution is against the last persisted state: a brand-new dataset+API pair links on the run after the dataset is first published. There is no within-run ordering guarantee between the independent per-resource publish steps anyway, so this is the consistent choice.
Source code in src/metadataswiss_connector/pipeline.py
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 | |
transform_resource ¶
transform_resource(
source: CatalogSource, resource_name: str
) -> dict
Transform raw rows of a single resource into the i14y_dcat dataset.
Returns the stats dict from run_transform_resource so Dagster
assets (and any other caller) can surface row counts and validation
rate as materialization metadata.
Source code in src/metadataswiss_connector/pipeline.py
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 | |
publish_resource ¶
publish_resource(
source: CatalogSource,
resource_name: str,
*,
limit: int | None = None,
log: Logger | None = None
) -> SyncResult
Publish transformed records of a single resource to I14Y.
log is threaded into the I14Y client so per-request telemetry
(method, status, duration, request id) shows up on the Dagster
asset's run log when called from an asset.
Source code in src/metadataswiss_connector/pipeline.py
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 | |
sync ¶
Sync logic: reconcile local I14Y input records with remote state.
Generic core (sync_records / purge_records) reconciles a list of
transformed records against a per-resource state file via any I14Y
resource client that exposes the standard CRUD + lifecycle operations.
The public sync / purge entrypoints dispatch on a kind string
("dataset" / "concept") via the _KINDS registry.
Change detection is hash-based: each transformed record is canonically serialised and hashed; the digest is persisted alongside the I14Y id and compared on the next run. Any change to the transform logic, the source data, or upstream lookups that affects the published payload flips the hash and forces a re-publish — no manual version bumps required.
ResourceClient ¶
Bases: Protocol
Duck-typed contract satisfied by client.datasets / client.concepts.
Source code in src/metadataswiss_connector/sync.py
40 41 42 43 44 45 46 47 | |
FailedRecord
dataclass
¶
A record that could not be published to I14Y.
Carries the source identifier (dataspot UUID), the error text, and — when available — a human-readable title so the alert email can name the record instead of only showing its opaque UUID.
Source code in src/metadataswiss_connector/sync.py
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 | |
SyncResult
dataclass
¶
Summary of a sync run.
Source code in src/metadataswiss_connector/sync.py
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 | |
SyncState ¶
Persistent mapping of source identifier → I14Y UUID + payload hash.
Stored as a JSON file so the sync knows which records it owns on I14Y without having to query the remote API, and can detect changes to the transformed payload without re-publishing untouched records.
Source code in src/metadataswiss_connector/sync.py
128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 | |
payload_hash ¶
payload_hash(record: dict) -> str
SHA-256 over a canonical JSON serialisation of the full record.
default=str coerces datetimes (and any other non-JSON-native
values dlt may surface) deterministically. sort_keys ensures
dict ordering doesn't perturb the digest. Extras are included
because they're part of the published payload.
Source code in src/metadataswiss_connector/sync.py
58 59 60 61 62 63 64 65 66 67 | |
active_id_map ¶
active_id_map(state_path: Path) -> dict[str, str]
Source identifier → I14Y id for every record currently active on I14Y.
Reads the given per-resource state file; soft-deleted entries and entries without an I14Y id (never successfully created) are excluded. Empty when the state file doesn't exist yet (nothing published). Owned by this module so no other layer needs to know the state-file format.
Source code in src/metadataswiss_connector/sync.py
204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 | |
sync_records ¶
sync_records(
resource: ResourceClient,
source_records: list[dict],
*,
model_class: type[BaseModel],
source_id_for: SourceIdFn,
state_path: Path,
dry_run: bool = False,
force: bool = False
) -> SyncResult
Synchronise transformed records to I14Y via the given resource client.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
resource
|
ResourceClient
|
I14Y resource accessor ( |
required |
source_records
|
list[dict]
|
dicts produced by the transform step, each
validating as |
required |
model_class
|
type[BaseModel]
|
Pydantic input model class for the resource. |
required |
source_id_for
|
SourceIdFn
|
Pull the stable source identifier out of a record.
All current kinds use |
required |
state_path
|
Path
|
Per-resource state file (one file per source+resource). |
required |
dry_run
|
bool
|
Compute the diff without calling the API. |
False
|
force
|
bool
|
Re-publish existing records regardless of payload hash. |
False
|
Source code in src/metadataswiss_connector/sync.py
223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 | |
sync ¶
sync(
client: I14YClient,
source_records: list[dict],
*,
kind: str,
state_path: Path,
dry_run: bool = False,
force: bool = False
) -> SyncResult
Sync transformed records to I14Y for the given resource kind.
Source code in src/metadataswiss_connector/sync.py
493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 | |
purge_records ¶
purge_records(
resource: ResourceClient,
*,
state_path: Path,
dry_run: bool = False
) -> SyncResult
Delete every active record recorded in the state file from I14Y.
Active = not yet marked deleted_at. Soft-deleted entries are
skipped (already considered gone on I14Y per our records).
On success, entries are soft-deleted in the state file.
Source code in src/metadataswiss_connector/sync.py
515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 | |
purge ¶
purge(
client: I14YClient,
*,
kind: str,
state_path: Path,
dry_run: bool = False
) -> SyncResult
Delete every active record of the given resource kind from I14Y.
Source code in src/metadataswiss_connector/sync.py
546 547 548 549 550 551 552 553 554 555 | |
resources ¶
Shared dlt + I14Y client helpers.
duckdb_destination ¶
duckdb_destination()
Shared DuckDB destination used by raw and DCAT pipelines.
Source code in src/metadataswiss_connector/resources.py
15 16 17 | |
raw_pipeline_for ¶
raw_pipeline_for(source: CatalogSource) -> dlt.Pipeline
The dlt pipeline that lands raw data for a given source in DuckDB.
Source code in src/metadataswiss_connector/resources.py
20 21 22 23 24 25 26 | |
i14y_client_from_env ¶
i14y_client_from_env(
logger: Logger | None = None,
) -> I14YClient
Build an I14Y API client from environment-derived config.
logger lets the caller (e.g. a Dagster asset passing
context.log) capture per-request telemetry alongside their own
output. Defaults to the package logger.
Source code in src/metadataswiss_connector/resources.py
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | |