Migrating Schemas to SDK 0.6.0
SDK 0.6.0 changes how schema.yaml files are dispatched and validated. This
guide explains what changed, how to tell whether a schema is affected, and how
to migrate it — organised by loading strategy, with a before/after example for
each case.
What changed in 0.6.0
Before 0.6.0, the task field selected the loader (ASR, TTS, OTH),
each task loader supported only some strategies, and a missing root_strategy
silently fell back to index-based loading.
Since 0.6.0:
-
root_strategyis required. There is no default. A schema without it fails with:ValueError: Schema must specify 'root_strategy'. Supported strategies: index, multi_split, multi_sections, paired_glob, glob -
Dispatch is strategy-based and task-agnostic. The
root_strategyfield alone selects the loader; any strategy can be combined with any task (e.g.TTS+multi_splitis now valid). -
taskis optional and only validates. When set to a task with a known contract, the loaded DataFrame is expected to contain that task's logical columns:Task Required columns in the loaded DataFrame ASRaudio_path,transcriptionTTSaudio_path,transcriptionLLMtextOTH(or any other value)no validation A violation emits a
TaskValidationWarninglisting the missing and available columns — the DataFrame is still returned. The warning is issued through Python'swarningsmodule, so it is shown even when package logging is disabled (enable_logging=False). -
multi_sectionsnow appliescolumns. Previously column mappings were silently ignored for this strategy; now they are applied when declared (thesectioncolumn is kept). -
Unknown
root_strategyvalues now error. Previously a typo (e.g.root_strategy: "multisplit") silently fell through to index-based loading; now it raisesValueError: Unknown root_strategy 'multisplit'. Supported strategies: …. -
content_mappingis removed. The field was never consumed by any loader ("reserved for future use"); its use case is covered by the glob strategy'scolumnsmapping. Schemas still carrying the block parse fine — it lands in the schema'sextracatch-all and is ignored, so no migration is needed beyond optionally deleting it. -
index_fileresolution is deterministic. The literal path relative to the dataset root wins when it exists; otherwise the recursive search picks the shallowest match as before, but multiple matches at the same depth now raise an error instead of silently picking one. Affected archives (equal-depth duplicate index files) must setindex_fileto an explicit relative path. A new optionalstrict: truefield additionally disables the recursive search and fuzzy column-name matching. -
Schemas are validated at parse time. Unknown
dtypevalues (e.g."flaot") and unknown keys inside a column mapping entry (e.g.dtpye:) now raise aValueErrorat parse instead of being silently ignored or cast to string. Unknownroot_strategyvalues fail at parse rather than at load. Unknown top-level keys still parse, but emit aSchemaValidationWarningwith a "did you mean …?" hint and land in theextracatch-all. -
Separator sniffing is removed. Pre-0.6.0, a file whose declared or inferred separator parsed into a single column was re-parsed with a guessed delimiter. Now the declared separator is authoritative: a schema that relied on sniffing fails with "Required column … not found" listing the mushed single-column header. Fix by declaring the real separator explicitly (e.g.
separator: ";"). -
Dirty data warns instead of passing silently (
DataLoadWarning). While loading, the SDK now emits aDataLoadWarning(visible even withenable_logging=False) when values are lost or unresolved:file_pathvalues (defaultdirectstrategy) that resolve to no existing file — the constructed paths are kept as-is, but the warning reports how many missed (usually a wrongbase_audio_pathorfile_extension);file_contentvalues that resolve to no existing file — those cells are now set to missing instead of silently containing a path string;int/floatcells that cannot be parsed — set to missing, with example offending values listed;- paired-glob text sidecars with no matching audio file — skipped, with
a count and examples (usually a wrong
audio_extension).
exact/containspath search misses still raiseFileNotFoundError. Related output fix: the defaultstringdtype now preserves missing values instead of turning them into the literal string"nan".
Keep the
taskfield. Even though 0.6.0 no longer needs it for dispatch, SDK versions before 0.6.0 requiretaskand use it to select the loader. Registry schemas must keep it so both old and new SDKs can load the dataset.
Migration checklist
For every schema.yaml in the registry:
- Add
root_strategyif it is missing. Pre-0.6.0, only index-based schemas could omit it, so a schema withoutroot_strategybecomesroot_strategy: "index". - Check the task contract. If
taskisASRorTTS, the loaded DataFrame should containaudio_pathandtranscription(every load emits aTaskValidationWarningotherwise):- If the schema declares
columns, those two must appear as logical column names (the keys of thecolumnsmapping). Rename keys likeaudio→audio_pathortext→transcriptionif needed. - If the schema declares no
columns, the raw file headers must already be namedaudio_pathandtranscription— otherwise add acolumnsblock that maps them.
- If the schema declares
- Fix silent misconfigurations that 0.6.0 now surfaces: typo'd
root_strategyvalues, typo'ddtypevalues or column-mapping keys (now parse errors), and inertcolumnsblocks onmulti_sectionsschemas (now applied — verify the mappings are correct or remove them). - Declare the separator explicitly (
separator: ";", or a correctformat) for any schema whose index file previously loaded only thanks to separator sniffing — sniffing is gone, and a wrong separator now surfaces as "Required column … not found". - Verify every migrated schema against its extracted archive with the recipe in Verifying a migrated schema — a clean run loads with zero warnings.
The sections below walk through each strategy.
Case 1: Index-based schemas
Who is affected: every schema without a root_strategy field. This was
the implicit default, so this is the most common case.
Migration: add root_strategy: "index". If the task is ASR/TTS, also
make sure the logical column names include audio_path and transcription.
Symptom if not migrated:
ValueError: Schema must specify 'root_strategy'. …
Before (pre-0.6.0)
dataset_id: "my-asr-dataset"
task: "ASR"
format: "tsv"
index_file: "metadata.tsv"
base_audio_path: "clips/"
columns:
audio: # ← non-contract logical name
source_column: "path"
dtype: "file_path"
text: # ← non-contract logical name
source_column: "sentence"
dtype: "string"
After (0.6.0)
dataset_id: "my-asr-dataset"
root_strategy: "index" # ← now required, no default
task: "ASR"
format: "tsv"
index_file: "metadata.tsv"
base_audio_path: "clips/"
columns:
audio_path: # ← renamed to satisfy the ASR contract
source_column: "path"
dtype: "file_path"
transcription: # ← renamed to satisfy the ASR contract
source_column: "sentence"
dtype: "string"
Renaming only changes the logical (output) column names — the
source_column values stay whatever the file actually contains. If the old
logical names had downstream consumers, note that the loaded DataFrame's
column names change with them.
Sub-case: index schema without columns (raw loading)
Pre-0.6.0, TTS and OTH index schemas could omit columns and return the
raw file. That still works in 0.6.0, but a raw ASR/TTS schema now
emits a TaskValidationWarning on every load unless the raw headers happen
to be named audio_path and transcription. Add a minimal columns block:
# Before — loaded the raw file, columns were e.g. "wav" and "sentence"
dataset_id: "my-tts-dataset"
task: "TTS"
index_file: "meta.csv"
# After — explicit strategy + mappings that satisfy the TTS contract
dataset_id: "my-tts-dataset"
root_strategy: "index"
task: "TTS"
index_file: "meta.csv"
columns:
audio_path:
source_column: "wav"
dtype: "file_path"
transcription:
source_column: "sentence"
dtype: "string"
For OTH (or any task without a contract), adding root_strategy: "index"
is the only required change — raw loading without columns keeps working.
Case 2: Multi-split schemas (root_strategy: "multi_split")
Who is affected: schemas already set root_strategy: "multi_split", so no
dispatch change is needed. Only the task contract applies.
Migration:
- With
columns: ensure the logical names includeaudio_pathandtranscription(same rename as Case 1). - Without
columns: the raw split files' headers must already containaudio_pathandtranscription; otherwise add acolumnsblock. (Common Voice-style files withpath/sentenceheaders need mappings.)
Symptom if not migrated: a TaskValidationWarning on every load (the
DataFrame is still returned).
Before (pre-0.6.0)
dataset_id: "my-multisplit-dataset"
task: "ASR"
root_strategy: "multi_split"
splits: ["train", "dev", "test"]
splits_file_pattern: "**/*.tsv"
# no columns → raw file headers (path, sentence, …) + a "split" column
After (0.6.0)
dataset_id: "my-multisplit-dataset"
task: "ASR"
root_strategy: "multi_split"
splits: ["train", "dev", "test"]
splits_file_pattern: "**/*.tsv"
base_audio_path: "clips/"
columns: # ← added so the output satisfies the ASR contract
audio_path:
source_column: "path"
dtype: "file_path"
transcription:
source_column: "sentence"
dtype: "string"
The split column is added automatically in both cases.
New in 0.6.0:
multi_splitis no longer ASR-only — it can be used with any task.
Case 3: Multi-sections schemas (root_strategy: "multi_sections")
Who is affected: all of them, in two ways:
- Pre-0.6.0 this strategy ignored
columnsand always returned the raw per-section index columns plussection. In 0.6.0,columnsis applied when declared. - These schemas were TTS-only, and the TTS contract now expects
audio_path+transcriptionin the output. Raw section files rarely use those exact header names, so mostmulti_sectionsschemas withtask: "TTS"need acolumnsblock added to load without warnings.
Migration: add a columns block mapping the section files' headers to
audio_path/transcription. If a schema already carried an (inert) columns
block, verify the mappings are actually correct — they now take effect.
Symptom if not migrated: a TaskValidationWarning: Loaded dataset does
not satisfy the 'TTS' task contract: missing column(s) ['audio_path',
'transcription'] … on every load (the DataFrame is still returned).
Before (pre-0.6.0)
Layout: dataset/General/metadata.tsv, dataset/Chat/metadata.tsv, each with
headers audio and text.
dataset_id: "my-sections-dataset"
task: "TTS"
root_strategy: "multi_sections"
section_root: "dataset"
sections: ["General", "Chat"]
index_file: "metadata.tsv"
format: "tsv"
# → raw columns: audio, text, section
After (0.6.0)
dataset_id: "my-sections-dataset"
task: "TTS"
root_strategy: "multi_sections"
section_root: "dataset"
sections: ["General", "Chat"]
index_file: "metadata.tsv"
format: "tsv"
columns: # ← now applied; output: audio_path, transcription, section
audio_path:
source_column: "audio"
dtype: "file_path"
transcription:
source_column: "text"
dtype: "string"
The section column is always kept, with or without mappings.
Case 4: Paired-glob text schemas (root_strategy: "paired_glob", text sidecars)
Who is affected: nobody — no migration needed.
These schemas already set root_strategy, and the loader constructs the
output columns itself (audio_path, transcription, split), so the
ASR/TTS contract is satisfied by construction.
Valid before and after
dataset_id: "my-paired-tts-dataset"
task: "TTS"
root_strategy: "paired_glob"
file_pattern: "**/*.txt"
audio_extension: ".webm"
New in 0.6.0: the text-sidecar variant is no longer TTS-only. Pre-0.6.0,
task: "ASR"withpaired_globdemandedformat: "json"; now an ASR dataset with.txtsidecars is expressible with exactly the schema above (just withtask: "ASR").
Also new in 0.6.0 (optional): the text variant now applies a declared
columns block over its derived audio_path / transcription / split
sources (rename, dtype, drop; split is kept). Pre-0.6.0 such a block was
silently ignored — existing schemas do not carry one, so no migration is
needed. Text files skipped for lack of a paired audio file now emit a
DataLoadWarning with a count instead of disappearing silently — treat that
warning during verification as a probable audio_extension mistake.
Case 5: Paired-glob JSON schemas (root_strategy: "paired_glob", format: "json")
Who is affected: only schemas whose columns use non-contract logical
names. root_strategy and format: "json" were already required, and the
required fields are unchanged (file_pattern, columns; optional
record_path).
Migration: verify the columns keys include audio_path and
transcription when task is ASR/TTS; rename the logical keys if not
(same as Case 1).
Symptom if not migrated: a TaskValidationWarning on every load,
listing the missing contract columns (the DataFrame is still returned).
Valid before and after
dataset_id: "my-json-asr-dataset"
task: "ASR"
root_strategy: "paired_glob"
format: "json"
file_pattern: "**/*.merged.json"
record_path: "transcriptions"
columns:
audio_path: # ← contract column
source_column: "audio.filename"
dtype: "file_path"
path_match_strategy: "exact"
transcription: # ← contract column
source_column: "text"
dtype: "string"
speaker_id:
source_column: "speaker"
dtype: "category"
optional: true
Case 6: Glob schemas (root_strategy: "glob")
Who is affected: nobody with task: "OTH" — no migration needed.
root_strategy was already required for glob, the fields are unchanged, and
OTH has no contract, and the pre-0.6.0 fields work unchanged: without
columns, the loader keeps producing its default output (audio_path,
language, speaker_id, and split when splits is set).
New in 0.6.0 (optional): glob schemas may declare a columns mapping over
path-derived sources (path, name, stem, parent, parents[N],
content) to control the output columns instead of relying on the default —
see the glob strategy page. Existing schemas do not need
this to migrate.
Valid before and after
dataset_id: "my-glob-dataset"
task: "OTH"
root_strategy: "glob"
file_pattern: "**/*.wav"
splits: ["Train", "Dev"]
extract_files:
- "Train.tar.gz"
- "Dev.tar.gz"
Caution: avoid combining
globwithtask: "ASR"ortask: "TTS"unless acolumnsmapping actually produces the contract columns — the default glob output has notranscriptioncolumn, so every load would emit aTaskValidationWarning.
Verifying a migrated schema
Test the schema locally against the extracted dataset before submitting it to the registry. Escalating all SDK warnings to errors makes every problem a hard failure:
import warnings
from pathlib import Path
from datacollective.schema import _parse_schema
from datacollective.schema_loaders.registry import _load_dataset_from_schema
warnings.simplefilter("error", UserWarning) # any SDK warning → hard failure
schema = _parse_schema(Path("path/to/extracted/schema.yaml"))
df = _load_dataset_from_schema(schema, extract_dir=Path("path/to/extracted/"))
print(df.head())
print(df.dtypes)
assert len(df) > 0
A migrated schema is correct when the snippet completes with no error and no warning. Each failure mode points at a specific fix:
| Signal | Meaning | Fix |
|---|---|---|
ValueError: Schema must specify 'root_strategy' |
Checklist step 1 missed. | Add root_strategy. |
ValueError: Unknown root_strategy … / unknown dtype / unknown mapping key |
Typo in the schema (parse-time validation). | Correct the value/key. |
SchemaValidationWarning: Unknown schema key 'X' — did you mean 'Y'? |
Misspelled top-level key, silently ignored. | Rename it (or delete leftovers like content_mapping). |
ValueError: Ambiguous index_file … |
Duplicate index files at equal depth. | Set index_file to an explicit relative path. |
KeyError: Required column … not found listing one mushed header |
Wrong separator (sniffing no longer rescues it). | Declare separator / fix format. |
TaskValidationWarning: … missing column(s) […] |
Output misses the task contract. | Rename logical columns / add a columns block (checklist step 2). |
DataLoadWarning: Column 'X': N of M values did not resolve … |
Audio/content paths don't resolve. | Fix base_audio_path, file_extension, path_template, or path_match_strategy. |
DataLoadWarning: … could not be parsed as int/float |
Wrong dtype for the column's actual values. |
Fix the dtype or accept missing values. |
DataLoadWarning: … had no paired '<ext>' audio file |
Text sidecars skipped. | Fix audio_extension. |
For fully deterministic schemas, additionally set strict: true (literal
index_file path, exact column names) and confirm the load still passes.
Cross-version caveats
Because the migrated schema keeps task and only adds fields that pre-0.6.0
loaders either required anyway (columns renames) or ignored
(root_strategy: "index" on index schemas), sanity-check the pre-0.6.0
behavior too if the dataset must stay loadable by older SDKs. Fields that
old SDKs parse but ignore produce diverging output between versions:
columnsonmulti_sectionsschemas: old SDKs return the raw section columns, 0.6.0 returns the mapped ones;columnsonglobor paired-glob text schemas: old SDKs return the default output, 0.6.0 returns the mapped one;strictis ignored entirely by old SDKs (they keep searching/fuzzy-matching).
Users upgrading do not need to clear previously extracted datasets: a
schema.yaml cached next to an archive by an SDK before 0.6.0 has no
root_strategy, so 0.6.0 treats it as a cache miss and re-downloads the
migrated registry schema even when the archive checksum still matches.
Prefer migrations that avoid these fields unless the divergence is acceptable, or accept that pre-0.6.0 users see the old column names until they upgrade.
Field reference
The complete per-strategy field tables live in Schema-Based Loading, with full examples on the per-strategy pages under docs/loaders/.