tests.system.openlineage.operator

Attributes

NoVariableError

AIRFLOW_VERSION

log

VALIDATION_ERROR_TAG

Classes

Attempt

Which execution of a job an event belongs to.

OpenLineageTestOperator

This operator is added for system testing purposes.

Functions

log_validation_error(msg, *args)

check_values(checks)

Check values a task computes itself, given as {name: (actual, expected)}, e.g. what a macro returns.

any(result)

is_numeric(result)

is_datetime(result)

is_uuid(result)

regex_match(result, pattern)

env_var(var[, default])

Use this jinja method to access the environment variable named 'var'.

not_match(result, pattern)

url_scheme_authority(url)

url_path(url)

setup_jinja()

match(expected, result, env[, path])

Check if result is "equal" to expected value.

check_run_id_integrity(events_by_key[, ...])

Check that run.runId identifies exactly one attempt of exactly one job, and return the problems found.

sort_by_execution(event)

Order the events of executions by Dag run clear, loop run and iteration, map index and try, as event_sort_fn.

Module Contents

tests.system.openlineage.operator.NoVariableError[source]
tests.system.openlineage.operator.AIRFLOW_VERSION[source]
tests.system.openlineage.operator.log[source]
tests.system.openlineage.operator.VALIDATION_ERROR_TAG = '[OL_VALIDATION_ERROR]'[source]
tests.system.openlineage.operator.log_validation_error(msg, *args)[source]
tests.system.openlineage.operator.check_values(checks)[source]

Check values a task computes itself, given as {name: (actual, expected)}, e.g. what a macro returns.

Every mismatch is logged as a validation error before failing the task.

tests.system.openlineage.operator.any(result)[source]
tests.system.openlineage.operator.is_numeric(result)[source]
tests.system.openlineage.operator.is_datetime(result)[source]
tests.system.openlineage.operator.is_uuid(result)[source]
tests.system.openlineage.operator.regex_match(result, pattern)[source]
tests.system.openlineage.operator.env_var(var, default=None)[source]

Use this jinja method to access the environment variable named ‘var’.

If there is no such environment variable set, return the default. If the default is None, raise an exception for an undefined variable.

tests.system.openlineage.operator.not_match(result, pattern)[source]
tests.system.openlineage.operator.url_scheme_authority(url)[source]
tests.system.openlineage.operator.url_path(url)[source]
tests.system.openlineage.operator.setup_jinja()[source]
tests.system.openlineage.operator.match(expected, result, env, path=None)[source]

Check if result is “equal” to expected value.

Omits keys not specified in expected value and resolves any jinja templates found.

Special sentinel values in expected dicts:

None (JSON null) means the key must not be present in the result.

{"$optional": "<reason>", ...} means the key is optional. When the key is present, the remaining template keys are validated. When the key is absent, validation passes silently and the reason is logged.

{"$optional": "<reason>", "$value": <list_template>} is the list-template form of the optional sentinel. It behaves the same way, but also skips validation when the actual value is an empty list. Use this form when the template value is a list rather than a dict. Dict templates embed their keys directly alongside "$optional".

class tests.system.openlineage.operator.Attempt[source]

Bases: NamedTuple

Which execution of a job an event belongs to.

try_number, map_index, loop_index and region_id are None for a Dag event and when the task has none. clear_number plays for a Dag run the role try_number plays for a task instance, and region_id tells apart two executions of one loop iteration after the loop was cleared, which can share every other field.

dag_run_id: Any[source]
clear_number: Any[source]
try_number: Any[source]
map_index: Any[source]
loop_index: Any[source]
region_id: Any[source]
tests.system.openlineage.operator.check_run_id_integrity(events_by_key, repeated_start_keys=(), corrected_terminal_keys=(), unstarted_terminal_keys=())[source]

Check that run.runId identifies exactly one attempt of exactly one job, and return the problems found.

Events are grouped by runId. The attempt of an event is its Dag run id and clear number, plus for a task event (try_number, map_index, loop.index, region_id), all from run.facets.airflow (airflowDagRun for Dag events). Query events, which are child runs of a task, have no attempt. For every group:

  1. all events share one job.name;

  2. all events share one attempt, so another Dag run, clear, retry, map index or loop iteration can’t reuse a runId;

  3. the lifecycle is complete, so START and COMPLETE/FAIL of an attempt can’t carry different runId values:

    • task and query: exactly one START, or several when the key of the START events is in repeated_start_keys (deferral re-emits START on every resume), and exactly one COMPLETE or FAIL;

    • Dag run: one START and at most one COMPLETE or FAIL. The Dag run that contains the checking task is still running when the check happens, so its COMPLETE/FAIL is missing, while Dag runs triggered by it can already have finished;

    • one more COMPLETE or FAIL is allowed when one of them has its key in corrected_terminal_keys: a user marking a finished task or Dag run with another state emits a second one for the same run;

    • no START is needed when the key of its COMPLETE or FAIL is in unstarted_terminal_keys: a task that fails before it runs, e.g. on rendering its templates, or that a user marks before it started.

Across groups:

  1. one attempt of a job has one runId.

For a task in a loop, i.e. with task.loop, also:

  1. runId is taskInstance.id, as its iterations share every other coordinate;

  2. taskInstance.loop.region_id is taskInstance.region_id for a task that isn’t mapped, and another region for a mapped one, whose own region is its expansion inside the loop’s;

  3. one run of a loop, i.e. its Dag run, clear and loop.trajectory, has one loop.region_id.

Only the events passed in are checked, i.e. those of the keys listed in the expected events.

tests.system.openlineage.operator.sort_by_execution(event)[source]

Order the events of executions by Dag run clear, loop run and iteration, map index and try, as event_sort_fn.

class tests.system.openlineage.operator.OpenLineageTestOperator(event_templates=None, file_path=None, env=None, event_count_assertions=None, clear_variables=True, fail_fast=False, event_sort_fn=None, check_run_ids=True, corrected_terminal_keys=(), unstarted_terminal_keys=(), **kwargs)[source]

Bases: airflow.providers.common.compat.sdk.BaseOperator

This operator is added for system testing purposes.

It compares expected event templates set on initialization with ones emitted by OpenLineage integration and stored in Variables by VariableTransport.

Note:

If clear_variables is True, only the Airflow Variables listed in event_templates (or derived from file_path) will be deleted - those that are supposed to be checked by the Operator. We won’t remove all Airflow Variables to avoid interfering with other instances of this Operator running in parallel on different DAGs. Running continuous system tests without clearing Variables may lead to leftover or growing Variables size. We recommend implementing a process to remove all Airflow Variables after all system tests have run to ensure a clean environment for each test run.

Parameters:
  • event_templates (dict[str, dict | list] | None) – Dictionary where each key is the key used by VariableTransport, in the format <DAG_ID>.<TASK_ID>.event.<EVENT_TYPE>. Each value may be one of the following: a single event template dict, matched against the last received event; a list of event template dicts, matched pairwise against all received events after sorting with event_sort_fn when provided; or {"$absent": "<reason>"}, which asserts that no events exist for that key. The "$absent" reason is logged and included in the error if events are unexpectedly found.

  • file_path (str | None) – alternatively, a path to a JSON file containing an array of event templates. Multiple events sharing the same job.name + eventType key are accumulated into a list. An event with "$absent": "<reason>" asserts the variable must not exist. An event with "$since": "<version>" is checked only from that Airflow version on, e.g. when older versions don’t emit it.

  • env (jinja2.Environment | None) – jinja environment used to render event templates.

  • event_count_assertions (dict[str, str | int] | None) – dict mapping a regex pattern (matched against variable keys) to a count expression. Supported expressions: "N", "==N", ">=N", "<=N". Applied to single-template keys only; list-template keys derive their count from the list length. Example: {".*\.event\.start": ">=2"}.

  • clear_variables (bool) – if True, deletes only the checked variables after all checks run (or on failure). Default True.

  • fail_fast (bool) – if True, raise on the first failure. If False (default), collect all failures and raise a combined error at the end.

  • event_sort_fn (collections.abc.Callable[[dict], Any] | None) – optional callable (event: dict) -> Any used to sort actual events ascending before comparison. Useful for mapped tasks whose events arrive in non-deterministic order. When the template for a key is a list, sorting is required for pairwise comparison to be meaningful.

  • corrected_terminal_keys (collections.abc.Collection[str]) – keys of COMPLETE/FAIL events emitted when a user marks a finished task or Dag run with another state. The run they belong to may then have one COMPLETE/FAIL more in the check_run_id_integrity check: the one of its original outcome, and the one of the correction.

  • unstarted_terminal_keys (collections.abc.Collection[str]) – keys of COMPLETE/FAIL events of runs that have no START, as the task never ran: it failed before it started or a user marked it before it started. The check_run_id_integrity check then doesn’t require a START for their runs.

  • check_run_ids (bool) – if True (default), additionally check the collected events with check_run_id_integrity: START and COMPLETE/FAIL of one attempt share a runId, while another try, map index or loop iteration gets its own. Set to False only for a Dag whose expected events intentionally list a task’s lifecycle partially. It runs only once every key matches its template.

Special sentinel values in expected dicts:

None (JSON null) means the key must not be present in the result.

{"$optional": "<reason>", ...} means the key is optional. When the key is present, the remaining template keys are validated. When the key is absent, validation passes silently and the reason is logged.

{"$optional": "<reason>", "$value": <list_template>} is the list-template form of the optional sentinel. It behaves the same way, but also skips validation when the actual value is an empty list. Use this form when the template value is a list rather than a dict. Dict templates embed their keys directly alongside "$optional".

Raises:

ValueError if the received events do not match with expected ones.

event_templates = None[source]
file_path = None[source]
env[source]
event_count_assertions = None[source]
clear_variables = True[source]
fail_fast = False[source]
check_run_ids = True[source]
corrected_terminal_keys = ()[source]
unstarted_terminal_keys = ()[source]
event_sort_fn = None[source]
execute(context)[source]

Derive when creating an operator.

The main method to execute the task. Context is the same dictionary used as when rendering jinja templates.

Refer to get_template_context for more context.

Was this entry helpful?