Skip to content

niess.dispatch

The registry mechanism both conversion targets share: three-tier resolution over the assembled instance tree, keyed on niess provenance or the McStas component type.

dispatch

Target-neutral registry dispatch over the assembled Instance tree.

Conversion targets (STEP/CAD geometry in :mod:niess.brep, NeXus Structure JSON in :mod:niess.nexus) resolve a builder for each mccode_antlr Instance using three tiers, most specific first:

  1. the niess source_type recorded in the instance's provenance metadata,
  2. the niess role recorded there,
  3. the raw McCode component-type name, which every Instance carries whether or not niess produced it.

The registry only resolves builders; each target invokes them with whatever signature it needs. Keeping resolution and invocation separate lets a caller distinguish "no builder registered" from "a builder ran and declined to emit anything" -- a distinction :mod:niess.nexus depends on for group suppression.

Classes:

  • NiessRegistry

    Three-tier builder lookup keyed on provenance metadata or component type.

Functions:

  • expr_float

    A plain float out of a McCode Expr, or ValueError if it is not one.

  • merged_params

    Instance parameters evaluated to floats, layered over params.

NiessRegistry

NiessRegistry(parent: 'NiessRegistry[B] | None' = None)

Bases: Generic[B]

Three-tier builder lookup keyed on provenance metadata or component type.

A registry may extend another by naming it as parent: lookups that find nothing locally fall through to the parent's own three tiers. That is how an instrument-specific registry adds its translators without touching the shared default one, so which translators apply is a property of a single conversion rather than of whatever the process happened to import.

Methods:

Source code in src/niess/dispatch.py
def __init__(self, parent: 'NiessRegistry[B] | None' = None) -> None:
    self.parent = parent
    self._source_type_builders: dict[str, B] = {}
    self._role_builders: dict[str, B] = {}
    self._component_type_builders: dict[str, B] = {}

register

register(source: type | str)

Register against a niess class (or its dotted source_type name).

Source code in src/niess/dispatch.py
def register(self, source: type | str):
    """Register against a niess class (or its dotted ``source_type`` name)."""
    source_type = source if isinstance(source, str) else niess_source_type(source)

    def decorator(func: B) -> B:
        self._source_type_builders[source_type] = func
        return func

    return decorator

register_role

register_role(role: str)

Register against a provenance role.

Source code in src/niess/dispatch.py
def register_role(self, role: str):
    """Register against a provenance ``role``."""
    def decorator(func: B) -> B:
        self._role_builders[role] = func
        return func

    return decorator

register_component_type

register_component_type(*comp_type_names: str)

Register against one or more raw McCode component-type names.

Source code in src/niess/dispatch.py
def register_component_type(self, *comp_type_names: str):
    """Register against one or more raw McCode component-type names."""
    def decorator(func: B) -> B:
        for name in comp_type_names:
            self._component_type_builders[name] = func
        return func

    return decorator

resolve_builder

resolve_builder(instance) -> B | None

Return the builder for instance, or None if none is registered.

This registry's own three tiers are tried first, in full, before any parent is consulted: the more specific registry wins outright.

None means unhandled -- it never means "handled, emit nothing". Callers that need that distinction must invoke the returned builder themselves and interpret its return value.

Source code in src/niess/dispatch.py
def resolve_builder(self, instance) -> B | None:
    """Return the builder for ``instance``, or ``None`` if none is registered.

    This registry's own three tiers are tried first, in full, before any parent
    is consulted: the more specific registry wins outright.

    ``None`` means *unhandled* -- it never means "handled, emit nothing".
    Callers that need that distinction must invoke the returned builder
    themselves and interpret its return value.
    """
    provenance = NiessProvenance.from_instance(instance)
    builder = self._resolve_local(instance, provenance)
    if builder is not None:
        return builder
    return None if self.parent is None else self.parent.resolve_builder(instance)

registered_component_types

registered_component_types() -> frozenset[str]

Component-type names this registry handles, inherited ones included.

Source code in src/niess/dispatch.py
def registered_component_types(self) -> frozenset[str]:
    """Component-type names this registry handles, inherited ones included."""
    inherited = frozenset() if self.parent is None else self.parent.registered_component_types()
    return frozenset(self._component_type_builders) | inherited

expr_float

expr_float(value)

A plain float out of a McCode Expr, or ValueError if it is not one.

hasattr(expr, 'value') is not the test it looks like: Expr.value is a property that raises for anything not constant, and hasattr only swallows AttributeError, so asking whether an expression has a value used to raise NotImplementedError straight through every caller. Every caller catches TypeError/ValueError -- that is what "this names a run-time parameter, skip it" is spelled as throughout niess -- so an expression that does not reduce says so that way.

Source code in src/niess/dispatch.py
def expr_float(value):
    """A plain float out of a McCode ``Expr``, or ``ValueError`` if it is not one.

    ``hasattr(expr, 'value')`` is not the test it looks like: ``Expr.value`` is a property
    that *raises* for anything not constant, and ``hasattr`` only swallows
    ``AttributeError``, so asking whether an expression has a value used to raise
    ``NotImplementedError`` straight through every caller. Every caller catches
    ``TypeError``/``ValueError`` -- that is what "this names a run-time parameter, skip it"
    is spelled as throughout niess -- so an expression that does not reduce says so that
    way.
    """
    if isinstance(value, (int, float)):
        return float(value)
    simplified = value.simplify() if hasattr(value, 'simplify') else value
    try:
        held = simplified.value
    except (AttributeError, NotImplementedError):
        held = None
    if isinstance(held, (int, float)):
        return float(held)
    try:
        return float(simplified)
    except (AttributeError, NotImplementedError, TypeError) as error:
        raise ValueError(
            f'{simplified!r} does not reduce to a number; it depends on something only '
            f'known at run time'
        ) from error

merged_params

merged_params(instance, params: dict[str, float] | None = None) -> dict[str, float]

Instance parameters evaluated to floats, layered over params.

Parameters that cannot be reduced to a float (they depend on a runtime instrument parameter, say) are skipped rather than raising.

Source code in src/niess/dispatch.py
def merged_params(instance, params: dict[str, float] | None = None) -> dict[str, float]:
    """Instance parameters evaluated to floats, layered over ``params``.

    Parameters that cannot be reduced to a float (they depend on a runtime
    instrument parameter, say) are skipped rather than raising.
    """
    merged = dict(params or {})
    for component_parameter in instance.parameters:
        try:
            evaluated = component_parameter.value.evaluate(merged)
            merged[component_parameter.name] = expr_float(evaluated)
        except Exception:
            continue
    return merged

provenance