Schemas & normalization¶
A schema is an asset's data contract; a normalizer coerces the vendor's rows into it and a materialization strategy decides how they land.
schema
¶
Create a Schema subclass from a decorated class.
Can be used bare or with arguments:
@schema
class UserSchema:
id: int
name: str
email: str
@schema(key="custom", name="Custom Schema")
class OtherSchema:
value: float
The decorated class's annotations and attributes become the Schema subclass
body. key overrides the auto-derived snake_cased key and name is the
human-readable display name.
Schema
¶
Bases: interloper.serializable.base.Serializable
Defines the expected output structure of an asset.
Subclass to declare output fields:
Class methods provide schema operations on list[dict] data:
key
¶
str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str
Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to 'strict'.
model_config
¶
model_config = {'arbitrary_types_allowed': True, 'ignored_types': (<class 'interloper.serializable.base.IgnoredDescriptor'>,)}
dict() -> new empty dictionary dict(mapping) -> new dictionary initialized from a mapping object's (key, value) pairs dict(iterable) -> new dictionary initialized as if via: d = {} for k, v in iterable: d[k] = v dict(**kwargs) -> new dictionary initialized with the name=value pairs in the keyword argument list. For example: dict(one=1, two=2)
field_specs
¶
Extract backend-agnostic field specs from this schema.
Fields declared by Schema itself (framework plumbing, if
any) are excluded — only the subclass's data columns count. Fields
come out in declaration order: Serializable normalizes
model_fields so a column shadowing a parent ClassVar (name,
key) is not hoisted out of place.
Returns:
| Type | Description |
|---|---|
list[FieldSpec]
|
One |
infer
¶
Infer a Schema subclass from a list of row dicts.
Examines the values across all rows for each key and maps Python types
to Pydantic field types. All fields are Optional because any key
may be absent in some rows.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
rows
|
list[dict[str, Any]]
|
Non-empty list of dicts to infer from. |
required |
Returns:
| Type | Description |
|---|---|
type[Self]
|
A dynamically created Schema subclass. |
Raises:
| Type | Description |
|---|---|
SchemaError
|
If rows is empty. |
json_schema
¶
JSON Schema for this schema's data fields only.
Like pydantic.BaseModel.model_json_schema but restricted to
the subclass's data columns, in declaration order (see
field_specs).
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
A JSON Schema dict whose |
reconcile
¶
Reconcile rows against this schema.
Fields are coerced one by one through per-field TypeAdapters —
the row-wise mirror of the DataFrame representation's column-wise casts.
Extra keys are dropped (their union logged as a warning), missing
fields get their default (or None, which pydantic accepts only
for nullable fields), and scalar str fields are stringified
up front since pydantic's lax mode never coerces to str.
This is more permissive than validate_rows: it actively
transforms data to match the schema rather than rejecting mismatches.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
rows
|
list[dict[str, Any]]
|
List of row dicts. |
required |
Returns:
| Type | Description |
|---|---|
list[dict[str, Any]]
|
A new list of row dicts with columns aligned and types coerced. |
Raises:
| Type | Description |
|---|---|
SchemaError
|
If a value cannot be coerced (e.g. |
validate_rows
¶
Validate each row against this schema.
Stops at the first row that fails validation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
rows
|
list[dict[str, Any]]
|
List of row dicts. |
required |
strict
|
bool
|
When |
False
|
Raises:
| Type | Description |
|---|---|
SchemaError
|
If any row fails validation. |
Normalizer
¶
Bases: interloper.serializable.base.Serializable
Type-native normalizer for list[dict] asset data.
Applies optional transformations to rows (column-name normalization, nested-dict flattening, missing-column fill). The shapes an asset may return besides rows (a generator, models, a lone dict) are unwrapped by the asset before normalization, so a normalizer only ever reshapes rows.
Normalizer is Serializable so instances round-trip through
Spec with their concrete subclass intact — e.g. across the
host → child-pod DAG-spec boundary.
Usage:
@asset(normalizer=Normalizer())
def my_asset(context):
return [{"UserName": "alice", "Address": {"City": "NYC"}}]
Attributes:
| Name | Type | Description |
|---|---|---|
normalize_columns_names |
Convert column names to snake_case. |
|
flatten_max_level |
Maximum nesting depth to flatten. |
|
flatten_separator |
Separator for flattened key names. |
|
fill_missing |
Fill missing keys across rows with |
|
replace_empty_dicts |
Replace |
|
replace_empty_strings |
Replace |
|
drop_na_columns |
Drop columns where every value is |
|
snake_case_digits |
Treat digit groups as words when snake-casing
column names ( |
|
column_overrides |
Explicit raw-name → normalized-name mapping, applied
before the snake_case conversion. For API quirk names no general
rule can handle ( |
key
¶
str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str
Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to 'strict'.
model_config
¶
model_config = {'arbitrary_types_allowed': True, 'ignored_types': (<class 'interloper.serializable.base.IgnoredDescriptor'>,)}
dict() -> new empty dictionary dict(mapping) -> new dictionary initialized from a mapping object's (key, value) pairs dict(iterable) -> new dictionary initialized as if via: d = {} for k, v in iterable: d[k] = v dict(**kwargs) -> new dictionary initialized with the name=value pairs in the keyword argument list. For example: dict(one=1, two=2)
column_name
¶
Transform a column name according to the normalizer's convention.
Explicit column_overrides win; otherwise converts to
snake_case, optionally splitting letter→digit boundaries
(see snake_case_digits).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Original column name. |
required |
Returns:
| Type | Description |
|---|---|
str
|
Transformed column name. |
normalize
¶
Normalize rows with the configured transformations.
Applies flatten, column rename, and fill-missing in order.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
Any
|
The rows to normalize. |
required |
Returns:
| Type | Description |
|---|---|
list[dict[str, Any]]
|
Normalized list of row dicts. |
Raises:
| Type | Description |
|---|---|
NormalizerError
|
If data is not a list of rows. |
MaterializationStrategy
¶
Bases: builtins.str, enum.Enum
How the conform step enforces an asset's schema.
Both strategies hand destinations data in the schema's canonical types; they differ in what counts as an error.
Attributes:
| Name | Type | Description |
|---|---|---|
RECONCILE |
The default. Align columns to the schema (extras dropped with a warning, missing nullables filled) and coerce values. With no schema declared, infer one from the data for the destinations. |
|
STRICT |
Schema required. Extra columns, missing required columns and values the schema rejects fail the materialization; values that pass are still coerced to the declared types. |
RECONCILE
¶
How the conform step enforces an asset's schema.
Both strategies hand destinations data in the schema's canonical types; they differ in what counts as an error.
Attributes:
| Name | Type | Description |
|---|---|---|
RECONCILE |
The default. Align columns to the schema (extras dropped with a warning, missing nullables filled) and coerce values. With no schema declared, infer one from the data for the destinations. |
|
STRICT |
Schema required. Extra columns, missing required columns and values the schema rejects fail the materialization; values that pass are still coerced to the declared types. |
STRICT
¶
How the conform step enforces an asset's schema.
Both strategies hand destinations data in the schema's canonical types; they differ in what counts as an error.
Attributes:
| Name | Type | Description |
|---|---|---|
RECONCILE |
The default. Align columns to the schema (extras dropped with a warning, missing nullables filled) and coerce values. With no schema declared, infer one from the data for the destinations. |
|
STRICT |
Schema required. Extra columns, missing required columns and values the schema rejects fail the materialization; values that pass are still coerced to the declared types. |