Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
31dd43e
fall back to inherited metric metadata
Oct 6, 2026
ccb2cb7
fixes around fallback behaviour (null, propagation to weaker fields)
Oct 20, 2026
87047ab
switch to more sensible default and fallback behaviours
Oct 21, 2026
28e729b
changelog and documentation changes
Oct 21, 2026
47f0945
better document glom spec to sub fields in documentation
Oct 21, 2026
5d67fa7
evaluate metadata fallbacks only for metrics lacking information
Oct 22, 2026
fc70922
extract metadata of the current convention in a single pass
Oct 22, 2026
bba03ec
add helper for default timezone with fallback behaviour
Oct 22, 2026
95c1a70
do not replace an explicit single metric by another only metric
Oct 22, 2026
7cbd8eb
resolve only the single metric and determine it in one place
Oct 22, 2026
dd0cd10
add recording
Oct 22, 2026
4cfc58f
cleanup
Oct 26, 2026
2eee82c
rework to strict conventions
Oct 26, 2026
76e1417
fix timezone test
Oct 26, 2026
b81c995
update docs
jennykupzig Oct 27, 2026
b7494a7
formatting
jennykupzig Oct 27, 2026
79c0fd0
update python
jennykupzig Oct 27, 2026
314048d
Merge pull request #15 from hetida/inherited
jennykupzig Oct 27, 2026
f8b3964
Merge remote-tracking branch 'origin/develop' into metadata-model
jennykupzig Oct 27, 2026
3a881e4
fixes and improvements
Oct 27, 2026
b8f5c92
review fixes and refactoring
Oct 27, 2026
80d030a
rename without_metadata, shorten Changelog, update docs, enhance pipe…
jennykupzig Oct 27, 2026
b7ac2b1
update docs
jennykupzig Oct 28, 2026
a036379
delete test_publish_docs
jennykupzig Oct 28, 2026
9117114
fix metadata snapshot and update readme
jennykupzig Oct 28, 2026
7db85a0
Merge pull request #17 from hetida/metadata-model
jennykupzig Oct 28, 2026
a07491a
update version and changelog
jennykupzig Oct 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions .github/workflows/check_pull_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,15 @@ jobs:
- "3.12"
- "3.13"
- "3.14"
- "3.15"

steps:
- uses: actions/checkout@v6

- name: Install uv and set the Python version
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
with:
version: ">=0.13.4"
python-version: ${{ matrix.python-version }}

- name: Display Python version
Expand All @@ -44,13 +46,71 @@ jobs:
./run test


test_without_plotly:
name: Core installation without Plotly (Python ${{ matrix.python-version }})
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.12", "3.15"]

steps:
- uses: actions/checkout@v6

- name: Install uv and set the Python version
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
with:
version: ">=0.13.4"
python-version: ${{ matrix.python-version }}

- name: Install the core package without extras or development dependencies
run: uv sync --locked --no-dev --no-editable

- name: Check core functionality and optional plotting exports
run: |
uv run --no-sync python -I - <<'PY'
import importlib.util

assert importlib.util.find_spec("plotly") is None, "Plotly must not be installed"

import pandas as pd
import hdhelpers
from hdhelpers.metadata import attach_metadata, attrs_to_json, get_series_unit, read_metadata
from hdhelpers.metadata.conventions import SERIES_METADATA_EXAMPLE

series = pd.Series([1.0], index=pd.to_datetime(["2026-01-01T00:00:00Z"]))
series.attrs = SERIES_METADATA_EXAMPLE
meta = attach_metadata(series)
assert meta is not None
assert read_metadata(series) is meta
assert get_series_unit(series) == "m"
assert attrs_to_json(series.attrs)["metrics"][0]["value_dimensions"][0]["unit"] == "m"
converted = hdhelpers.modify_timezone(series, to_timezone="Europe/Berlin")
assert str(converted.index.tz) == "Europe/Berlin"
assert read_metadata(converted) is meta

# Repeat every export to catch modules cached by an earlier failed import.
names = (
"plotting", "FUSEKI_COLOR_CYCLE", "resolve_color",
"set_agnostic_theme", "TICKFORMATSTOPS", "set_dt_ticks",
)
for name in names * 2:
try:
getattr(hdhelpers, name)
except ImportError as error:
assert "hdhelpers[plotting]" in str(error), (name, error)
else:
raise AssertionError(f"hdhelpers.{name} is available without Plotly")
PY

check_lint_format_mypy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6

- name: Install uv
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
with:
version: ">=0.13.4"

- name: Set up Python
run: uv python install
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,4 @@ todo
untracked
Dockerfile
docker-compose.yaml
docs/build
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
## [0.1.0] - 2026-10-28
- Expand documentation with installation instructions, a metadata model reference, shared examples, and clearer API descriptions
- Add validated, immutable metadata models with helpers to read, attach, update, and serialize them. Pandas operations share attached models instead of repeatedly copying metadata. Attached models are stored under attrs["hd_metadata"]; use attrs_to_json before JSON export.
- Improve metadata lookups with consistent fallbacks for missing values, display names, and inherited properties (`include_inherited=True` for dedicated unit/name helpers).
- Add helpers for default time zones, recording information, default aggregation methods, and copying pandas objects without metadata (`copy_without_metadata`).
- **Breaking:** Only the current metadata conventions are supported. Metadata getters ignore invalid metadata and use their documented defaults. Legacy formats and arbitrary glom specs are removed; queries accept field names and dotted paths.
- **Breaking:** Make Plotly optional: install `hdhelpers[plotting]` for plotting helpers. Core metadata and time zone functionality works without it.
- Add Python 3.15 support and update development dependencies and test tooling to address the CA certificate issue.
- Expand validation and regression tests, correct lint configuration, and add CI checks of actual installations without Plotly.

## [0.0.9] - 2026-09-10
- singletsframe support

Expand Down
45 changes: 36 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

[![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-green.svg)](https://github.com/hetida/hdhelpers/graphs/commit-activity)
[![build](https://github.com/hetida/hdhelpers/actions/workflows/check_pull_request.yml/badge.svg)](https://github.com/hetida/hdhelpers/actions/workflows/check_pull_request.yml)
![python](https://img.shields.io/badge/python-%203.12%20|%203.13|%203.14%20-blue)
![python](https://img.shields.io/badge/python-%203.12%20|%203.13|%203.14%20|%203.15%20-blue)
[![PyPI version](https://badge.fury.io/py/hdhelpers.svg)](https://badge.fury.io/py/hdhelpers)
![PyPI - License](https://img.shields.io/pypi/l/hdhelpers)
[![Downloads](https://pepy.tech/badge/hdhelpers)](https://pepy.tech/project/hdhelpers)
Expand All @@ -18,6 +18,9 @@ The documentation of the package is a [GitHub page](https://hetida.github.io/hdh
## Getting Started with hdhelpers
Since version >0.13.10 the hetida designer runtime comes with an installed version of hdhelpers. To start the hetida designer we recommend following the [hetida designer setup guide](https://github.com/hetida/hetida-designer/blob/release/README.md#getting-started-with-hetida-designer).

The plotting helpers (`hdhelpers.plotting` and their names in `hdhelpers`) need plotly, which is the optional extra
`plotting`: `pip install "hdhelpers[plotting]"`. Everything else, e.g. the metadata models, works without plotly.

An example is given in [GitHub page](https://hetida.github.io/hdhelpers/first_steps.html#how-to-get-metadata-with-hdhelpers) of how to use functionalities of hdhelpers inside a hetida designer component. Furthermore the base component [Single Time Series Plot](https://github.com/hetida/hetida-designer/blob/release/runtime/transformations/components/visualization) uses hdhelpers to demonstrate the usage (since version >0.13.10).

## Developing for hdhelpers
Expand All @@ -27,7 +30,8 @@ For dependency management and venv setup, building and publishing, [uv](https://
#### Python environment
1) Create a virtual environment with `uv venv`. This will create a hidden `.venv` directory.
2) Activate the virtual environment via `source .venv/bin/activate`
3) Run `uv sync` to install all dependencies given in pyproject.toml.
3) Run `uv sync --all-extras` to install all dependencies given in pyproject.toml, including the optional
ones of the plotting helpers.
4) In case you need to add a new dependency, do so via `uv add <new_dependency>`. That way, uv finds versions of all
dependencies that are compatible with each other.
5) In case you need a new requirement for development purposes please use `uv add --dev <new_dependency>`
Expand All @@ -43,7 +47,7 @@ FROM hetida/designer-backend:<<hd_version>>

USER root

RUN pip install .
RUN pip install ".[plotting]"

USER hd_app

Expand All @@ -69,6 +73,34 @@ writing component code.
### Code Quality
Once you are done writing your code, including unit tests, use `./run check` to see if your code quality is sufficient.

#### Metadata schema snapshot
`tests/metadata/test_conventions_schema.py` compares the generated metadata JSON schemas with
`tests/data/convention_schemas.json`. Class docstrings become schema descriptions, so documentation
changes can also require a snapshot update.

After an intentional schema change, regenerate the snapshot in Bash/WSL:

```bash
UPDATE_SCHEMA_SNAPSHOT=1 uv run pytest tests/metadata/test_conventions_schema.py -q
```

Or in PowerShell:

```powershell
$env:UPDATE_SCHEMA_SNAPSHOT = "1"
uv run pytest tests/metadata/test_conventions_schema.py -q
Remove-Item Env:UPDATE_SCHEMA_SNAPSHOT
```

Review the diff for unintended changes and rerun the test without the update flag:

```bash
git diff -- tests/data/convention_schemas.json
uv run pytest tests/metadata/test_conventions_schema.py -q
```

Commit the updated snapshot together with the corresponding model or docstring changes.

### Documentation
For documentation we use the tool sphinx. Please apply `./run build_docs` to create the current state of documentation. It will be stored in **docs**. You can open the documentation by opening `docs/index.html`, e.g. with your browser.

Expand Down Expand Up @@ -100,12 +132,7 @@ To **publish** the build from the `dist` subdirectory to PyPI,

3) verifiy that package can be installed by using:
```bash
uv run --with 'hdhelpers==<version>'
--refresh-package hdhelpers
--default-index https://test.pypi.org/simple/
--index https://test.pypi.org/simple/
--no-project
-- python -c "import hdhelpers; print(hdhelpers.__version__)"
uv run --with 'hdhelpers==0.0.9' --refresh-package hdhelpers --default-index https://test.pypi.org/simple/ --index https://test.pypi.org/simple/ --no-project -- python -c "import hdhelpers; print(hdhelpers.__version__)"
```

4) use `uv publish dist/*` to upload the package on [hdhelpers PyPI project](https://pypi.org/project/hdhelpers/).
Expand Down
120 changes: 120 additions & 0 deletions docs/_modules/hdhelpers/exceptions.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
<!DOCTYPE html>

<html lang="en" data-content_root="../../">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>hdhelpers.exceptions &#8212; hdhelpers - documentation</title>
<link rel="stylesheet" type="text/css" href="../../_static/pygments.css?v=03e43079" />
<link rel="stylesheet" type="text/css" href="../../_static/classic.css?v=14e077c0" />
<link rel="stylesheet" type="text/css" href="../../_static/stylesheet.css?v=55595bb5" />

<script src="../../_static/documentation_options.js?v=b1628c55"></script>
<script src="../../_static/doctools.js?v=fd6eb6e6"></script>
<script src="../../_static/sphinx_highlight.js?v=6ffebe34"></script>

<link rel="icon" href="../../_static/favicon.ico"/>
<link rel="index" title="Index" href="../../genindex.html" />
<link rel="search" title="Search" href="../../search.html" />
</head><body>
<div class="related" role="navigation" aria-label="Related">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
<a href="../../genindex.html" title="General Index"
accesskey="I">index</a></li>
<li class="right" >
<a href="../../py-modindex.html" title="Python Module Index"
>modules</a> |</li>
<li class="nav-item nav-item-0"><a href="../../index.html">hdhelpers - documentation</a> &#187;</li>
<li class="nav-item nav-item-1"><a href="../index.html" accesskey="U">Module code</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">hdhelpers.exceptions</a></li>
</ul>
</div>

<div class="document">
<div class="documentwrapper">
<div class="bodywrapper">
<div class="body" role="main">

<h1>Source code for hdhelpers.exceptions</h1><div class="highlight"><pre>
<span></span><span class="linenos"> 1</span><span class="kn">from</span><span class="w"> </span><span class="nn">typing</span><span class="w"> </span><span class="kn">import</span> <span class="n">Any</span>
<span class="linenos"> 2</span>
<span class="linenos"> 3</span>
<div class="viewcode-block" id="HelperException">
<a class="viewcode-back" href="../../index.html#hdhelpers.exceptions.HelperException">[docs]</a>
<span class="linenos"> 4</span><span class="k">class</span><span class="w"> </span><span class="nc">HelperException</span><span class="p">(</span><span class="ne">Exception</span><span class="p">):</span>
<span class="linenos"> 5</span><span class="w"> </span><span class="sd">"""Exception for reporting errors to hetida designer.</span>
<span class="linenos"> 6</span>
<span class="linenos"> 7</span><span class="sd"> Attributes:</span>
<span class="linenos"> 8</span><span class="sd"> error_code (int | str): Error identifier; an empty string if unspecified.</span>
<span class="linenos"> 9</span><span class="sd"> extra_information (dict | None): Optional details for the error handler.</span>
<span class="linenos">10</span><span class="sd"> """</span>
<span class="linenos">11</span>
<span class="linenos">12</span> <span class="n">__is_hetida_designer_exception__</span> <span class="o">=</span> <span class="kc">True</span>
<span class="linenos">13</span>
<span class="linenos">14</span> <span class="k">def</span><span class="w"> </span><span class="fm">__init__</span><span class="p">(</span>
<span class="linenos">15</span> <span class="bp">self</span><span class="p">,</span>
<span class="linenos">16</span> <span class="o">*</span><span class="n">args</span><span class="p">:</span> <span class="n">Any</span><span class="p">,</span>
<span class="linenos">17</span> <span class="n">error_code</span><span class="p">:</span> <span class="nb">int</span> <span class="o">|</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">""</span><span class="p">,</span>
<span class="linenos">18</span> <span class="n">extra_information</span><span class="p">:</span> <span class="nb">dict</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
<span class="linenos">19</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">:</span> <span class="n">Any</span><span class="p">,</span>
<span class="linenos">20</span> <span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
<span class="linenos">21</span> <span class="k">if</span> <span class="ow">not</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">error_code</span><span class="p">,</span> <span class="nb">int</span> <span class="o">|</span> <span class="nb">str</span><span class="p">):</span>
<span class="linenos">22</span> <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="s2">"The HelperException.error_code must be int or string!"</span><span class="p">)</span>
<span class="linenos">23</span> <span class="bp">self</span><span class="o">.</span><span class="n">error_code</span> <span class="o">=</span> <span class="n">error_code</span>
<span class="linenos">24</span> <span class="bp">self</span><span class="o">.</span><span class="n">extra_information</span> <span class="o">=</span> <span class="n">extra_information</span>
<span class="linenos">25</span> <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span></div>

<span class="linenos">26</span>
<span class="linenos">27</span>
<div class="viewcode-block" id="InsufficientPlottingData">
<a class="viewcode-back" href="../../index.html#hdhelpers.exceptions.InsufficientPlottingData">[docs]</a>
<span class="linenos">28</span><span class="k">class</span><span class="w"> </span><span class="nc">InsufficientPlottingData</span><span class="p">(</span><span class="n">HelperException</span><span class="p">):</span>
<span class="linenos">29</span><span class="w"> </span><span class="sd">"""Raised when a plot component has insufficient data to produce a meaningful plot.</span>
<span class="linenos">30</span>
<span class="linenos">31</span><span class="sd"> Frontends can handle this by displaying a message instead of an empty plot.</span>
<span class="linenos">32</span><span class="sd"> """</span></div>

</pre></div>

<div class="clearer"></div>
</div>
</div>
</div>
<div class="sphinxsidebar" role="navigation" aria-label="Main">
<div class="sphinxsidebarwrapper">
<search id="searchbox" style="display: none" role="search">
<h3 id="searchlabel">Quick search</h3>
<div class="searchformwrapper">
<form class="search" action="../../search.html" method="get">
<input type="text" name="q" aria-labelledby="searchlabel" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"/>
<input type="submit" value="Go" />
</form>
</div>
</search>
<script>document.getElementById('searchbox').style.display = "block"</script>
</div>
</div>
<div class="clearer"></div>
</div>
<div class="related" role="navigation" aria-label="Related">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
<a href="../../genindex.html" title="General Index"
>index</a></li>
<li class="right" >
<a href="../../py-modindex.html" title="Python Module Index"
>modules</a> |</li>
<li class="nav-item nav-item-0"><a href="../../index.html">hdhelpers - documentation</a> &#187;</li>
<li class="nav-item nav-item-1"><a href="../index.html" >Module code</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">hdhelpers.exceptions</a></li>
</ul>
</div>
<div class="footer" role="contentinfo">
&#169; Copyright 2026, Steffen Wittkamp, Jenny Kupzig.
Created using <a href="https://www.sphinx-doc.org/">Sphinx</a> 9.2.0.
</div>
</body>
</html>
Loading
Loading