handlers.trigger
Unified trigger/condition evaluation for event handlers.
A handler's "trigger" decides whether an incoming event should actually run the
handler. It is configured per FeatureConfig under the trigger key and unifies
what used to be spread across the class-level conditions attribute and the
various bespoke condition checks inside individual handlers.
A trigger is a dict of scenarios, all AND-combined - every present scenario must hold for the handler to run. Supported scenarios:
events list[str] - event types. Evaluated *before* the fact sheet
preload via EventHandler.get_event_types();
overrides the class-level ``conditions``.
fs_types list[str] - fact sheet types (pre-preload, see get_fs_types()).
field_updated str - only run on a FactSheetFieldUpdatedEvent whose
path is "/<field_updated>".
match dict - {field: value} equality, AND-combined. A string
value prefixed with "!" negates (field must NOT
equal the value).
field_value_in dict - {field: [values]} membership, AND-combined.
event_match dict - {event_key: value} against the top-level keys of the
*event* payload (not the fact sheet), AND-combined,
e.g. {"userId": "<id>"}. A string value prefixed
with "!" negates; a list value means membership.
Event-only, so it doesn't force a full fact sheet
load (unlike an equivalent ``code`` trigger).
creator_match dict - {user_key: value} against the user who *created* the
fact sheet ("id", "displayName", "email"), read from
the fact sheet's first log entry. Same value syntax as
event_match. Needed where the event's ``userId`` is
ambiguous: LeanIX system users (e.g. "System user
'trc-data-digest'" and "System user 'reference-data'")
share one user id and differ only by displayName, which
isn't in the event. Costs one log lookup (retried briefly
if the entry isn't readable yet), so it is evaluated
last; combine it with fs_types / event_match to keep
lookups rare. An event that already carries the creator
as ``event["creator"]`` (initial sync) skips the lookup.
code str | list - arbitrary Python with ``fs`` and ``event`` in
scope. A string is evaluated as
``lambda fs, event: <string>``; a list of lines
is evaluated as a function body and MUST contain
a ``return``. A truthy result means the trigger
matches.
events and fs_types are cheap and event-only, so they are consumed by
EventHandler.get_event_types()/get_fs_types() and filtered before the expensive
fact sheet preload. The remaining scenarios need the loaded fact sheet and are
evaluated by matches(event, fs) after preload.
Shorthand: a bare string trigger is treated as {"code": <string>}.
A top-level list is reserved for a future OR-of-triggers grammar and currently
raises NotImplementedError. (Code that needs multiple lines goes under the
code key as a list, not at the top level.)
Evaluate the post-preload scenarios (field_updated, match, field_value_in, event_match, code, creator_match), AND-combined. Returns True if the handler should run. creator_match goes last: it is the only one that calls LeanIX.
events/fs_types are intentionally not re-checked here: they are cheap, event-only, and already filtered before the fact sheet preload via EventHandler.get_event_types()/get_fs_types().
skip names scenarios to leave out, e.g. InitialSync skips "field_updated",
since its synthetic events stand for "every field as it is now".
Statically extract the fact-sheet field keys this trigger reads in matches(), so the event loop can fold them into the lean-load field set.
Returns a list of field-name strings drawn from the 'match' and 'field_value_in' scenarios ('field_updated' reads the event path, not a fact-sheet field).
Returns None if the trigger cannot be introspected statically - i.e. it has a 'code' scenario running arbitrary Python. The event loop treats None as "load the full fact sheet".