Declarative entry records

Applications can define served data with frozen dataclasses, without repeating identifier fields, plugin registrations, or direct field projections. These helpers are available on the current development branch; they require a release containing this API when installing from PyPI.

from typing import Annotated
from httk.core import DataEntryRecord, Property, entry_record

@entry_record("example.measurement")
class Measurement(DataEntryRecord):
    energy: Annotated[float, Property(description="Energy per atom.", unit="eV")]
    label: str

measurement = Measurement(-1.2, "sample-1")
assert measurement.id is None
assert measurement.energy == -1.2
assert Measurement.entry_type_definition().served_form().name == "_httk_records"

EntryRecord supplies keyword-only id, immutable_id, and last_modified fields. They are excluded from equality and content identity. DataEntryRecord selects the _httk_records family without prescribing any scientific fields. The existing DataRecord remains the separate model for one canonical JSON property value; its constructors and identities are unchanged.

entry_record creates the frozen dataclass, so a separate @dataclass is not needed. Its explicit name pins the logical content identity independently of the Python class name or module. Physical storage names are derived deterministically from that identity. A directly declared StorageInfo can supply indexes or an explicit physical name. Every concrete subclass needs its own decorator and identity; generated property mappings are specific to that class.

Only fields marked with Property or PropertyDefinition are published as attributes. In the example, energy becomes _httk_custom_energy, while label remains local bookkeeping and still contributes to content identity. Definitions, exact direct-field filtering, and sorting are generated for str, int, float, and bool, including nullable variants. Scientific meaning and units remain explicit. Other values, including exact rationals needing a presentation conversion, use the existing explicit projection API.

An existing property definition can be attached directly:

from httk.core import PropertyDefinition

energy_definition = PropertyDefinition.from_simple(
    "_httk_custom_energy", description="Energy per atom.", fulltype="float", unit="eV"
)

@entry_record("example.energy-result")
class EnergyResult(DataEntryRecord):
    energy: Annotated[float, energy_definition]

The definition’s type must match the Python scalar annotation, and nullable Python fields cannot use a non-nullable definition. Custom names must carry a registered provider prefix. Conflicting declarations fail at decoration time.

Record-valued fields remain ordinary references; they do not need StrongLink. When their target is a served family, httk-store and httk-serve expose them as OPTIMADE relationships. The storage reference pins the nested record; it is not a mutable weak link.

The decorator does not register global plugins. With httk-store, pass records=[Measurement] when creating and reopening the store. Keep the record definition in an importable module shared by your importer and server. The store checks the supplied classes against its persisted layout; it does not import Python classes or execute code named by the database.

See the three-file serving walkthrough for CIF structures, JSON results, SQLite storage, and a separate serving script. The API is documented in httk.core.entry_records.