The MetadataParser Classes#
A MetadataParser extracts BDF metadata fields (such as start_time) from a data file.
The base MetadataParser never matches, and returns nothing; concrete subclasses own all file I/O for their source type.
Two concrete parsers are provided:
TxtPreambleParser– reads metadata from the head bytes of the data file itself (magic token + regex extraction)JsonSidecarParser– reads metadata from a.metadata.jsonfile adjacent to the data file
import re
import json
import tempfile
from pathlib import Path
from bdf.metadata_parsers import MetadataParser, TxtPreambleParser, JsonSidecarParser, MetadataSchema
from bdf.file_utils import resolve_source
BIOLOGIC_URL = (
"https://zenodo.org/api/records/18986774/files/"
"SINTEF__NaCR32140-MP10-04__2025-08-25__GITT_0p05C_25degC__BioLogic.mpt/content"
)
# The base MetadataParser never matches and extracts nothing
parser = MetadataParser()
biologic_file = resolve_source(BIOLOGIC_URL)
print("matches():", parser.matches(biologic_file))
print("parse(): ", parser.parse(biologic_file))
matches(): False
parse(): {}
MetadataSchema — the field name registry#
MetadataSchema is a generic Pydantic model that declares one field per supported BDF metadata field.
It is the single source of truth for metadata field names (analogous to TableNormalizer’s mr_name fields).
Because the model is configured with extra="forbid", a typo in a field name raises a validation
error at construction time rather than silently producing an empty parser.
# extra="forbid" catches typos immediately
try:
MetadataSchema(strat_time="typo") # misspelled field name
except Exception as e:
print("Caught:", type(e).__name__)
# Available field names
print("BDF metadata fields:", list(MetadataSchema.model_fields.keys()))
Caught: ValidationError
BDF metadata fields: ['start_time']
TxtPreambleParser — extract metadata from the file preamble#
Construct a TxtPreambleParser with:
magic– one or more tokens;matches()returnsTruewhen any token is found in the file’s head bytesregex_patterns– aMetadataSchemamapping each desired field to a compiled regex whosegroup(1)is the extracted value
preamble_parser = TxtPreambleParser(
magic=("BT-Lab ASCII FILE",),
regex_patterns=MetadataSchema(
start_time=re.compile(r"Acquisition started on\s*:\s*(.+)"),
),
)
preamble_parser
TxtPreambleParser(kind='txt_preamble', magic=('BT-Lab ASCII FILE',), encoding='utf-8', regex_patterns=MetadataSchema[Pattern[str]](start_time=re.compile('Acquisition started on\\s*:\\s*(.+)')))
# matches() scans head bytes for the magic token
preamble_parser.matches(biologic_file)
True
# parse() applies the regex patterns and returns the extracted values
preamble_parser.parse(biologic_file)
{'start_time': '06/23/2025 09:17:30.015'}
JsonSidecarParser — extract metadata from an adjacent JSON file#
JsonSidecarParser looks for a .metadata.json file at path.with_suffix(".metadata.json").
key_synonyms maps each BDF field to an ordered tuple of candidate JSON keys; the first matching key wins.
Below we write a temporary sidecar file to demonstrate the API, then clean it up.
sidecar_parser = JsonSidecarParser(
key_synonyms=MetadataSchema(
start_time=("acquisition_started_on", "start_time"),
),
)
# Create a temp data file and write a .metadata.json sidecar alongside it
with tempfile.NamedTemporaryFile(suffix=".data", delete=False) as tf:
data_path = Path(tf.name)
sidecar_path = data_path.with_suffix(".metadata.json")
try:
sidecar_path.write_text(json.dumps({"acquisition_started_on": "05/13/2024 11:19:51.602"}))
print("matches():", sidecar_parser.matches(data_path))
print("parse(): ", sidecar_parser.parse(data_path))
finally:
data_path.unlink(missing_ok=True)
sidecar_path.unlink(missing_ok=True)
matches(): True
parse(): {'start_time': '05/13/2024 11:19:51.602'}