Source code for cadrumo.domain.calculations.registry.export_field_casilla

"""The casilla an export layout field resolves to, and the back-references that follow from it.

A casilla's ``export_refs`` answer "which export fields carry me". The layout
already answers the converse for every field, so the casilla side is derived
from it here rather than declared a second time: an edition's layout owns the
edge, and a field moved to another casilla moves its back-reference with it.

A field resolves to a casilla along one of two paths:

- its own endpoint, :attr:`~.schema_exports.ExportFieldDefinition.endpoint_casilla_id`,
  which covers a ``casilla_id`` and a numbered ``projection_ref``;
- the record row mapping, for a ``binding`` field in a repeated record: the
  binding's row-set ``row_field`` names a slot, and the record's
  ``row_field_casilla_ids`` names the casilla that slot belongs to.

A binding_record row template contributes no casilla edge. Declaring
``binding_record`` makes the export derivation take the record's field
positions from its bindings' export selectors, so the record's rows are
templates the derivation fills, and neither the ``casilla`` field that
templates a mapped slot nor a ``binding`` field in that record is an edge. A
record whose field positions come from the official record design uses
``row_field_casilla_ids`` only to say which box each field fills; those rows
are real edges and keep their back-references. Declaring ``binding_record``
on such a record would add a second source of field positions beside the
official design, so the two record shapes are deliberately distinct.
"""

from __future__ import annotations

from collections.abc import Iterable, Mapping

from ....core.aggregation import BindingAggregationOp
from ....core.casilla_id import CasillaId
from ..export_field_kind import CasillaFieldKind
from .binding_aggregation import binding_aggregation_op
from .binding_selector_utils import binding_row_set_selector
from .errors import RegistryValidationError
from .ids import ExportFieldId
from .schema import BindingDefinition
from .schema_exports import ExportFieldDefinition, ExportLayoutDefinition, ExportRecordDefinition

__all__ = ["derive_casilla_export_refs", "export_field_casilla_id", "layout_fields_in_emission_order"]


[docs] def layout_fields_in_emission_order( layout: ExportLayoutDefinition, ) -> tuple[tuple[ExportRecordDefinition, ExportFieldDefinition], ...]: """Return every field of ``layout`` with its record, in the order the layout emits them. Records are taken by their declared ``order``, records of equal order in declaration order. Within a record, fields are taken by ``offset``, a field declaring no offset ahead of every positioned one, and by field id where two share an offset. """ return tuple( (record, field) for record in sorted(layout.records, key=lambda item: item.order) for field in sorted(record.fields, key=lambda item: (-1 if item.offset is None else item.offset, item.id)) )
[docs] def export_field_casilla_id( record: ExportRecordDefinition, field: ExportFieldDefinition, *, bindings: Mapping[str, BindingDefinition], ) -> CasillaId | None: """Return the casilla ``field`` of ``record`` resolves to, or ``None`` when it resolves to none. ``bindings`` maps binding id to its declaration. A binding field naming a binding absent from it, or one that produces no rows, resolves to nothing; reference validation reports the unknown binding. A binding_record row template contributes no casilla edge: in a record declaring ``binding_record`` the field positions come from the bindings' export selectors, so a ``casilla`` field whose casilla is a mapped row slot is the template of that slot and resolves to nothing, and a ``binding`` field never takes the row-mapping path. A record without it takes its positions from the official design, and its row mapping names real edges. Raises: RegistryValidationError: When a row-producing binding the field names declares a malformed row-set projection. """ if record.binding_record is not None: if field.kind == CasillaFieldKind.CASILLA and field.casilla_id in set(record.row_field_casilla_ids.values()): return None return field.endpoint_casilla_id endpoint = field.endpoint_casilla_id if endpoint is not None or field.kind != CasillaFieldKind.BINDING or field.binding is None: return endpoint binding = bindings.get(field.binding) if binding is None or binding_aggregation_op(binding) != BindingAggregationOp.ROWS: return None selector = binding_row_set_selector(binding) if selector is None: return None return record.row_field_casilla_ids.get(selector.row_field)
[docs] def derive_casilla_export_refs( export_layouts: Iterable[ExportLayoutDefinition], bindings: Iterable[BindingDefinition], ) -> dict[CasillaId, tuple[ExportFieldId, ...]]: """Return every addressed casilla's export field ids, in the order its layout emits them. Only casillas some field resolves to appear. The order is part of the value: a casilla's field ids follow :func:`layout_fields_in_emission_order`, record order and then field offset, never the order the declarations happen to be written in. Raises: RegistryValidationError: When fields of more than one layout resolve to one casilla. Two layouts each claiming a casilla is a conflict to resolve in the declarations, never a union to take silently. """ bindings_by_id = {str(binding.id): binding for binding in bindings} refs: dict[CasillaId, list[ExportFieldId]] = {} owner: dict[CasillaId, str] = {} for layout in export_layouts: for record, field in layout_fields_in_emission_order(layout): casilla_id = export_field_casilla_id(record, field, bindings=bindings_by_id) if casilla_id is None: continue claimed = owner.setdefault(casilla_id, str(layout.id)) if claimed != str(layout.id): raise RegistryValidationError( f"export layouts {claimed!r} and {str(layout.id)!r} both address casilla {casilla_id!r}; " "a casilla's export references are derived from one layout", ) refs.setdefault(casilla_id, []).append(field.id) return {casilla_id: tuple(field_ids) for casilla_id, field_ids in refs.items()}