API reference — DCAT layer¶
Source-neutral building of I14Y DCAT models, the transform step over DuckDB, schema introspection, cross-reference lookups and the SHACL/Turtle builder.
Note
dcat/i14y_models.py is intentionally omitted here: it is generated from
the I14Y OpenAPI specification (see the README) and is not meant to be read
as hand-written reference.
builders ¶
I14Y DCAT Input-Model builders.
Helpers that construct typed I14Y *InputModel instances. Each helper
takes the loose, source-side primitives (a string, an epoch timestamp,
a list of tags) and returns a Pydantic model that matches the I14Y
Partner API contract exactly. Validation happens here, not at POST time.
The model classes themselves are generated from the I14Y OpenAPI spec
into i14y_models.py — see that file's header for the regen command.
This module is source-agnostic: it knows the I14Y data model and the
EU-level controlled vocabularies, nothing about any specific catalog
source. Source-specific vocabulary mappings live next to the source
(e.g. sources/dataspot/mappings.py).
html_to_plain_text ¶
html_to_plain_text(value: str | None) -> str | None
Strip HTML markup so the value can be sent as plain text.
Source descriptions may contain inline HTML (<p>…</p>, <br>,
entity references). I14Y stores descriptions as unformatted plain
text, so all tags are removed and whitespace is collapsed.
Source code in src/metadataswiss_connector/dcat/builders.py
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 | |
file_format ¶
file_format(value: str | None) -> CodeInputModel | None
Map a loose file-format string to an I14Y file-type vocabulary code.
I14Y uses the EU file-type authority codes (uppercase).
Returns None for unknown or missing values so the field is omitted.
Source code in src/metadataswiss_connector/dcat/builders.py
102 103 104 105 106 107 108 109 110 111 112 113 | |
frequency ¶
frequency(uri: str | None) -> CodeInputModel | None
Build a CodeInputModel from an EU frequency URI.
Maps to the I14Y-supported VOCAB_EU_FREQUENCY codes; falls back to
OTHER when the value is outside the controlled vocabulary.
Source code in src/metadataswiss_connector/dcat/builders.py
125 126 127 128 129 130 131 132 133 134 135 136 | |
contact_points ¶
contact_points(email: str | None) -> list[VCardModel]
Build a VCardModel list from an email address.
Source code in src/metadataswiss_connector/dcat/builders.py
154 155 156 157 158 | |
epoch_ms_to_datetime ¶
epoch_ms_to_datetime(
epoch_ms: int | float | None,
) -> datetime | None
Convert epoch milliseconds to a timezone-aware datetime.
Source code in src/metadataswiss_connector/dcat/builders.py
167 168 169 170 171 | |
transforms ¶
Generic raw-to-DCAT transform pipeline step.
Reads raw data from DuckDB (loaded by a source-specific dlt pipeline),
applies a source-specific transform function, and loads the result into
the i14y_dcat dataset.
run_transform ¶
run_transform(
pipeline_raw: Pipeline,
resources: "dict[str, ResourceSpec]",
destination,
*,
publisher: str,
extra_lookups: dict[str, list[dict]] | None = None
) -> None
Transform raw resources to I14Y input models and load into DuckDB.
resources maps each raw resource/table name to its ResourceSpec
(transform + target kind), allowing one source to carry multiple
resource types.
Source code in src/metadataswiss_connector/dcat/transforms.py
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 | |
run_transform_resource ¶
run_transform_resource(
pipeline_raw: Pipeline,
resource_name: str,
spec: "ResourceSpec",
destination,
*,
publisher: str,
extra_lookups: dict[str, list[dict]] | None = None
) -> dict
Transform a single raw resource and load into the i14y_dcat dataset.
extra_lookups are synthetic lookup tables merged over the raw
sibling tables before they reach the transform — used by the
pipeline to expose e.g. the published-ID maps from sync state.
Returns a stats dict {valid, invalid, raw_rows, loaded_tables} so
callers (e.g. Dagster assets) can surface it as materialization
metadata and run validation-rate checks against it.
Source code in src/metadataswiss_connector/dcat/transforms.py
102 103 104 105 106 107 108 109 110 111 112 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 142 143 144 145 146 147 | |
duckdb_io ¶
DuckDB read-path for transformed DCAT records.
Re-nests dlt's parent__child flat columns back into nested dicts and
re-attaches child tables so the result matches the I14Y camelCase input
contract and can be fed directly into DcatDatasetInputModel.model_validate().
The dlt-convention plumbing (child-table discovery, _dlt_list_idx
ordering, unflattening) is shared with the transform read path via
dlt_schema.
Also owns the two constants describing how post-sync "extras" sidecar payloads are carried through DuckDB (column name on write, key on read).
read_transformed ¶
read_transformed(
table_name: str, *, limit: int | None = None
) -> list[dict]
Read transformed DCAT records from DuckDB and reconstruct nested dicts.
dlt flattens nested models into parent__child columns. This function
re-nests them so the result matches the I14Y camelCase input contract and
can be fed directly into DcatDatasetInputModel.model_validate().
Source code in src/metadataswiss_connector/dcat/duckdb_io.py
40 41 42 43 44 45 46 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 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 | |
dlt_schema ¶
dlt-schema introspection and re-nesting over DuckDB.
Single home for the dlt naming conventions: child tables named
<parent>__<field>, _dlt_* system columns, list order via
_dlt_list_idx, and a__b__c column flattening. Both read paths —
the transform step (dlt sql_client) and the publish step (raw duckdb
connection) — share the loaders here through a small query adapter, so
rules like "ORDER BY _dlt_list_idx or the payload hash flips" live
in exactly one place.
Loaded shapes:
- scalar children: child tables with a single
valuedata column (dlt's shape forlist[str]fields likekeywords), yielded as bare values, - struct children: child tables with real columns, yielded as re-nested dicts,
- sibling tables: top-level resources in the same dataset, either
wired to a parent via
include_from_parent(carrying a_<parent>_<key>ref column) or unanchored lookup tables.
Source-agnostic — only knows the dlt naming conventions.
duckdb_query ¶
duckdb_query(con: DuckDBPyConnection) -> QueryFn
Adapt a raw duckdb connection to QueryFn.
Source code in src/metadataswiss_connector/dcat/dlt_schema.py
52 53 54 55 56 57 58 59 | |
sql_client_query ¶
sql_client_query(client) -> QueryFn
Adapt a dlt sql_client to QueryFn.
Source code in src/metadataswiss_connector/dcat/dlt_schema.py
62 63 64 65 66 67 68 69 | |
unflatten ¶
unflatten(flat: dict) -> dict
Convert dlt's a__b__c flat keys back into nested dicts.
Example: {"title__de": "Foo"} → {"title": {"de": "Foo"}}
Drops keys whose values are None.
Source code in src/metadataswiss_connector/dcat/dlt_schema.py
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 | |
load_child_tables ¶
load_child_tables(
query: QueryFn,
schema: str,
parent_table: str,
*,
scalars_only: bool = False
) -> dict[str, dict[str, list]]
Discover and load dlt child tables grouped by parent _dlt_id.
Returns {child_field: {parent_dlt_id: [item, ...]}}. Scalar-list
children yield bare values; struct children yield re-nested dicts
(or are skipped entirely with scalars_only). Rows are ordered by
_dlt_list_idx so list elements keep their source order
deterministically across runs — without it DuckDB's physical row
order can vary, perturbing the payload hash and triggering spurious
re-publishes.
Source code in src/metadataswiss_connector/dcat/dlt_schema.py
111 112 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 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 | |
load_scalar_children ¶
load_scalar_children(
client, dataset_name: str, parent_table: str
) -> dict[str, dict[str, list[str]]]
Load dlt scalar-list child tables grouped by parent, via sql_client.
Returns {child_field: {parent_dlt_id: [values]}}. Only child
tables with a single value column (dlt's shape for list[str]
fields) are included — struct children are skipped here.
Source code in src/metadataswiss_connector/dcat/dlt_schema.py
159 160 161 162 163 164 165 166 167 168 169 170 | |
load_sibling_children ¶
load_sibling_children(
client,
dataset_name: str,
parent_table: str,
*,
parent_key: str,
ref_parent: str | None = None
) -> tuple[
dict[str, dict[str, list[dict]]], dict[str, list[dict]]
]
Load top-level sibling tables alongside the parent.
Returns (siblings_by_parent, lookups):
siblings_by_parent: tables wired via dltresolve+include_from_parent; rows carry a_<parent>_<key>column and are grouped by parent id so the caller can attach them under the sibling table's name in thechildrendict.lookups: tables in the same dataset with no parent ref column, loaded in full and keyed by table name. Used by transforms for cross-reference data (e.g. attribution → role/post/person).
dlt derives the ref column from the extract resource the children
resolve from (_<resource>_<key>). ref_parent names that
resource when it differs from parent_table — e.g. when the
transform reads a filtered transformer table (data_products) but
its children were extracted against the broad upstream resource
(data_products_all).
Sibling rows stay dlt-flattened (custom_properties__x columns):
transforms address raw records by their flat keys.
Source code in src/metadataswiss_connector/dcat/dlt_schema.py
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 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 | |
lookups ¶
Cross-reference lookup tables for one transform run.
Lookups wraps the {table: [rows]} dict produced by
dlt_schema.load_sibling_children (plus any synthetic tables injected
by the pipeline) behind a read-only mapping with memoized per-key
indexes. Transforms are called once per record but share one instance
across the whole run, so by/unique_by replace repeated
full-table scans (O(records × rows)) with a single index build per
(table, key).
Lookups ¶
Bases: Mapping[str, list[dict]]
Read-only view over the lookup tables of one transform run.
Source code in src/metadataswiss_connector/dcat/lookups.py
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 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 | |
by ¶
by(table: str, key: str) -> dict[Any, list[dict]]
Rows of table grouped by row[key] (memoized).
Rows without the key (or with a None value) are dropped; an unknown table yields an empty index.
Source code in src/metadataswiss_connector/dcat/lookups.py
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | |
unique_by ¶
unique_by(table: str, key: str) -> dict[Any, dict]
One row of table per row[key] (memoized).
For 1:1 reference tables (collection_agencies,
collections, …). Later rows win on duplicate keys, mirroring
a dict comprehension over the raw rows.
Source code in src/metadataswiss_connector/dcat/lookups.py
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 | |
published_ids_table ¶
published_ids_table(resource_name: str) -> str
Name of the synthetic lookup carrying a resource's published I14Y ids.
Injected by the pipeline (see pipeline.published_id_lookups) with
rows {"source_id": ..., "i14y_id": ...} so transforms can link to
already-published sibling records without reading sync state.
Source code in src/metadataswiss_connector/dcat/lookups.py
18 19 20 21 22 23 24 25 | |
shacl ¶
Source-neutral SHACL/Turtle builder for I14Y dataset structure imports.
I14Y accepts a SHACL Turtle document at
POST /datasets/{id}/structures/imports describing a dataset's
attributes (columns). This module renders that document from a list of
StructureComponent dicts — one per attribute — without knowing
anything about where the components came from.
Per-source adapters (e.g. sources/dataspot/structure.py) translate
their own raw rows into StructureComponent dicts and call
build_shacl_turtle from here. To wire up a second source, write that
adapter — no changes to this file should be needed.
The Turtle is hand-built rather than via rdflib to avoid a heavy dependency: the output shape is small, deterministic, and only needs to escape string literals correctly.
StructureComponent ¶
Bases: TypedDict
One sh:property block worth of fields.
path_iri and xsd_datatype are required; all other keys are
optional and emitted only when present. min_count/max_count
follow SHACL semantics (min_count=0 means optional, max_count=1
means single-valued). conforms_to_iri is typically the I14Y
concept IRI for a code-list-typed attribute.
Source code in src/metadataswiss_connector/dcat/shacl.py
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | |
build_shacl_turtle ¶
build_shacl_turtle(
*,
subject_iri: str,
subject_label: str | None,
components: list[StructureComponent]
) -> str | None
Render a SHACL NodeShape for subject_iri.
Returns None when components is empty, so callers can skip
the upload entirely. Component order in the input list determines
block order in the output; sort upstream if you need a specific
sh:order sequence.
Source code in src/metadataswiss_connector/dcat/shacl.py
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 | |