Source code for aiida_pythonjob_ins.data.base

"""Shared base class for AiiDA Data nodes that wrap a Euphonic object.

Euphonic's core objects (``ForceConstants``, ``QpointPhononModes``, ...) all
expose a public JSON round-trip (``obj.to_json_file(path)`` /
``Cls.from_json_file(path)``); see
https://euphonic.readthedocs.io/en/stable/ .

We reuse that public serialisation and store the resulting JSON inside the AiiDA
node's *repository* (file store) rather than the database, because force-constants
arrays can be large. This keeps the DB light while preserving full provenance.

AiiDA Data / repository API reference:
https://aiida.readthedocs.io/projects/aiida-core/en/stable/topics/data_types.html
"""

from __future__ import annotations

import tempfile
from pathlib import Path
from typing import Any, ClassVar, Self

from aiida.orm import Data


[docs] class EuphonicJSONData(Data): """Store a single Euphonic object as JSON in the node repository. Subclasses set :attr:`_euphonic_cls` (the wrapped Euphonic class). The object is written on construction and read back lazily via :meth:`get_object`. """ # Set by subclasses, e.g. ``euphonic.ForceConstants``. _euphonic_cls: ClassVar[type | None] = None # Repository object key: the name the JSON blob is stored under (and read back # from) in the node's file repository. Only needs to be *stable*, not unique # per class -- the node type and the JSON's own ``__euphonic_class__`` already # identify the object -- so a single shared name suffices for all subclasses. _filename: ClassVar[str] = "euphonic_object.json" def __init__(self, euphonic_object: Any | None = None, **kwargs) -> None: super().__init__(**kwargs) if euphonic_object is not None: self._set_object(euphonic_object) def _set_object(self, euphonic_object: Any) -> None: """Validate and serialise ``euphonic_object`` into the repository.""" self._validate_type(euphonic_object) # Euphonic only serialises to/from a filesystem path, so bounce through a # temporary file and hand the bytes to the AiiDA repository. with tempfile.TemporaryDirectory() as tmpdir: tmp_path = Path(tmpdir) / self._filename euphonic_object.to_json_file(str(tmp_path)) self.base.repository.put_object_from_file(str(tmp_path), self._filename) def _validate_type(self, euphonic_object: Any) -> None: """Raise ``TypeError`` unless the object matches :attr:`_euphonic_cls`.""" if self._euphonic_cls is not None and not isinstance( euphonic_object, self._euphonic_cls ): msg = ( f"Expected a {self._euphonic_cls.__name__} instance, " f"got {type(euphonic_object).__name__}." ) raise TypeError(msg)
[docs] def get_object(self) -> Any: """Reconstruct and return the wrapped Euphonic object.""" with tempfile.TemporaryDirectory() as tmpdir: tmp_path = Path(tmpdir) / self._filename with self.base.repository.open(self._filename, mode="rb") as handle: tmp_path.write_bytes(handle.read()) return self._euphonic_cls.from_json_file(str(tmp_path))
[docs] @classmethod def from_json_file(cls, filepath: str | Path) -> Self: """Build a node directly from an existing Euphonic JSON file.""" node = cls() node.base.repository.put_object_from_file(str(filepath), cls._filename) return node