{ "cells": [ { "cell_type": "markdown", "id": "0", "metadata": {}, "source": "# The `MetadataParser` Classes\n\nA `MetadataParser` extracts BDF metadata fields (such as `start_time`) from a data file.\nThe base `MetadataParser` never matches, and returns nothing; concrete subclasses own all file I/O for their source type.\n\nTwo concrete parsers are provided:\n- **`TxtPreambleParser`** \u2013 reads metadata from the head bytes of the data file itself (magic token + regex extraction)\n- **`JsonSidecarParser`** \u2013 reads metadata from a `.metadata.json` file adjacent to the data file" }, { "cell_type": "code", "execution_count": null, "id": "1", "metadata": {}, "outputs": [], "source": [ "import re\n", "import json\n", "import tempfile\n", "from pathlib import Path\n", "\n", "from bdf.metadata_parsers import MetadataParser, TxtPreambleParser, JsonSidecarParser, MetadataSchema\n", "from bdf.file_utils import resolve_source\n", "\n", "BIOLOGIC_URL = (\n", " \"https://zenodo.org/api/records/18986774/files/\"\n", " \"SINTEF__NaCR32140-MP10-04__2025-08-25__GITT_0p05C_25degC__BioLogic.mpt/content\"\n", ")" ] }, { "cell_type": "code", "execution_count": null, "id": "2", "metadata": {}, "outputs": [], "source": [ "# The base MetadataParser never matches and extracts nothing\n", "parser = MetadataParser()\n", "biologic_file = resolve_source(BIOLOGIC_URL)\n", "\n", "print(\"matches():\", parser.matches(biologic_file))\n", "print(\"parse(): \", parser.parse(biologic_file))" ] }, { "cell_type": "markdown", "id": "3", "metadata": {}, "source": [ "## `MetadataSchema` \u2014 the field name registry\n", "\n", "`MetadataSchema` is a generic Pydantic model that declares one field per supported BDF metadata field.\n", "It is the single source of truth for metadata field names (analogous to `TableNormalizer`'s `mr_name` fields).\n", "\n", "Because the model is configured with `extra=\"forbid\"`, a typo in a field name raises a validation\n", "error at construction time rather than silently producing an empty parser." ] }, { "cell_type": "code", "execution_count": null, "id": "4", "metadata": {}, "outputs": [], "source": [ "# extra=\"forbid\" catches typos immediately\n", "try:\n", " MetadataSchema(strat_time=\"typo\") # misspelled field name\n", "except Exception as e:\n", " print(\"Caught:\", type(e).__name__)\n", "\n", "# Available field names\n", "print(\"BDF metadata fields:\", list(MetadataSchema.model_fields.keys()))" ] }, { "cell_type": "markdown", "id": "5", "metadata": {}, "source": [ "## `TxtPreambleParser` \u2014 extract metadata from the file preamble\n", "\n", "Construct a `TxtPreambleParser` with:\n", "- **`magic`** \u2013 one or more tokens; `matches()` returns `True` when any token is found in the file's head bytes\n", "- **`regex_patterns`** \u2013 a `MetadataSchema` mapping each desired field to a compiled regex whose `group(1)` is the extracted value" ] }, { "cell_type": "code", "execution_count": null, "id": "6", "metadata": {}, "outputs": [], "source": [ "preamble_parser = TxtPreambleParser(\n", " magic=(\"BT-Lab ASCII FILE\",),\n", " regex_patterns=MetadataSchema(\n", " start_time=re.compile(r\"Acquisition started on\\s*:\\s*(.+)\"),\n", " ),\n", ")\n", "preamble_parser" ] }, { "cell_type": "code", "execution_count": null, "id": "7", "metadata": {}, "outputs": [], "source": [ "# matches() scans head bytes for the magic token\n", "preamble_parser.matches(biologic_file)" ] }, { "cell_type": "code", "execution_count": null, "id": "8", "metadata": {}, "outputs": [], "source": [ "# parse() applies the regex patterns and returns the extracted values\n", "preamble_parser.parse(biologic_file)" ] }, { "cell_type": "markdown", "id": "9", "metadata": {}, "source": "## `JsonSidecarParser` \u2014 extract metadata from an adjacent JSON file\n\n`JsonSidecarParser` looks for a `.metadata.json` file at `path.with_suffix(\".metadata.json\")`.\n`key_synonyms` maps each BDF field to an ordered tuple of candidate JSON keys; the first matching key wins.\n\nBelow we write a temporary sidecar file to demonstrate the API, then clean it up." }, { "cell_type": "code", "execution_count": null, "id": "10", "metadata": {}, "outputs": [], "source": "sidecar_parser = JsonSidecarParser(\n key_synonyms=MetadataSchema(\n start_time=(\"acquisition_started_on\", \"start_time\"),\n ),\n)\n\n# Create a temp data file and write a .metadata.json sidecar alongside it\nwith tempfile.NamedTemporaryFile(suffix=\".data\", delete=False) as tf:\n data_path = Path(tf.name)\n\nsidecar_path = data_path.with_suffix(\".metadata.json\")\ntry:\n sidecar_path.write_text(json.dumps({\"acquisition_started_on\": \"05/13/2024 11:19:51.602\"}))\n print(\"matches():\", sidecar_parser.matches(data_path))\n print(\"parse(): \", sidecar_parser.parse(data_path))\nfinally:\n data_path.unlink(missing_ok=True)\n sidecar_path.unlink(missing_ok=True)" } ], "metadata": { "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.8" } }, "nbformat": 4, "nbformat_minor": 5 }