.. DO NOT EDIT. .. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. .. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: .. "auto_how-to/example_work_with_config_files.py" .. LINE NUMBERS ARE GIVEN BELOW. .. only:: html .. note:: :class: sphx-glr-download-link-note :ref:`Go to the end ` to download the full example code. .. rst-class:: sphx-glr-example-title .. _sphx_glr_auto_how-to_example_work_with_config_files.py: Work with configuration files ============================= NeoFOAM configs are :class:`BaseConfig` (Pydantic) subclasses tagged with an ``@IOStrategy`` decorator that binds a *file* and a *format* (YAML / JSON / OpenFOAM dictionary) to the class. The class then gains ``.load()`` / ``.save()`` / ``.collect_errors()`` methods that read and write that file with full type validation. This page walks through the four things you typically do with configs — declare, load, validate, and share a file between multiple configs via subdicts. The YAML fixtures shipped alongside this example are staged into a temporary case directory and loaded with the real on-disk reader; the file contents shown below are read straight from those fixtures by Sphinx. .. GENERATED FROM PYTHON SOURCE LINES 20-28 Set up a throwaway case directory and locate the fixtures --------------------------------------------------------- Every example below stages real YAML files from ``examples/how-to/*.yaml`` into the same temporary directory. The real solver hands you a ``case_dir`` argument; here we make our own. ``HERE`` resolves to the directory containing this script so every fixture stays addressable, including when sphinx-gallery ``exec()``s the script with no ``__file__``. .. GENERATED FROM PYTHON SOURCE LINES 28-46 .. code-block:: Python import inspect import shutil import tempfile from pathlib import Path from neofoam.io import BaseConfig, IOStrategy, YAML def _here() -> None: """Marker used to locate this script on disk via inspect.getfile().""" HERE = Path(inspect.getfile(_here)).resolve().parent case_dir = Path(tempfile.mkdtemp(prefix="neofoam_howto_")) print("case_dir =", case_dir) .. rst-class:: sphx-glr-script-out .. code-block:: none case_dir = /tmp/neofoam_howto_xy84ustr .. GENERATED FROM PYTHON SOURCE LINES 47-54 Declare a config class ---------------------- ``@IOStrategy(YAML("...yaml"))`` binds the class to a file *and* a format in one decorator. The class body is a plain Pydantic model: field names, types, and default values become the schema you'll validate against. The decorator attaches an ``io_config`` class var the framework reads at ``load()`` time. .. GENERATED FROM PYTHON SOURCE LINES 54-70 .. code-block:: Python @IOStrategy(YAML("solver_config.yaml")) class SolverConfig(BaseConfig): name: str dt: float end_time: float write_interval: int = 10 # Show what the binding looks like. print("file:", SolverConfig.io_config.file) print("reader:", type(SolverConfig.io_config.reader).__name__) print("default path under case_dir:", SolverConfig.get_default_path(case_dir)) .. rst-class:: sphx-glr-script-out .. code-block:: none file: solver_config.yaml reader: YAMLStrategy default path under case_dir: /tmp/neofoam_howto_xy84ustr/solver_config.yaml .. GENERATED FROM PYTHON SOURCE LINES 71-82 Read a config from disk ----------------------- In a real run the file already exists in the case directory — the user wrote it, or it shipped with the tutorial. The class method :meth:`BaseConfig.load` takes a ``case_dir`` and resolves the filename via ``io_config.file``, so you never spell the path twice. The fixture used here lives at ``examples/how-to/solver_config.yaml``: .. literalinclude:: ../../examples/how-to/solver_config.yaml :language: yaml .. GENERATED FROM PYTHON SOURCE LINES 82-90 .. code-block:: Python shutil.copy(HERE / "solver_config.yaml", case_dir / "solver_config.yaml") cfg = SolverConfig.load(case_dir=case_dir) print("loaded:", cfg) print("cfg.dt =", cfg.dt, "(type:", type(cfg.dt).__name__ + ")") .. rst-class:: sphx-glr-script-out .. code-block:: none loaded: name='pisoFoam' dt=0.001 end_time=1.0 write_interval=20 cfg.dt = 0.001 (type: float) .. GENERATED FROM PYTHON SOURCE LINES 91-96 Write changes back to disk -------------------------- Once loaded, the instance behaves like any Pydantic model — mutate fields in memory, then :meth:`BaseConfig.save` writes the YAML back to the same path the loader read from. .. GENERATED FROM PYTHON SOURCE LINES 96-104 .. code-block:: Python cfg.write_interval = 50 cfg.save(case_dir=case_dir) print("file after save:") print((case_dir / "solver_config.yaml").read_text()) .. rst-class:: sphx-glr-script-out .. code-block:: none file after save: name: pisoFoam dt: 0.001 end_time: 1.0 write_interval: 50 .. GENERATED FROM PYTHON SOURCE LINES 105-116 Loading bad data raises with everything wrong at once ----------------------------------------------------- Pydantic batches errors per call — one ``ValidationError`` reports every field that's wrong, not just the first. Useful when a user mistypes three keys in one file. The intentionally-broken fixture lives at ``examples/how-to/solver_config_bad.yaml``: .. literalinclude:: ../../examples/how-to/solver_config_bad.yaml :language: yaml .. GENERATED FROM PYTHON SOURCE LINES 116-127 .. code-block:: Python shutil.copy(HERE / "solver_config_bad.yaml", case_dir / "solver_config.yaml") try: SolverConfig.load(case_dir=case_dir) except Exception as exc: print(type(exc).__name__, "raised — listing every problem:") for err in exc.errors(): print(f" {'.'.join(map(str, err['loc'])):12s} {err['msg']}") .. rst-class:: sphx-glr-script-out .. code-block:: none ValidationError raised — listing every problem: name Input should be a valid string dt Input should be a valid number, unable to parse string as a number .. GENERATED FROM PYTHON SOURCE LINES 128-135 Validate without raising via collect_errors ------------------------------------------- Use :meth:`BaseConfig.collect_errors` when you want a flat list of problems with file/subdict context — typical inside a CLI that shows the user every error before exiting. Returns ``[]`` on success. The framework's bulk validator (:func:`neofoam.io.validate_models`) is built on top of this. .. GENERATED FROM PYTHON SOURCE LINES 135-143 .. code-block:: Python errors = SolverConfig.collect_errors(case_dir=case_dir) print(f"{len(errors)} error(s):") for e in errors: field = ".".join(map(str, e.field)) if e.field else "" print(f" {field:12s} {e.error_type}: {e.message[:60]}") .. rst-class:: sphx-glr-script-out .. code-block:: none 2 error(s): name string_type: Input should be a valid string dt float_parsing: Input should be a valid number, unable to parse string as a .. GENERATED FROM PYTHON SOURCE LINES 144-154 Share one file between multiple configs via subdicts ---------------------------------------------------- A single ``fvSolution.yaml`` typically holds several disjoint blocks (``PIMPLE``, ``solvers.p``, ``solvers.U``). Each one gets its own ``BaseConfig`` class pointed at the same file with a different ``subdict`` path; ``.load()`` then only sees its own block. .. literalinclude:: ../../examples/how-to/fvSolution.yaml :language: yaml .. GENERATED FROM PYTHON SOURCE LINES 154-178 .. code-block:: Python shutil.copy(HERE / "fvSolution.yaml", case_dir / "fvSolution.yaml") @IOStrategy(YAML("fvSolution.yaml", subdict="PIMPLE")) class PIMPLEConfig(BaseConfig): n_outer_correctors: int n_inner_correctors: int @IOStrategy(YAML("fvSolution.yaml", subdict="solvers.p")) class PressureSolver(BaseConfig): solver: str tolerance: float pimple = PIMPLEConfig.load(case_dir=case_dir) psolver = PressureSolver.load(case_dir=case_dir) print("PIMPLE :", pimple) print("solvers.p:", psolver) print("subdict path on PIMPLEConfig:", pimple.subdict) .. rst-class:: sphx-glr-script-out .. code-block:: none PIMPLE : n_outer_correctors=2 n_inner_correctors=1 solvers.p: solver='GAMG' tolerance=1e-06 subdict path on PIMPLEConfig: PIMPLE .. GENERATED FROM PYTHON SOURCE LINES 179-195 Other formats ------------- Swap the strategy in the decorator to read the same data shape from a different format — no other changes needed. The three strategies that ship today: - :func:`~neofoam.io.YAML` — human-readable, supports subdicts, round-trips floats safely. - :func:`~neofoam.io.JSON` — strict, machine-friendly, also supports subdicts. - :func:`~neofoam.io.OF` — OpenFOAM dictionary format, lets you point at an existing case's ``system/fvSolution`` or ``constant/transportProperties`` directly. A typical migration looks like changing ``YAML("foo.yaml")`` to ``OF("foo")`` and pointing the case at the OpenFOAM-format file. .. GENERATED FROM PYTHON SOURCE LINES 198-208 See also -------- - :doc:`/reference/framework/dependency_resolver` — ``BaseConfig`` parameters in operations are pulled from ``runtime.config`` via the same resolver shown in :doc:`example_use_depends_for_injection`. - :doc:`example_register_a_model` — the LOAD stage's job is to instantiate one of these configs per model and return it inside a :class:`LoadResult`. .. rst-class:: sphx-glr-timing **Total running time of the script:** (0 minutes 0.008 seconds) .. _sphx_glr_download_auto_how-to_example_work_with_config_files.py: .. only:: html .. container:: sphx-glr-footer sphx-glr-footer-example .. container:: sphx-glr-download sphx-glr-download-jupyter :download:`Download Jupyter notebook: example_work_with_config_files.ipynb ` .. container:: sphx-glr-download sphx-glr-download-python :download:`Download Python source code: example_work_with_config_files.py ` .. container:: sphx-glr-download sphx-glr-download-zip :download:`Download zipped: example_work_with_config_files.zip ` .. only:: html .. rst-class:: sphx-glr-signature `Gallery generated by Sphinx-Gallery `_