dough
Roll your own typed wrapper.
Warning
dough is still pre-1.0. The API is still evolving and can break in minor releases.
dough is a small framework for building typed Python wrappers around the output files of simulation codes.
It ships the generic machinery โ file parsers, declarative output mappings, optional library converters โ and stays out of the way of the code-specific details.
Code-specific wrappers live in their own packages (see Packages built on dough below).
๐ฆ Installation
Wrapper packages declare dough as a runtime dependency in their own pyproject.toml:
[project]
dependencies = [
"dough>=0.3",
]
For ad-hoc exploration you can of course also just install it directly:
pip install dough
๐ฏ The current layers
- Parsers โ turn one output file into a plain dict. One parser per file format; stateless, with a single
parse(content)method. - Output mappings โ frozen dataclasses whose fields are
Annotated[T, Spec(...)]. Each field declares the output's name, type, unit (in its docstring), and how to extract it from the parsed dicts via aglomSpec. One source of truth per quantity. - Converters โ optional adapters that turn base Python outputs into
ase,pymatgen, oraiida-coreobjects. Heavy third-party imports stay lazy so wrapper packages don't pay for them at import time.
See the outputs design page for the full picture.
โจ A minimal example
Declare the outputs as Annotated[T, Spec(...)] fields on an @output_mapping class, then bind it to a BaseOutput subclass:
from typing import Annotated
from dough.outputs import BaseOutput, output_mapping
from glom import Spec
@output_mapping
class _MyMapping:
fermi_energy: Annotated[float, Spec("xml.output.band_structure.fermi_energy")]
"""Fermi energy in eV."""
class MyOutput(BaseOutput[_MyMapping]):
...
Each subclass implements from_dir (and/or from_files) to wire in its parsers.
Once instantiated, the declared fields are reachable as a typed namespace:
my_out = MyOutput.from_dir("/path/to/run_dir")
my_out.outputs.fermi_energy # -> float
โ๏ธ Units and pint
Stack a Unit marker to label a field's physical unit:
@output_mapping
class _MyMapping:
total_energy: Annotated[float, Spec("energy"), Unit("eV")]
xc_functional: Annotated[str, Spec("xc")]
Users can obtain a pint Quantity when reading the output:
out.get_output("total_energy") # 6.23
out.get_output("total_energy", to="pint") # <Quantity(6.23, 'eV')>
out.get_output("total_energy", to="pint").to("Ha")
Non-numeric fields and fields without a Unit marker pass through unchanged under to="pint".
Install with pip install dough[pint].
See the units design page for the full contract.
๐งช Testing
dough.testing ships shared pytest fixtures (json_serializer, robust_data_regression_check) used by downstream wrapper packages for regression tests.
It's an opt-in plugin โ activate it in your top-level conftest.py with pytest_plugins = ["dough.testing.plugin"].
See the testing design page.
๐ฅ Packages built on dough
| Package | Code | Status |
|---|---|---|
qe-tools |
Quantum ESPRESSO | alpha โ pw.x, dos.x outputs |
strudel |
VASP | alpha โ basic outputs + magnetization |