Source code for httk.store.query.portable

"""Derive portable query fields and operations from OPTIMADE definitions."""

from collections.abc import Iterable, Mapping
from dataclasses import dataclass

from httk.core import EntryTypeDefinition, PropertyDefinition

__all__ = ["PortableQueryCapabilities", "portable_query_capabilities", "portable_query_fields"]

_SCALAR_TYPES = frozenset({"boolean", "string", "integer", "float", "timestamp"})
_STRUCTURED_TYPES = frozenset({"list", "dictionary", "object"})
_ALL_MANDATORY = "all mandatory"
_EQUALITY_ONLY = "equality only"


@dataclass(frozen=True, slots=True)
[docs] class PortableQueryCapabilities: """Describe the query operations guaranteed by one property definition. ``query-support`` expresses a cross-provider guarantee, not a particular server's implementation detail. ``all optional`` is deliberately fail-closed here: it gives a portable client no operation it can rely on. A server may offer more, but that is not represented by the definition. :param query_support: The normalized declared query-support level. :param operations: The portable operation families guaranteed by the definition. """
[docs] query_support: str | None
[docs] operations: frozenset[str]
[docs] def supports(self, operation: str) -> bool: """Report whether ``operation`` is guaranteed by this definition. :param operation: The operation family to test. :return: ``True`` when the operation is portable. """ return operation in self.operations
def _names(argument: Iterable[str], *, parameter: str, known: set[str]) -> set[str]: """Validate a caller-supplied field-name iterable and return its members.""" names: set[str] = set() for name in argument: if not isinstance(name, str): raise ValueError(f"{parameter} names must be strings, got {type(name).__name__}") if name in names: raise ValueError(f"duplicate {parameter} field name: {name!r}") if name not in known: raise ValueError(f"unknown {parameter} field name: {name!r}") names.add(name) return names def _query_support(definition: PropertyDefinition) -> str | None: """Return the normalized declared query-support level, if valid.""" requirements = definition.requirements if "query-support" not in requirements: return None support = requirements["query-support"] return support.casefold() if isinstance(support, str) else None def _is_flat_list(definition: PropertyDefinition) -> bool: """Whether an OPTIMADE list definition has a non-structured item definition.""" items = definition.as_optimade().get("items") if not isinstance(items, Mapping): return False item_type = items.get("x-optimade-type") return not (isinstance(item_type, str) and item_type.casefold() in _STRUCTURED_TYPES) def _is_portable_type(definition: PropertyDefinition) -> bool: """Whether a property type belongs to the portable query profile.""" type_name = definition.optimade_type.casefold() return type_name in _SCALAR_TYPES or (type_name == "list" and _is_flat_list(definition))
[docs] def portable_query_capabilities(definition: PropertyDefinition) -> PortableQueryCapabilities: """Derive the portable operation subset for ``definition``. The operation names are ``"equality"``, ``"ordering"``, ``"stringmatching"``, and ``"set"``. They intentionally describe the query-language operation families rather than storage implementation. ``IS [NOT] KNOWN`` is part of the equality family because it is the NULL spelling of equality/inequality in the OPTIMADE filter language. :param definition: The OPTIMADE property definition to inspect. :return: The guaranteed portable query capabilities. """ support = _query_support(definition) if support == _EQUALITY_ONLY: return PortableQueryCapabilities(support, frozenset({"equality"})) if support != _ALL_MANDATORY or not _is_portable_type(definition): return PortableQueryCapabilities(support, frozenset()) kind = definition.optimade_type.casefold() if kind == "list": return PortableQueryCapabilities(support, frozenset({"set"})) operations = {"equality"} if kind != "boolean": operations.add("ordering") if kind == "string": operations.add("stringmatching") return PortableQueryCapabilities(support, frozenset(operations))
[docs] def portable_query_fields( entry_type: EntryTypeDefinition, *, include: Iterable[str] = (), exclude: Iterable[str] = (), ) -> tuple[str, ...]: """Return the ordered portable query fields described by ``entry_type``. By default, this selects scalar fields and flat lists with at least one operation guaranteed by their definition. ``include`` is an explicit binding override for named existing properties; it is appended as a second ordered group after the derived fields, in entry-definition order among the included names, but does not manufacture query capabilities absent from that definition. ``exclude`` always wins. Both arguments reject unknown or duplicate names so binding mistakes cannot silently broaden a profile. :param entry_type: The entry definition whose properties are inspected. :param include: Existing property names to append to the derived selection. :param exclude: Existing property names to remove from the selection. :return: Derived property names followed by explicitly included names. :raises ValueError: If ``include`` or ``exclude`` contains an unknown or duplicate property name. """ properties = entry_type.properties property_names = tuple(properties) known = set(property_names) included = _names(include, parameter="include", known=known) excluded = _names(exclude, parameter="exclude", known=known) selected = [ name for name in property_names if name not in excluded and portable_query_capabilities(properties[name]).operations ] selected.extend( name for name in property_names if name in included and name not in excluded and name not in selected ) return tuple(selected)