{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "\n# Writing Datasets with Attributes\n\nAttach metadata attributes to HDF5 and HDF4 datasets, and understand datatype\nrestrictions specific to each format.\n\nThis example demonstrates how to attach key-value metadata attributes to PSI-style\nHDF datasets using the ``**kwargs`` interface of :func:`~psi_io.psi_io.write_hdf_data`.\nIt also illustrates the datatype restrictions imposed by the HDF4 format and how to\nhandle attribute write failures gracefully using the ``strict`` parameter.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "import tempfile\nfrom pathlib import Path\nimport numpy as np\nfrom psi_io import write_hdf_data, read_hdf_meta"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Construct a simple 2D dataset with coordinate scales, representing a binary\ncoronal hole map in (\u03b8, \u03c6):\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "nt, np_ = 180, 360\nt = np.linspace(0.0, np.pi, nt, dtype=np.float32)\np = np.linspace(0.0, 2*np.pi, np_, dtype=np.float32)\nchmap = np.zeros((np_, nt), dtype=np.float32)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "**Writing attributes to an HDF5 file**\n\nAttributes are passed as keyword arguments to :func:`~psi_io.psi_io.write_hdf_data`.\nFor HDF5 files, attribute values are stored via :mod:`h5py`, which accepts most\nPython and NumPy types without restriction:\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "with tempfile.TemporaryDirectory() as tmpdir:\n    out_h5 = Path(tmpdir) / \"chmap.h5\"\n    write_hdf_data(out_h5, chmap, t, p,\n                   description=\"Coronal Hole Map\",\n                   source=\"synthetic\",\n                   resolution=np.float32(1.0),\n                   cr_number=np.int32(2190))\n\n    meta = read_hdf_meta(out_h5)\n    print(f\"Dataset : {meta[0].name!r},  shape={meta[0].shape}\")\n    print(\"Attributes:\")\n    for key, val in meta[0].attr.items():\n        print(f\"  {key!r:<16}: {val!r}\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "<div class=\"alert alert-info\"><h4>Note</h4><p>Prefer explicit NumPy scalar types (*e.g.* ``np.float32``, ``np.int32``) over\n   bare Python ``float`` or ``int`` when precision on disk matters. Python ``float``\n   is stored as ``float64``; Python ``int`` is stored as ``int64``.</p></div>\n\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "**HDF4 datatype restrictions \u2013 primary data and scales**\n\nHDF4 supports only a restricted set of numeric types, mapped through the\n``SDC`` type system. The supported types are:\n\n+----------+------------------------------------------+\n| Kind     | Supported itemsizes                      |\n+==========+==========================================+\n| integer  | ``int8``, ``int16``, ``int32``           |\n+----------+------------------------------------------+\n| unsigned | ``uint8``, ``uint16``, ``uint32``        |\n+----------+------------------------------------------+\n| float    | ``float32``, ``float64``                 |\n+----------+------------------------------------------+\n| string   | Unicode and byte strings                 |\n+----------+------------------------------------------+\n\nThe types ``float16``, ``int64``, and ``uint64`` have no SDC equivalent.\nAttempting to write a ``float16`` primary dataset to an HDF4 file raises a\n:exc:`KeyError` immediately, before any scales or attributes are written:\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "with tempfile.TemporaryDirectory() as tmpdir:\n    out_hdf = Path(tmpdir) / \"bad_data_dtype.hdf\"\n    try:\n        write_hdf_data(out_hdf, chmap.astype(np.float16), t, p)\n    except KeyError as e:\n        print(f\"KeyError raised for float16 data: {e}\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "The same restriction applies to scale arrays. Attempting to pass an ``int64``\nscale to an HDF4 file also raises a :exc:`KeyError`:\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "with tempfile.TemporaryDirectory() as tmpdir:\n    out_hdf = Path(tmpdir) / \"bad_scale_dtype.hdf\"\n    try:\n        write_hdf_data(out_hdf, chmap, t.astype(np.float16), p)\n    except KeyError as e:\n        print(f\"KeyError raised for float16 scale: {e}\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "**HDF4 datatype restrictions \u2013 attributes**\n\nThe same SDC type constraints apply to attribute values. Passing an ``int64``\nor ``float16`` attribute value to an HDF4 file raises a :exc:`KeyError`.\nUnlike data and scale failures \u2013 which always propagate immediately \u2013 attribute\nfailures are gated by the ``strict`` parameter:\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "with tempfile.TemporaryDirectory() as tmpdir:\n    out_hdf = Path(tmpdir) / \"bad_attr_strict.hdf\"\n    try:\n        write_hdf_data(out_hdf, chmap, t, p,\n                       cr_number=np.int64(2190))    # int64: no SDC equivalent\n    except KeyError as e:\n        print(f\"KeyError raised for int64 attribute (strict=True): {e}\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "When ``strict=False`` is set, attribute write failures are downgraded to printed\nwarnings. Compatible attributes are still written; only the offending attribute\nis skipped. This is useful when converting files from formats that use wider\ninteger or float types than HDF4 supports:\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "with tempfile.TemporaryDirectory() as tmpdir:\n    out_hdf = Path(tmpdir) / \"partial_attrs.hdf\"\n    write_hdf_data(out_hdf, chmap, t, p,\n                   description=\"Binary coronal hole map\",  # str    : valid\n                   cr_number=np.int64(2190),               # int64  : skipped with warning\n                   resolution=np.float32(1.0),             # float32: valid\n                   strict=False)\n\n    meta = read_hdf_meta(out_hdf)\n    print(\"Attributes written (incompatible attributes were skipped):\")\n    for key, val in meta[0].attr.items():\n        print(f\"  {key!r:<16}: {val!r}\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "<div class=\"alert alert-info\"><h4>Note</h4><p>``strict`` also controls behavior for HDF5 attribute writes; a :exc:`TypeError`\n   is raised (or warned) when a value cannot be stored as an HDF5 attribute \u2013 for\n   example, if the value is an arbitrary Python object that :mod:`h5py` does not\n   know how to serialize.</p></div>\n\n"
      ]
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": "Python 3",
      "language": "python",
      "name": "python3"
    },
    "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.13.9"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}