OPTIMADE property & entry-type definitions

httk-core models OPTIMADE property definitions and entry-type definitions as first-class, immutable Python objects in httk.core.property_definitions, and pairs the standard entry types it vendors with ready-to-use, stdlib-only data models in httk.core.entry_types. The httk.core.EntryProvider implementations that serve those models live in the httk-data module (a capability built on these core models).

A property definition is a self-describing document: it carries a property’s canonical $id, its OPTIMADE type and unit, its requirements, and a human-readable description. An entry-type definition bundles the property definitions of one entry type together with the entry type’s own description.

Vendoring policy

The authoritative, supported copies of the standard OPTIMADE entry-type definitions are the JSON files checked in under src/httk/core/optimade_defs/ (references, files, calculations). They are distributed by the Materials-Consortia under the MIT License (see the LICENSE next to them). httk-core supports exactly the checked-in versions; the README.md in that directory records their provenance, and make optimade-defs re-fetches them from the network (the only source task that does). Ordinary builds and tests read the committed copies offline.

Loading a standard definition

standard_entry_type returns the vendored definition for one of httk-core’s standard entry types (references, files, calculations):

from httk.core import standard_entry_type

references = standard_entry_type("references")
print(references.description)
print(len(references.properties), "properties")
assert len(references.properties) == 30

Each property is a PropertyDefinition. Vendored definitions keep their canonical $ids — note the mix of core (shared) and entry-scoped identifiers — and every one carries the "1.2" definition-format stamp:

from httk.core import standard_entry_type

references = standard_entry_type("references")
assert references.properties["id"].definition_id.endswith("/properties/core/id")
assert references.properties["title"].definition_id.endswith("/optimade/references/title")
assert all(prop.format_version == "1.2" for prop in references.properties.values())

The "1.2" stamp is deliberate: httk-core’s generator emits only features that already exist in format 1.2 of the OPTIMADE property-definition schema, and the definition format is versioned in lockstep with the specification — re-stamped only when a definition actually uses newer features. That is why even calculations (a v1.3 entry type) keeps "1.2"-format property definitions.

Generating a custom property

PropertyDefinition.from_simple generates an implementation-neutral definition from a compact description. A database-specific property must carry a recognized prefix (_httk_ or _omdb_), which routes its $id under httk.org:

from httk.core import PropertyDefinition

energy = PropertyDefinition.from_simple(
    "_httk_total_energy",
    description="Total energy of the calculation.",
    fulltype="float",
)
doc = energy.as_optimade()
assert doc["$id"] == "https://httk.org/optimade/defs/properties/_httk_total_energy"
assert doc["x-optimade-type"] == "float"
assert doc["type"] == ["number", "null"]

Registering a definition prefix

The recognized database-specific prefixes are held in a small registry. _httk_ and _omdb_ are pre-registered (both under the httk.org base). A database serving its own custom properties registers its prefix once, giving the base URL under which those properties’ $ids are minted; a prefix must be a lower-case alphanumeric token wrapped in single underscores:

from httk.core import (
    PropertyDefinition,
    known_definition_prefixes,
    register_definition_prefix,
    standard_entry_type,
)

register_definition_prefix("_anyt_", "https://anyterial.se/optimade/defs/properties")
assert "_anyt_" in known_definition_prefixes()

# from_simple now routes the prefixed name's $id under the registered base:
wave_class = PropertyDefinition.from_simple(
    "_anyt_wave_class", description="Altermagnetic wave class.", fulltype="string"
)
assert wave_class.as_optimade()["$id"] == "https://anyterial.se/optimade/defs/properties/_anyt_wave_class"

# ...and extended() accepts the newly registered prefix as a custom property:
structures = standard_entry_type("references").extended({"_anyt_wave_class": wave_class})
assert "_anyt_wave_class" in structures.properties

An invalid prefix (e.g. "anyt", "_Anyt_", "anyt_") raises a clear ValueError, and re-registering an existing prefix overwrites its base.

Per-deployment sortable/response-default flags are layered on separately, so the definition itself stays neutral:

from httk.core import PropertyDefinition

energy = PropertyDefinition.from_simple("_httk_total_energy", description="E", fulltype="float")
served = energy.with_implementation(sortable=False, response_default=True)
assert served.as_optimade()["x-optimade-implementation"] == {"sortable": False, "response-default": True}
# The original is untouched:
assert "x-optimade-implementation" not in energy.as_optimade()

Extending an entry type

EntryTypeDefinition.extended merges custom properties into a copy of a standard definition. Unprefixed custom names are rejected (OPTIMADE reserves them for standard properties):

from httk.core import PropertyDefinition, standard_entry_type

energy = PropertyDefinition.from_simple("_httk_total_energy", description="E", fulltype="float")
calculations = standard_entry_type("calculations").extended({"_httk_total_energy": energy})
assert "_httk_total_energy" in calculations.properties

try:
    standard_entry_type("calculations").extended(
        {"cogwheels": PropertyDefinition.from_simple("cogwheels", description="w", fulltype="integer")}
    )
except ValueError as exc:
    assert "_httk_" in str(exc)

Entry-type record models

httk.core.entry_types provides one frozen dataclass per standard entry type (Reference, File, Calculation), each carrying a field for every non-core property of its standard. id comes from a provider’s mapping key and type is constant, so neither is a dataclass field. create accepts either an instance or a plain mapping (unknown keys are rejected), which is how a provider ingests records:

from httk.core import Reference

ref = Reference.create(
    {"title": "A study of gallium titanium compounds", "doi": "10.1234/demo.2021.1"}
)
assert ref.title == "A study of gallium titanium compounds"
assert ref.year is None  # every non-core property defaults to None

# create() is idempotent on an existing instance:
assert Reference.create(ref) is ref

These models are deliberately stdlib-only. The httk.core.EntryProvider implementations that map {id: record} mappings of them onto the neutral provider contract — and self-register as data-references, data-files, and data-calculations — live in the httk-data module, together with property-definition validation built on the definitions above. That keeps httk-core a dependency-free layer of contracts and models, with the concrete capabilities provided by the modules built on top of it.