This is an automated email from the ASF dual-hosted git repository. uranusjr pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/main by this push: new fce3a58334 Implement context accessor for DatasetEvent extra (#38481) fce3a58334 is described below commit fce3a583348162f655282d032eca654dcb67b497 Author: Tzu-ping Chung <uranu...@gmail.com> AuthorDate: Fri Mar 29 10:06:02 2024 +0800 Implement context accessor for DatasetEvent extra (#38481) --- .pre-commit-config.yaml | 5 + airflow/datasets/__init__.py | 11 +- airflow/models/taskinstance.py | 14 ++- airflow/utils/context.py | 34 ++++++ airflow/utils/context.pyi | 12 ++- contributing-docs/08_static_code_checks.rst | 2 + dev/breeze/doc/images/output_static-checks.svg | 114 +++++++++++---------- dev/breeze/doc/images/output_static-checks.txt | 2 +- dev/breeze/src/airflow_breeze/pre_commit_ids.py | 1 + .../authoring-and-scheduling/datasets.rst | 31 +++++- docs/apache-airflow/templates-ref.rst | 2 + .../pre_commit_template_context_key_sync.py | 0 tests/models/test_taskinstance.py | 67 +++++++++++- tests/operators/test_python.py | 1 + 14 files changed, 228 insertions(+), 68 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1829f9b200..2f347b1c88 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -629,6 +629,11 @@ repos: entry: ./scripts/ci/pre_commit/pre_commit_sync_init_decorator.py pass_filenames: false files: ^airflow/models/dag\.py$|^airflow/(?:decorators|utils)/task_group\.py$ + - id: check-template-context-variable-in-sync + name: Check all template context variable references are in sync + language: python + entry: ./scripts/ci/pre_commit/pre_commit_template_context_key_sync.py + files: ^airflow/models/taskinstance\.py$|^airflow/utils/context\.pyi?$|^docs/apache-airflow/templates-ref\.rst$ - id: check-base-operator-usage language: pygrep name: Check BaseOperator core imports diff --git a/airflow/datasets/__init__.py b/airflow/datasets/__init__.py index 2507c69d01..d20d3b578e 100644 --- a/airflow/datasets/__init__.py +++ b/airflow/datasets/__init__.py @@ -42,7 +42,14 @@ def _get_uri_normalizer(scheme: str) -> Callable[[SplitResult], SplitResult] | N return ProvidersManager().dataset_uri_handlers.get(scheme) -def _sanitize_uri(uri: str) -> str: +def sanitize_uri(uri: str) -> str: + """Sanitize a dataset URI. + + This checks for URI validity, and normalizes the URI if needed. A fully + normalized URI is returned. + + :meta private: + """ if not uri: raise ValueError("Dataset URI cannot be empty") if uri.isspace(): @@ -110,7 +117,7 @@ class Dataset(os.PathLike, BaseDatasetEventInput): """A representation of data dependencies between workflows.""" uri: str = attr.field( - converter=_sanitize_uri, + converter=sanitize_uri, validator=[attr.validators.min_len(1), attr.validators.max_len(3000)], ) extra: dict[str, Any] | None = None diff --git a/airflow/models/taskinstance.py b/airflow/models/taskinstance.py index 8bb9947327..c9bd2ce617 100644 --- a/airflow/models/taskinstance.py +++ b/airflow/models/taskinstance.py @@ -104,7 +104,13 @@ from airflow.templates import SandboxedEnvironment from airflow.ti_deps.dep_context import DepContext from airflow.ti_deps.dependencies_deps import REQUEUEABLE_DEPS, RUNNING_DEPS from airflow.utils import timezone -from airflow.utils.context import ConnectionAccessor, Context, VariableAccessor, context_merge +from airflow.utils.context import ( + ConnectionAccessor, + Context, + DatasetEventAccessors, + VariableAccessor, + context_merge, +) from airflow.utils.email import send_email from airflow.utils.helpers import prune_dict, render_template_to_string from airflow.utils.log.logging_mixin import LoggingMixin @@ -766,6 +772,7 @@ def _get_template_context( "dag_run": dag_run, "data_interval_end": timezone.coerce_datetime(data_interval.end), "data_interval_start": timezone.coerce_datetime(data_interval.start), + "dataset_events": DatasetEventAccessors(), "ds": ds, "ds_nodash": ds_nodash, "execution_date": logical_date, @@ -2569,7 +2576,7 @@ class TaskInstance(Base, LoggingMixin): session.add(Log(self.state, self)) session.merge(self).task = self.task if self.state == TaskInstanceState.SUCCESS: - self._register_dataset_changes(session=session) + self._register_dataset_changes(events=context["dataset_events"], session=session) session.commit() if self.state == TaskInstanceState.SUCCESS: @@ -2579,7 +2586,7 @@ class TaskInstance(Base, LoggingMixin): return None - def _register_dataset_changes(self, *, session: Session) -> None: + def _register_dataset_changes(self, *, events: DatasetEventAccessors, session: Session) -> None: if TYPE_CHECKING: assert self.task @@ -2590,6 +2597,7 @@ class TaskInstance(Base, LoggingMixin): dataset_manager.register_dataset_change( task_instance=self, dataset=obj, + extra=events[obj].extra, session=session, ) diff --git a/airflow/utils/context.py b/airflow/utils/context.py index 3501ca7dbc..033b7aa39d 100644 --- a/airflow/utils/context.py +++ b/airflow/utils/context.py @@ -36,8 +36,10 @@ from typing import ( ValuesView, ) +import attrs import lazy_object_proxy +from airflow.datasets import Dataset, sanitize_uri from airflow.exceptions import RemovedInAirflow3Warning from airflow.utils.types import NOTSET @@ -54,6 +56,7 @@ KNOWN_CONTEXT_KEYS: set[str] = { "dag_run", "data_interval_end", "data_interval_start", + "dataset_events", "ds", "ds_nodash", "execution_date", @@ -146,6 +149,37 @@ class ConnectionAccessor: return default_conn +@attrs.define() +class DatasetEventAccessor: + """Wrapper to access a DatasetEvent instance in template.""" + + extra: dict[str, Any] + + +class DatasetEventAccessors(Mapping[str, DatasetEventAccessor]): + """Lazy mapping of dataset event accessors.""" + + def __init__(self) -> None: + self._dict: dict[str, DatasetEventAccessor] = {} + + def __iter__(self) -> Iterator[str]: + return iter(self._dict) + + def __len__(self) -> int: + return len(self._dict) + + def __getitem__(self, key: str | Dataset) -> DatasetEventAccessor: + if isinstance(key, str): + uri = sanitize_uri(key) + elif isinstance(key, Dataset): + uri = key.uri + else: + return NotImplemented + if uri not in self._dict: + self._dict[uri] = DatasetEventAccessor({}) + return self._dict[uri] + + class AirflowContextDeprecationWarning(RemovedInAirflow3Warning): """Warn for usage of deprecated context variables in a task.""" diff --git a/airflow/utils/context.pyi b/airflow/utils/context.pyi index eb08201248..8b5deb4746 100644 --- a/airflow/utils/context.pyi +++ b/airflow/utils/context.pyi @@ -26,11 +26,12 @@ # declare "these are defined, but don't error if others are accessed" someday. from __future__ import annotations -from typing import Any, Collection, Container, Iterable, Mapping, overload +from typing import Any, Collection, Container, Iterable, Iterator, Mapping, overload from pendulum import DateTime from airflow.configuration import AirflowConfigParser +from airflow.datasets import Dataset from airflow.models.baseoperator import BaseOperator from airflow.models.dag import DAG from airflow.models.dagrun import DagRun @@ -55,6 +56,14 @@ class VariableAccessor: class ConnectionAccessor: def get(self, key: str, default_conn: Any = None) -> Any: ... +class DatasetEventAccessor: + extra: dict[str, Any] + +class DatasetEventAccessors(Mapping[str, DatasetEventAccessor]): + def __iter__(self) -> Iterator[str]: ... + def __len__(self) -> int: ... + def __getitem__(self, key: str | Dataset) -> DatasetEventAccessor: ... + # NOTE: Please keep this in sync with the following: # * KNOWN_CONTEXT_KEYS in airflow/utils/context.py # * Table in docs/apache-airflow/templates-ref.rst @@ -65,6 +74,7 @@ class Context(TypedDict, total=False): dag_run: DagRun | DagRunPydantic data_interval_end: DateTime data_interval_start: DateTime + dataset_events: DatasetEventAccessors ds: str ds_nodash: str exception: BaseException | str | None diff --git a/contributing-docs/08_static_code_checks.rst b/contributing-docs/08_static_code_checks.rst index 0b331bf3e9..c7be51b6a7 100644 --- a/contributing-docs/08_static_code_checks.rst +++ b/contributing-docs/08_static_code_checks.rst @@ -222,6 +222,8 @@ require Breeze Docker image to be built locally. +-----------------------------------------------------------+--------------------------------------------------------------+---------+ | check-system-tests-tocs | Check that system tests is properly added | | +-----------------------------------------------------------+--------------------------------------------------------------+---------+ +| check-template-context-variable-in-sync | Check all template context variable references are in sync | | ++-----------------------------------------------------------+--------------------------------------------------------------+---------+ | check-tests-in-the-right-folders | Check if tests are in the right folders | | +-----------------------------------------------------------+--------------------------------------------------------------+---------+ | check-tests-unittest-testcase | Check that unit tests do not inherit from unittest.TestCase | | diff --git a/dev/breeze/doc/images/output_static-checks.svg b/dev/breeze/doc/images/output_static-checks.svg index 679db3dfee..a709f8070c 100644 --- a/dev/breeze/doc/images/output_static-checks.svg +++ b/dev/breeze/doc/images/output_static-checks.svg @@ -1,4 +1,4 @@ -<svg class="rich-terminal" viewBox="0 0 1482 2099.6" xmlns="http://www.w3.org/2000/svg"> +<svg class="rich-terminal" viewBox="0 0 1482 2124.0" xmlns="http://www.w3.org/2000/svg"> <!-- Generated with Rich https://www.textualize.io --> <style> @@ -43,7 +43,7 @@ <defs> <clipPath id="breeze-static-checks-clip-terminal"> - <rect x="0" y="0" width="1463.0" height="2048.6" /> + <rect x="0" y="0" width="1463.0" height="2073.0" /> </clipPath> <clipPath id="breeze-static-checks-line-0"> <rect x="0" y="1.5" width="1464" height="24.65"/> @@ -294,9 +294,12 @@ <clipPath id="breeze-static-checks-line-82"> <rect x="0" y="2002.3" width="1464" height="24.65"/> </clipPath> +<clipPath id="breeze-static-checks-line-83"> + <rect x="0" y="2026.7" width="1464" height="24.65"/> + </clipPath> </defs> - <rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1" x="1" y="1" width="1480" height="2097.6" rx="8"/><text class="breeze-static-checks-title" fill="#c5c8c6" text-anchor="middle" x="740" y="27">Command: static-checks</text> + <rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1" x="1" y="1" width="1480" height="2122" rx="8"/><text class="breeze-static-checks-title" fill="#c5c8c6" text-anchor="middle" x="740" y="27">Command: static-checks</text> <g transform="translate(26,22)"> <circle cx="0" cy="0" r="7" fill="#ff5f57"/> <circle cx="22" cy="0" r="7" fill="#febc2e"/> @@ -338,58 +341,59 @@ </text><text class="breeze-static-checks-r5" x="0" y="727.6" textLength="12.2" clip-path="url(#breeze-static-checks-line-29)">│</text><text class="breeze-static-checks-r7" x="451.4" y="727.6" textLength="988.2" clip-path="url(#breeze-static-checks-line-29)">check-pydevd-left-in-code | check-revision-heads-map |                           </t [...] </text><text class="breeze-static-checks-r5" x="0" y="752" textLength="12.2" clip-path="url(#breeze-static-checks-line-30)">│</text><text class="breeze-static-checks-r7" x="451.4" y="752" textLength="988.2" clip-path="url(#breeze-static-checks-line-30)">check-safe-filter-usage-in-html | check-sql-dependency-common-data-structure |   </text><text class="breeze-static-checks-r5" x="1451.8" y="752" textLength="12.2" clip-path="url(#breeze-static-checks-line-30) [...] </text><text class="breeze-static-checks-r5" x="0" y="776.4" textLength="12.2" clip-path="url(#breeze-static-checks-line-31)">│</text><text class="breeze-static-checks-r7" x="451.4" y="776.4" textLength="988.2" clip-path="url(#breeze-static-checks-line-31)">check-start-date-not-used-in-defaults | check-system-tests-present |             </text><text class="breeze-static-checks-r5" x="1451.8" y="776.4" textLen [...] -</text><text class="breeze-static-checks-r5" x="0" y="800.8" textLength="12.2" clip-path="url(#breeze-static-checks-line-32)">│</text><text class="breeze-static-checks-r7" x="451.4" y="800.8" textLength="988.2" clip-path="url(#breeze-static-checks-line-32)">check-system-tests-tocs | check-tests-in-the-right-folders |                     </text><text class="breeze-static [...] -</text><text class="breeze-static-checks-r5" x="0" y="825.2" textLength="12.2" clip-path="url(#breeze-static-checks-line-33)">│</text><text class="breeze-static-checks-r7" x="451.4" y="825.2" textLength="988.2" clip-path="url(#breeze-static-checks-line-33)">check-tests-unittest-testcase | check-urlparse-usage-in-code |                   </text><text class="breeze-static-checks-r5 [...] -</text><text class="breeze-static-checks-r5" x="0" y="849.6" textLength="12.2" clip-path="url(#breeze-static-checks-line-34)">│</text><text class="breeze-static-checks-r7" x="451.4" y="849.6" textLength="988.2" clip-path="url(#breeze-static-checks-line-34)">check-usage-of-re2-over-re | check-xml | codespell | compile-www-assets |        </text><text class="breeze-static-checks-r5" x="1451.8" y="849.6" textLength=" [...] -</text><text class="breeze-static-checks-r5" x="0" y="874" textLength="12.2" clip-path="url(#breeze-static-checks-line-35)">│</text><text class="breeze-static-checks-r7" x="451.4" y="874" textLength="988.2" clip-path="url(#breeze-static-checks-line-35)">compile-www-assets-dev | create-missing-init-py-files-tests | debug-statements | </text><text class="breeze-static-checks-r5" x="1451.8" y="874" textLength="12.2" clip-path="url(#breeze-static-checks-line-35) [...] -</text><text class="breeze-static-checks-r5" x="0" y="898.4" textLength="12.2" clip-path="url(#breeze-static-checks-line-36)">│</text><text class="breeze-static-checks-r7" x="451.4" y="898.4" textLength="988.2" clip-path="url(#breeze-static-checks-line-36)">detect-private-key | doctoc | end-of-file-fixer | fix-encoding-pragma | flynt |  </text><text class="breeze-static-checks-r5" x="1451.8" y="898.4" textLength="12.2" clip-path="url [...] -</text><text class="breeze-static-checks-r5" x="0" y="922.8" textLength="12.2" clip-path="url(#breeze-static-checks-line-37)">│</text><text class="breeze-static-checks-r7" x="451.4" y="922.8" textLength="988.2" clip-path="url(#breeze-static-checks-line-37)">generate-airflow-diagrams | generate-pypi-readme | identity | insert-license |   </text><text class="breeze-static-checks-r5" x="1451.8" y="922.8" textLength="12.2" clip-path="url(#bre [...] -</text><text class="breeze-static-checks-r5" x="0" y="947.2" textLength="12.2" clip-path="url(#breeze-static-checks-line-38)">│</text><text class="breeze-static-checks-r7" x="451.4" y="947.2" textLength="988.2" clip-path="url(#breeze-static-checks-line-38)">kubeconform | lint-chart-schema | lint-css | lint-dockerfile | lint-helm-chart | </text><text class="breeze-static-checks-r5" x="1451.8" y="947.2" textLength="12.2" clip-path="url(#bre [...] -</text><text class="breeze-static-checks-r5" x="0" y="971.6" textLength="12.2" clip-path="url(#breeze-static-checks-line-39)">│</text><text class="breeze-static-checks-r7" x="451.4" y="971.6" textLength="988.2" clip-path="url(#breeze-static-checks-line-39)">lint-json-schema | lint-markdown | lint-openapi | mixed-line-ending |            </text><text class="breeze-static-checks-r5" x="1451.8" y= [...] -</text><text class="breeze-static-checks-r5" x="0" y="996" textLength="12.2" clip-path="url(#breeze-static-checks-line-40)">│</text><text class="breeze-static-checks-r7" x="451.4" y="996" textLength="988.2" clip-path="url(#breeze-static-checks-line-40)">mypy-airflow | mypy-dev | mypy-docs | mypy-providers | pretty-format-json |      </text><text class="breeze-static-checks-r5" x="1451.8" y="996" textLength="12.2" [...] -</text><text class="breeze-static-checks-r5" x="0" y="1020.4" textLength="12.2" clip-path="url(#breeze-static-checks-line-41)">│</text><text class="breeze-static-checks-r7" x="451.4" y="1020.4" textLength="988.2" clip-path="url(#breeze-static-checks-line-41)">pylint | python-no-log-warn | replace-bad-characters | rst-backticks | ruff |    </text><text class="breeze-static-checks-r5" x="1451.8" y="1020.4" textLength="12.2" c [...] -</text><text class="breeze-static-checks-r5" x="0" y="1044.8" textLength="12.2" clip-path="url(#breeze-static-checks-line-42)">│</text><text class="breeze-static-checks-r7" x="451.4" y="1044.8" textLength="988.2" clip-path="url(#breeze-static-checks-line-42)">ruff-format | shellcheck | trailing-whitespace | ts-compile-format-lint-www |    </text><text class="breeze-static-checks-r5" x="1451.8" y="1044.8" textLength="12.2" clip-path=" [...] -</text><text class="breeze-static-checks-r5" x="0" y="1069.2" textLength="12.2" clip-path="url(#breeze-static-checks-line-43)">│</text><text class="breeze-static-checks-r7" x="451.4" y="1069.2" textLength="988.2" clip-path="url(#breeze-static-checks-line-43)">update-black-version | update-breeze-cmd-output |                             [...] -</text><text class="breeze-static-checks-r5" x="0" y="1093.6" textLength="12.2" clip-path="url(#breeze-static-checks-line-44)">│</text><text class="breeze-static-checks-r7" x="451.4" y="1093.6" textLength="988.2" clip-path="url(#breeze-static-checks-line-44)">update-breeze-readme-config-hash | update-build-dependencies |                   </text><text class="breeze-static-checks- [...] -</text><text class="breeze-static-checks-r5" x="0" y="1118" textLength="12.2" clip-path="url(#breeze-static-checks-line-45)">│</text><text class="breeze-static-checks-r7" x="451.4" y="1118" textLength="988.2" clip-path="url(#breeze-static-checks-line-45)">update-chart-dependencies | update-common-sql-api-stubs | update-er-diagram |    </text><text class="breeze-static-checks-r5" x="1451.8" y="1118" textLength="12.2" clip-path="url(#breeze-stat [...] -</text><text class="breeze-static-checks-r5" x="0" y="1142.4" textLength="12.2" clip-path="url(#breeze-static-checks-line-46)">│</text><text class="breeze-static-checks-r7" x="451.4" y="1142.4" textLength="988.2" clip-path="url(#breeze-static-checks-line-46)">update-extras | update-in-the-wild-to-be-sorted |                             [...] -</text><text class="breeze-static-checks-r5" x="0" y="1166.8" textLength="12.2" clip-path="url(#breeze-static-checks-line-47)">│</text><text class="breeze-static-checks-r7" x="451.4" y="1166.8" textLength="988.2" clip-path="url(#breeze-static-checks-line-47)">update-inlined-dockerfile-scripts | update-installed-providers-to-be-sorted |    </text><text class="breeze-static-checks-r5" x="1451.8" y="1166.8" textLength="12.2" clip-path="url(#breeze-static-c [...] -</text><text class="breeze-static-checks-r5" x="0" y="1191.2" textLength="12.2" clip-path="url(#breeze-static-checks-line-48)">│</text><text class="breeze-static-checks-r7" x="451.4" y="1191.2" textLength="988.2" clip-path="url(#breeze-static-checks-line-48)">update-installers | update-local-yml-file | update-migration-references |        </text><text class="breeze-static-checks-r5" x="1451.8" y="1191.2" textLength="12.2" c [...] -</text><text class="breeze-static-checks-r5" x="0" y="1215.6" textLength="12.2" clip-path="url(#breeze-static-checks-line-49)">│</text><text class="breeze-static-checks-r7" x="451.4" y="1215.6" textLength="988.2" clip-path="url(#breeze-static-checks-line-49)">update-providers-dependencies | update-reproducible-source-date-epoch |          </text><text class="breeze-static-checks-r5" x="1451.8" y="1215.6" textLength="12.2" c [...] -</text><text class="breeze-static-checks-r5" x="0" y="1240" textLength="12.2" clip-path="url(#breeze-static-checks-line-50)">│</text><text class="breeze-static-checks-r7" x="451.4" y="1240" textLength="988.2" clip-path="url(#breeze-static-checks-line-50)">update-spelling-wordlist-to-be-sorted | update-supported-versions |              </text><text class="breeze-static-checks-r5" x="1451.8" y="1240" textL [...] -</text><text class="breeze-static-checks-r5" x="0" y="1264.4" textLength="12.2" clip-path="url(#breeze-static-checks-line-51)">│</text><text class="breeze-static-checks-r7" x="451.4" y="1264.4" textLength="988.2" clip-path="url(#breeze-static-checks-line-51)">update-vendored-in-k8s-json-schema | update-version | validate-operators-init |  </text><text class="breeze-static-checks-r5" x="1451.8" y="1264.4" textLength="12.2" clip-path="url(#breeze-static-c [...] -</text><text class="breeze-static-checks-r5" x="0" y="1288.8" textLength="12.2" clip-path="url(#breeze-static-checks-line-52)">│</text><text class="breeze-static-checks-r7" x="451.4" y="1288.8" textLength="988.2" clip-path="url(#breeze-static-checks-line-52)">yamllint)                                     & [...] -</text><text class="breeze-static-checks-r5" x="0" y="1313.2" textLength="12.2" clip-path="url(#breeze-static-checks-line-53)">│</text><text class="breeze-static-checks-r4" x="24.4" y="1313.2" textLength="12.2" clip-path="url(#breeze-static-checks-line-53)">-</text><text class="breeze-static-checks-r4" x="36.6" y="1313.2" textLength="61" clip-path="url(#breeze-static-checks-line-53)">-show</text><text class="breeze-static-checks-r4" x="97.6" y="1313.2" textLength="195.2" clip-path="url(# [...] -</text><text class="breeze-static-checks-r5" x="0" y="1337.6" textLength="12.2" clip-path="url(#breeze-static-checks-line-54)">│</text><text class="breeze-static-checks-r4" x="24.4" y="1337.6" textLength="12.2" clip-path="url(#breeze-static-checks-line-54)">-</text><text class="breeze-static-checks-r4" x="36.6" y="1337.6" textLength="134.2" clip-path="url(#breeze-static-checks-line-54)">-initialize</text><text class="breeze-static-checks-r4" x="170.8" y="1337.6" textLength="146.4" clip-p [...] -</text><text class="breeze-static-checks-r5" x="0" y="1362" textLength="12.2" clip-path="url(#breeze-static-checks-line-55)">│</text><text class="breeze-static-checks-r4" x="24.4" y="1362" textLength="12.2" clip-path="url(#breeze-static-checks-line-55)">-</text><text class="breeze-static-checks-r4" x="36.6" y="1362" textLength="48.8" clip-path="url(#breeze-static-checks-line-55)">-max</text><text class="breeze-static-checks-r4" x="85.4" y="1362" textLength="292.8" clip-path="url(#breeze- [...] -</text><text class="breeze-static-checks-r5" x="0" y="1386.4" textLength="12.2" clip-path="url(#breeze-static-checks-line-56)">│</text><text class="breeze-static-checks-r7" x="451.4" y="1386.4" textLength="854" clip-path="url(#breeze-static-checks-line-56)">(INTEGER RANGE)                                    [...] -</text><text class="breeze-static-checks-r5" x="0" y="1410.8" textLength="12.2" clip-path="url(#breeze-static-checks-line-57)">│</text><text class="breeze-static-checks-r5" x="451.4" y="1410.8" textLength="854" clip-path="url(#breeze-static-checks-line-57)">[default: 3; 1<=x<=10]                                 [...] -</text><text class="breeze-static-checks-r5" x="0" y="1435.2" textLength="1464" clip-path="url(#breeze-static-checks-line-58)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="breeze-static-checks-r1" x="1464" y="1435.2" textLength="12.2" clip-path="url(#breeze-static-checks-line-58)"> -</text><text class="breeze-static-checks-r5" x="0" y="1459.6" textLength="24.4" clip-path="url(#breeze-static-checks-line-59)">╭─</text><text class="breeze-static-checks-r5" x="24.4" y="1459.6" textLength="463.6" clip-path="url(#breeze-static-checks-line-59)"> Selecting files to run the checks on </text><text class="breeze-static-checks-r5" x="488" y="1459.6" textLength="951.6" clip-path="url(#breeze-static-checks-line-59)">──────────────────────── [...] -</text><text class="breeze-static-checks-r5" x="0" y="1484" textLength="12.2" clip-path="url(#breeze-static-checks-line-60)">│</text><text class="breeze-static-checks-r4" x="24.4" y="1484" textLength="12.2" clip-path="url(#breeze-static-checks-line-60)">-</text><text class="breeze-static-checks-r4" x="36.6" y="1484" textLength="61" clip-path="url(#breeze-static-checks-line-60)">-file</text><text class="breeze-static-checks-r6" x="256.2" y="1484" textLength="24.4" clip-path="url(#breeze-s [...] -</text><text class="breeze-static-checks-r5" x="0" y="1508.4" textLength="12.2" clip-path="url(#breeze-static-checks-line-61)">│</text><text class="breeze-static-checks-r4" x="24.4" y="1508.4" textLength="12.2" clip-path="url(#breeze-static-checks-line-61)">-</text><text class="breeze-static-checks-r4" x="36.6" y="1508.4" textLength="48.8" clip-path="url(#breeze-static-checks-line-61)">-all</text><text class="breeze-static-checks-r4" x="85.4" y="1508.4" textLength="73.2" clip-path="url(# [...] -</text><text class="breeze-static-checks-r5" x="0" y="1532.8" textLength="12.2" clip-path="url(#breeze-static-checks-line-62)">│</text><text class="breeze-static-checks-r4" x="24.4" y="1532.8" textLength="12.2" clip-path="url(#breeze-static-checks-line-62)">-</text><text class="breeze-static-checks-r4" x="36.6" y="1532.8" textLength="85.4" clip-path="url(#breeze-static-checks-line-62)">-commit</text><text class="breeze-static-checks-r4" x="122" y="1532.8" textLength="48.8" clip-path="url [...] -</text><text class="breeze-static-checks-r5" x="0" y="1557.2" textLength="12.2" clip-path="url(#breeze-static-checks-line-63)">│</text><text class="breeze-static-checks-r1" x="305" y="1557.2" textLength="183" clip-path="url(#breeze-static-checks-line-63)">exclusive with </text><text class="breeze-static-checks-r4" x="488" y="1557.2" textLength="12.2" clip-path="url(#breeze-static-checks-line-63)">-</text><text class="breeze-static-checks-r4" x="500.2" y="1557.2" textLength="61" [...] -</text><text class="breeze-static-checks-r5" x="0" y="1581.6" textLength="12.2" clip-path="url(#breeze-static-checks-line-64)">│</text><text class="breeze-static-checks-r7" x="305" y="1581.6" textLength="1134.6" clip-path="url(#breeze-static-checks-line-64)">(TEXT)                                       [...] -</text><text class="breeze-static-checks-r5" x="0" y="1606" textLength="12.2" clip-path="url(#breeze-static-checks-line-65)">│</text><text class="breeze-static-checks-r4" x="24.4" y="1606" textLength="12.2" clip-path="url(#breeze-static-checks-line-65)">-</text><text class="breeze-static-checks-r4" x="36.6" y="1606" textLength="61" clip-path="url(#breeze-static-checks-line-65)">-last</text><text class="breeze-static-checks-r4" x="97.6" y="1606" textLength="85.4" clip-path="url(#breeze-st [...] -</text><text class="breeze-static-checks-r5" x="0" y="1630.4" textLength="12.2" clip-path="url(#breeze-static-checks-line-66)">│</text><text class="breeze-static-checks-r4" x="24.4" y="1630.4" textLength="12.2" clip-path="url(#breeze-static-checks-line-66)">-</text><text class="breeze-static-checks-r4" x="36.6" y="1630.4" textLength="61" clip-path="url(#breeze-static-checks-line-66)">-only</text><text class="breeze-static-checks-r4" x="97.6" y="1630.4" textLength="134.2" clip-path="url(# [...] -</text><text class="breeze-static-checks-r5" x="0" y="1654.8" textLength="12.2" clip-path="url(#breeze-static-checks-line-67)">│</text><text class="breeze-static-checks-r1" x="305" y="1654.8" textLength="1134.6" clip-path="url(#breeze-static-checks-line-67)">branch and HEAD of your branch.                              [...] -</text><text class="breeze-static-checks-r5" x="0" y="1679.2" textLength="1464" clip-path="url(#breeze-static-checks-line-68)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="breeze-static-checks-r1" x="1464" y="1679.2" textLength="12.2" clip-path="url(#breeze-static-checks-line-68)"> -</text><text class="breeze-static-checks-r5" x="0" y="1703.6" textLength="24.4" clip-path="url(#breeze-static-checks-line-69)">╭─</text><text class="breeze-static-checks-r5" x="24.4" y="1703.6" textLength="463.6" clip-path="url(#breeze-static-checks-line-69)"> Building image before running checks </text><text class="breeze-static-checks-r5" x="488" y="1703.6" textLength="951.6" clip-path="url(#breeze-static-checks-line-69)">────────────────────────────────── [...] -</text><text class="breeze-static-checks-r5" x="0" y="1728" textLength="12.2" clip-path="url(#breeze-static-checks-line-70)">│</text><text class="breeze-static-checks-r4" x="24.4" y="1728" textLength="12.2" clip-path="url(#breeze-static-checks-line-70)">-</text><text class="breeze-static-checks-r4" x="36.6" y="1728" textLength="61" clip-path="url(#breeze-static-checks-line-70)">-skip</text><text class="breeze-static-checks-r4" x="97.6" y="1728" textLength="244" clip-path="url(#breeze-sta [...] -</text><text class="breeze-static-checks-r5" x="0" y="1752.4" textLength="12.2" clip-path="url(#breeze-static-checks-line-71)">│</text><text class="breeze-static-checks-r4" x="24.4" y="1752.4" textLength="12.2" clip-path="url(#breeze-static-checks-line-71)">-</text><text class="breeze-static-checks-r4" x="36.6" y="1752.4" textLength="73.2" clip-path="url(#breeze-static-checks-line-71)">-force</text><text class="breeze-static-checks-r4" x="109.8" y="1752.4" textLength="73.2" clip-path="ur [...] -</text><text class="breeze-static-checks-r5" x="0" y="1776.8" textLength="12.2" clip-path="url(#breeze-static-checks-line-72)">│</text><text class="breeze-static-checks-r4" x="24.4" y="1776.8" textLength="12.2" clip-path="url(#breeze-static-checks-line-72)">-</text><text class="breeze-static-checks-r4" x="36.6" y="1776.8" textLength="73.2" clip-path="url(#breeze-static-checks-line-72)">-image</text><text class="breeze-static-checks-r4" x="109.8" y="1776.8" textLength="48.8" clip-path="ur [...] -</text><text class="breeze-static-checks-r5" x="0" y="1801.2" textLength="12.2" clip-path="url(#breeze-static-checks-line-73)">│</text><text class="breeze-static-checks-r7" x="414.8" y="1801.2" textLength="963.8" clip-path="url(#breeze-static-checks-line-73)">(TEXT)                                      [...] -</text><text class="breeze-static-checks-r5" x="0" y="1825.6" textLength="12.2" clip-path="url(#breeze-static-checks-line-74)">│</text><text class="breeze-static-checks-r5" x="414.8" y="1825.6" textLength="963.8" clip-path="url(#breeze-static-checks-line-74)">[default: latest]                                    [...] -</text><text class="breeze-static-checks-r5" x="0" y="1850" textLength="12.2" clip-path="url(#breeze-static-checks-line-75)">│</text><text class="breeze-static-checks-r4" x="24.4" y="1850" textLength="12.2" clip-path="url(#breeze-static-checks-line-75)">-</text><text class="breeze-static-checks-r4" x="36.6" y="1850" textLength="85.4" clip-path="url(#breeze-static-checks-line-75)">-github</text><text class="breeze-static-checks-r4" x="122" y="1850" textLength="134.2" clip-path="url(#breez [...] -</text><text class="breeze-static-checks-r5" x="0" y="1874.4" textLength="12.2" clip-path="url(#breeze-static-checks-line-76)">│</text><text class="breeze-static-checks-r4" x="24.4" y="1874.4" textLength="12.2" clip-path="url(#breeze-static-checks-line-76)">-</text><text class="breeze-static-checks-r4" x="36.6" y="1874.4" textLength="97.6" clip-path="url(#breeze-static-checks-line-76)">-builder</text><text class="breeze-static-checks-r1" x="414.8" y="1874.4" textLength="756.4" clip-path= [...] -</text><text class="breeze-static-checks-r5" x="0" y="1898.8" textLength="12.2" clip-path="url(#breeze-static-checks-line-77)">│</text><text class="breeze-static-checks-r5" x="414.8" y="1898.8" textLength="756.4" clip-path="url(#breeze-static-checks-line-77)">[default: autodetect]                                  &# [...] -</text><text class="breeze-static-checks-r5" x="0" y="1923.2" textLength="1464" clip-path="url(#breeze-static-checks-line-78)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="breeze-static-checks-r1" x="1464" y="1923.2" textLength="12.2" clip-path="url(#breeze-static-checks-line-78)"> -</text><text class="breeze-static-checks-r5" x="0" y="1947.6" textLength="24.4" clip-path="url(#breeze-static-checks-line-79)">╭─</text><text class="breeze-static-checks-r5" x="24.4" y="1947.6" textLength="195.2" clip-path="url(#breeze-static-checks-line-79)"> Common options </text><text class="breeze-static-checks-r5" x="219.6" y="1947.6" textLength="1220" clip-path="url(#breeze-static-checks-line-79)">────────────────────────────────────────────────────────────────────── [...] -</text><text class="breeze-static-checks-r5" x="0" y="1972" textLength="12.2" clip-path="url(#breeze-static-checks-line-80)">│</text><text class="breeze-static-checks-r4" x="24.4" y="1972" textLength="12.2" clip-path="url(#breeze-static-checks-line-80)">-</text><text class="breeze-static-checks-r4" x="36.6" y="1972" textLength="48.8" clip-path="url(#breeze-static-checks-line-80)">-dry</text><text class="breeze-static-checks-r4" x="85.4" y="1972" textLength="48.8" clip-path="url(#breeze-s [...] -</text><text class="breeze-static-checks-r5" x="0" y="1996.4" textLength="12.2" clip-path="url(#breeze-static-checks-line-81)">│</text><text class="breeze-static-checks-r4" x="24.4" y="1996.4" textLength="12.2" clip-path="url(#breeze-static-checks-line-81)">-</text><text class="breeze-static-checks-r4" x="36.6" y="1996.4" textLength="97.6" clip-path="url(#breeze-static-checks-line-81)">-verbose</text><text class="breeze-static-checks-r6" x="158.6" y="1996.4" textLength="24.4" clip-path=" [...] -</text><text class="breeze-static-checks-r5" x="0" y="2020.8" textLength="12.2" clip-path="url(#breeze-static-checks-line-82)">│</text><text class="breeze-static-checks-r4" x="24.4" y="2020.8" textLength="12.2" clip-path="url(#breeze-static-checks-line-82)">-</text><text class="breeze-static-checks-r4" x="36.6" y="2020.8" textLength="61" clip-path="url(#breeze-static-checks-line-82)">-help</text><text class="breeze-static-checks-r6" x="158.6" y="2020.8" textLength="24.4" clip-path="url(# [...] -</text><text class="breeze-static-checks-r5" x="0" y="2045.2" textLength="1464" clip-path="url(#breeze-static-checks-line-83)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="breeze-static-checks-r1" x="1464" y="2045.2" textLength="12.2" clip-path="url(#breeze-static-checks-line-83)"> +</text><text class="breeze-static-checks-r5" x="0" y="800.8" textLength="12.2" clip-path="url(#breeze-static-checks-line-32)">│</text><text class="breeze-static-checks-r7" x="451.4" y="800.8" textLength="988.2" clip-path="url(#breeze-static-checks-line-32)">check-system-tests-tocs | check-template-context-variable-in-sync |              </text><text class="breeze-static-checks-r5" x="1451.8" y="800.8" te [...] +</text><text class="breeze-static-checks-r5" x="0" y="825.2" textLength="12.2" clip-path="url(#breeze-static-checks-line-33)">│</text><text class="breeze-static-checks-r7" x="451.4" y="825.2" textLength="988.2" clip-path="url(#breeze-static-checks-line-33)">check-tests-in-the-right-folders | check-tests-unittest-testcase |               </text><text class="breeze-static-checks-r5" x="1451.8" y="825. [...] +</text><text class="breeze-static-checks-r5" x="0" y="849.6" textLength="12.2" clip-path="url(#breeze-static-checks-line-34)">│</text><text class="breeze-static-checks-r7" x="451.4" y="849.6" textLength="988.2" clip-path="url(#breeze-static-checks-line-34)">check-urlparse-usage-in-code | check-usage-of-re2-over-re | check-xml | codespell</text><text class="breeze-static-checks-r5" x="1451.8" y="849.6" textLength="12.2" clip-path="url(#breeze-static-checks-li [...] +</text><text class="breeze-static-checks-r5" x="0" y="874" textLength="12.2" clip-path="url(#breeze-static-checks-line-35)">│</text><text class="breeze-static-checks-r7" x="451.4" y="874" textLength="988.2" clip-path="url(#breeze-static-checks-line-35)">| compile-www-assets | compile-www-assets-dev |                             [...] +</text><text class="breeze-static-checks-r5" x="0" y="898.4" textLength="12.2" clip-path="url(#breeze-static-checks-line-36)">│</text><text class="breeze-static-checks-r7" x="451.4" y="898.4" textLength="988.2" clip-path="url(#breeze-static-checks-line-36)">create-missing-init-py-files-tests | debug-statements | detect-private-key |     </text><text class="breeze-static-checks-r5" x="1451.8" y="898.4" textLength="12.2" clip-path="url(#bre [...] +</text><text class="breeze-static-checks-r5" x="0" y="922.8" textLength="12.2" clip-path="url(#breeze-static-checks-line-37)">│</text><text class="breeze-static-checks-r7" x="451.4" y="922.8" textLength="988.2" clip-path="url(#breeze-static-checks-line-37)">doctoc | end-of-file-fixer | fix-encoding-pragma | flynt |                       </t [...] +</text><text class="breeze-static-checks-r5" x="0" y="947.2" textLength="12.2" clip-path="url(#breeze-static-checks-line-38)">│</text><text class="breeze-static-checks-r7" x="451.4" y="947.2" textLength="988.2" clip-path="url(#breeze-static-checks-line-38)">generate-airflow-diagrams | generate-pypi-readme | identity | insert-license |   </text><text class="breeze-static-checks-r5" x="1451.8" y="947.2" textLength="12.2" clip-path="url(#bre [...] +</text><text class="breeze-static-checks-r5" x="0" y="971.6" textLength="12.2" clip-path="url(#breeze-static-checks-line-39)">│</text><text class="breeze-static-checks-r7" x="451.4" y="971.6" textLength="988.2" clip-path="url(#breeze-static-checks-line-39)">kubeconform | lint-chart-schema | lint-css | lint-dockerfile | lint-helm-chart | </text><text class="breeze-static-checks-r5" x="1451.8" y="971.6" textLength="12.2" clip-path="url(#bre [...] +</text><text class="breeze-static-checks-r5" x="0" y="996" textLength="12.2" clip-path="url(#breeze-static-checks-line-40)">│</text><text class="breeze-static-checks-r7" x="451.4" y="996" textLength="988.2" clip-path="url(#breeze-static-checks-line-40)">lint-json-schema | lint-markdown | lint-openapi | mixed-line-ending |            </text><text class="breeze-static-checks-r5" x="1451.8" y="996 [...] +</text><text class="breeze-static-checks-r5" x="0" y="1020.4" textLength="12.2" clip-path="url(#breeze-static-checks-line-41)">│</text><text class="breeze-static-checks-r7" x="451.4" y="1020.4" textLength="988.2" clip-path="url(#breeze-static-checks-line-41)">mypy-airflow | mypy-dev | mypy-docs | mypy-providers | pretty-format-json |      </text><text class="breeze-static-checks-r5" x="1451.8" y="1020.4" textLengt [...] +</text><text class="breeze-static-checks-r5" x="0" y="1044.8" textLength="12.2" clip-path="url(#breeze-static-checks-line-42)">│</text><text class="breeze-static-checks-r7" x="451.4" y="1044.8" textLength="988.2" clip-path="url(#breeze-static-checks-line-42)">pylint | python-no-log-warn | replace-bad-characters | rst-backticks | ruff |    </text><text class="breeze-static-checks-r5" x="1451.8" y="1044.8" textLength="12.2" c [...] +</text><text class="breeze-static-checks-r5" x="0" y="1069.2" textLength="12.2" clip-path="url(#breeze-static-checks-line-43)">│</text><text class="breeze-static-checks-r7" x="451.4" y="1069.2" textLength="988.2" clip-path="url(#breeze-static-checks-line-43)">ruff-format | shellcheck | trailing-whitespace | ts-compile-format-lint-www |    </text><text class="breeze-static-checks-r5" x="1451.8" y="1069.2" textLength="12.2" clip-path=" [...] +</text><text class="breeze-static-checks-r5" x="0" y="1093.6" textLength="12.2" clip-path="url(#breeze-static-checks-line-44)">│</text><text class="breeze-static-checks-r7" x="451.4" y="1093.6" textLength="988.2" clip-path="url(#breeze-static-checks-line-44)">update-black-version | update-breeze-cmd-output |                             [...] +</text><text class="breeze-static-checks-r5" x="0" y="1118" textLength="12.2" clip-path="url(#breeze-static-checks-line-45)">│</text><text class="breeze-static-checks-r7" x="451.4" y="1118" textLength="988.2" clip-path="url(#breeze-static-checks-line-45)">update-breeze-readme-config-hash | update-build-dependencies |                   </text><text class="breeze-static-checks-r5" [...] +</text><text class="breeze-static-checks-r5" x="0" y="1142.4" textLength="12.2" clip-path="url(#breeze-static-checks-line-46)">│</text><text class="breeze-static-checks-r7" x="451.4" y="1142.4" textLength="988.2" clip-path="url(#breeze-static-checks-line-46)">update-chart-dependencies | update-common-sql-api-stubs | update-er-diagram |    </text><text class="breeze-static-checks-r5" x="1451.8" y="1142.4" textLength="12.2" clip-path="url(#breez [...] +</text><text class="breeze-static-checks-r5" x="0" y="1166.8" textLength="12.2" clip-path="url(#breeze-static-checks-line-47)">│</text><text class="breeze-static-checks-r7" x="451.4" y="1166.8" textLength="988.2" clip-path="url(#breeze-static-checks-line-47)">update-extras | update-in-the-wild-to-be-sorted |                             [...] +</text><text class="breeze-static-checks-r5" x="0" y="1191.2" textLength="12.2" clip-path="url(#breeze-static-checks-line-48)">│</text><text class="breeze-static-checks-r7" x="451.4" y="1191.2" textLength="988.2" clip-path="url(#breeze-static-checks-line-48)">update-inlined-dockerfile-scripts | update-installed-providers-to-be-sorted |    </text><text class="breeze-static-checks-r5" x="1451.8" y="1191.2" textLength="12.2" clip-path="url(#breeze-static-c [...] +</text><text class="breeze-static-checks-r5" x="0" y="1215.6" textLength="12.2" clip-path="url(#breeze-static-checks-line-49)">│</text><text class="breeze-static-checks-r7" x="451.4" y="1215.6" textLength="988.2" clip-path="url(#breeze-static-checks-line-49)">update-installers | update-local-yml-file | update-migration-references |        </text><text class="breeze-static-checks-r5" x="1451.8" y="1215.6" textLength="12.2" c [...] +</text><text class="breeze-static-checks-r5" x="0" y="1240" textLength="12.2" clip-path="url(#breeze-static-checks-line-50)">│</text><text class="breeze-static-checks-r7" x="451.4" y="1240" textLength="988.2" clip-path="url(#breeze-static-checks-line-50)">update-providers-dependencies | update-reproducible-source-date-epoch |          </text><text class="breeze-static-checks-r5" x="1451.8" y="1240" textLength="12.2" clip-pa [...] +</text><text class="breeze-static-checks-r5" x="0" y="1264.4" textLength="12.2" clip-path="url(#breeze-static-checks-line-51)">│</text><text class="breeze-static-checks-r7" x="451.4" y="1264.4" textLength="988.2" clip-path="url(#breeze-static-checks-line-51)">update-spelling-wordlist-to-be-sorted | update-supported-versions |              </text><text class="breeze-static-checks-r5" x="1451.8" y="1264.4" [...] +</text><text class="breeze-static-checks-r5" x="0" y="1288.8" textLength="12.2" clip-path="url(#breeze-static-checks-line-52)">│</text><text class="breeze-static-checks-r7" x="451.4" y="1288.8" textLength="988.2" clip-path="url(#breeze-static-checks-line-52)">update-vendored-in-k8s-json-schema | update-version | validate-operators-init |  </text><text class="breeze-static-checks-r5" x="1451.8" y="1288.8" textLength="12.2" clip-path="url(#breeze-static-c [...] +</text><text class="breeze-static-checks-r5" x="0" y="1313.2" textLength="12.2" clip-path="url(#breeze-static-checks-line-53)">│</text><text class="breeze-static-checks-r7" x="451.4" y="1313.2" textLength="988.2" clip-path="url(#breeze-static-checks-line-53)">yamllint)                                     & [...] +</text><text class="breeze-static-checks-r5" x="0" y="1337.6" textLength="12.2" clip-path="url(#breeze-static-checks-line-54)">│</text><text class="breeze-static-checks-r4" x="24.4" y="1337.6" textLength="12.2" clip-path="url(#breeze-static-checks-line-54)">-</text><text class="breeze-static-checks-r4" x="36.6" y="1337.6" textLength="61" clip-path="url(#breeze-static-checks-line-54)">-show</text><text class="breeze-static-checks-r4" x="97.6" y="1337.6" textLength="195.2" clip-path="url(# [...] +</text><text class="breeze-static-checks-r5" x="0" y="1362" textLength="12.2" clip-path="url(#breeze-static-checks-line-55)">│</text><text class="breeze-static-checks-r4" x="24.4" y="1362" textLength="12.2" clip-path="url(#breeze-static-checks-line-55)">-</text><text class="breeze-static-checks-r4" x="36.6" y="1362" textLength="134.2" clip-path="url(#breeze-static-checks-line-55)">-initialize</text><text class="breeze-static-checks-r4" x="170.8" y="1362" textLength="146.4" clip-path="url [...] +</text><text class="breeze-static-checks-r5" x="0" y="1386.4" textLength="12.2" clip-path="url(#breeze-static-checks-line-56)">│</text><text class="breeze-static-checks-r4" x="24.4" y="1386.4" textLength="12.2" clip-path="url(#breeze-static-checks-line-56)">-</text><text class="breeze-static-checks-r4" x="36.6" y="1386.4" textLength="48.8" clip-path="url(#breeze-static-checks-line-56)">-max</text><text class="breeze-static-checks-r4" x="85.4" y="1386.4" textLength="292.8" clip-path="url( [...] +</text><text class="breeze-static-checks-r5" x="0" y="1410.8" textLength="12.2" clip-path="url(#breeze-static-checks-line-57)">│</text><text class="breeze-static-checks-r7" x="451.4" y="1410.8" textLength="854" clip-path="url(#breeze-static-checks-line-57)">(INTEGER RANGE)                                    [...] +</text><text class="breeze-static-checks-r5" x="0" y="1435.2" textLength="12.2" clip-path="url(#breeze-static-checks-line-58)">│</text><text class="breeze-static-checks-r5" x="451.4" y="1435.2" textLength="854" clip-path="url(#breeze-static-checks-line-58)">[default: 3; 1<=x<=10]                                 [...] +</text><text class="breeze-static-checks-r5" x="0" y="1459.6" textLength="1464" clip-path="url(#breeze-static-checks-line-59)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="breeze-static-checks-r1" x="1464" y="1459.6" textLength="12.2" clip-path="url(#breeze-static-checks-line-59)"> +</text><text class="breeze-static-checks-r5" x="0" y="1484" textLength="24.4" clip-path="url(#breeze-static-checks-line-60)">╭─</text><text class="breeze-static-checks-r5" x="24.4" y="1484" textLength="463.6" clip-path="url(#breeze-static-checks-line-60)"> Selecting files to run the checks on </text><text class="breeze-static-checks-r5" x="488" y="1484" textLength="951.6" clip-path="url(#breeze-static-checks-line-60)">────────────────────────────── [...] +</text><text class="breeze-static-checks-r5" x="0" y="1508.4" textLength="12.2" clip-path="url(#breeze-static-checks-line-61)">│</text><text class="breeze-static-checks-r4" x="24.4" y="1508.4" textLength="12.2" clip-path="url(#breeze-static-checks-line-61)">-</text><text class="breeze-static-checks-r4" x="36.6" y="1508.4" textLength="61" clip-path="url(#breeze-static-checks-line-61)">-file</text><text class="breeze-static-checks-r6" x="256.2" y="1508.4" textLength="24.4" clip-path="url(# [...] +</text><text class="breeze-static-checks-r5" x="0" y="1532.8" textLength="12.2" clip-path="url(#breeze-static-checks-line-62)">│</text><text class="breeze-static-checks-r4" x="24.4" y="1532.8" textLength="12.2" clip-path="url(#breeze-static-checks-line-62)">-</text><text class="breeze-static-checks-r4" x="36.6" y="1532.8" textLength="48.8" clip-path="url(#breeze-static-checks-line-62)">-all</text><text class="breeze-static-checks-r4" x="85.4" y="1532.8" textLength="73.2" clip-path="url(# [...] +</text><text class="breeze-static-checks-r5" x="0" y="1557.2" textLength="12.2" clip-path="url(#breeze-static-checks-line-63)">│</text><text class="breeze-static-checks-r4" x="24.4" y="1557.2" textLength="12.2" clip-path="url(#breeze-static-checks-line-63)">-</text><text class="breeze-static-checks-r4" x="36.6" y="1557.2" textLength="85.4" clip-path="url(#breeze-static-checks-line-63)">-commit</text><text class="breeze-static-checks-r4" x="122" y="1557.2" textLength="48.8" clip-path="url [...] +</text><text class="breeze-static-checks-r5" x="0" y="1581.6" textLength="12.2" clip-path="url(#breeze-static-checks-line-64)">│</text><text class="breeze-static-checks-r1" x="305" y="1581.6" textLength="183" clip-path="url(#breeze-static-checks-line-64)">exclusive with </text><text class="breeze-static-checks-r4" x="488" y="1581.6" textLength="12.2" clip-path="url(#breeze-static-checks-line-64)">-</text><text class="breeze-static-checks-r4" x="500.2" y="1581.6" textLength="61" [...] +</text><text class="breeze-static-checks-r5" x="0" y="1606" textLength="12.2" clip-path="url(#breeze-static-checks-line-65)">│</text><text class="breeze-static-checks-r7" x="305" y="1606" textLength="1134.6" clip-path="url(#breeze-static-checks-line-65)">(TEXT)                                       [...] +</text><text class="breeze-static-checks-r5" x="0" y="1630.4" textLength="12.2" clip-path="url(#breeze-static-checks-line-66)">│</text><text class="breeze-static-checks-r4" x="24.4" y="1630.4" textLength="12.2" clip-path="url(#breeze-static-checks-line-66)">-</text><text class="breeze-static-checks-r4" x="36.6" y="1630.4" textLength="61" clip-path="url(#breeze-static-checks-line-66)">-last</text><text class="breeze-static-checks-r4" x="97.6" y="1630.4" textLength="85.4" clip-path="url(#b [...] +</text><text class="breeze-static-checks-r5" x="0" y="1654.8" textLength="12.2" clip-path="url(#breeze-static-checks-line-67)">│</text><text class="breeze-static-checks-r4" x="24.4" y="1654.8" textLength="12.2" clip-path="url(#breeze-static-checks-line-67)">-</text><text class="breeze-static-checks-r4" x="36.6" y="1654.8" textLength="61" clip-path="url(#breeze-static-checks-line-67)">-only</text><text class="breeze-static-checks-r4" x="97.6" y="1654.8" textLength="134.2" clip-path="url(# [...] +</text><text class="breeze-static-checks-r5" x="0" y="1679.2" textLength="12.2" clip-path="url(#breeze-static-checks-line-68)">│</text><text class="breeze-static-checks-r1" x="305" y="1679.2" textLength="1134.6" clip-path="url(#breeze-static-checks-line-68)">branch and HEAD of your branch.                              [...] +</text><text class="breeze-static-checks-r5" x="0" y="1703.6" textLength="1464" clip-path="url(#breeze-static-checks-line-69)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="breeze-static-checks-r1" x="1464" y="1703.6" textLength="12.2" clip-path="url(#breeze-static-checks-line-69)"> +</text><text class="breeze-static-checks-r5" x="0" y="1728" textLength="24.4" clip-path="url(#breeze-static-checks-line-70)">╭─</text><text class="breeze-static-checks-r5" x="24.4" y="1728" textLength="463.6" clip-path="url(#breeze-static-checks-line-70)"> Building image before running checks </text><text class="breeze-static-checks-r5" x="488" y="1728" textLength="951.6" clip-path="url(#breeze-static-checks-line-70)">──────────────────────────────────────── [...] +</text><text class="breeze-static-checks-r5" x="0" y="1752.4" textLength="12.2" clip-path="url(#breeze-static-checks-line-71)">│</text><text class="breeze-static-checks-r4" x="24.4" y="1752.4" textLength="12.2" clip-path="url(#breeze-static-checks-line-71)">-</text><text class="breeze-static-checks-r4" x="36.6" y="1752.4" textLength="61" clip-path="url(#breeze-static-checks-line-71)">-skip</text><text class="breeze-static-checks-r4" x="97.6" y="1752.4" textLength="244" clip-path="url(#br [...] +</text><text class="breeze-static-checks-r5" x="0" y="1776.8" textLength="12.2" clip-path="url(#breeze-static-checks-line-72)">│</text><text class="breeze-static-checks-r4" x="24.4" y="1776.8" textLength="12.2" clip-path="url(#breeze-static-checks-line-72)">-</text><text class="breeze-static-checks-r4" x="36.6" y="1776.8" textLength="73.2" clip-path="url(#breeze-static-checks-line-72)">-force</text><text class="breeze-static-checks-r4" x="109.8" y="1776.8" textLength="73.2" clip-path="ur [...] +</text><text class="breeze-static-checks-r5" x="0" y="1801.2" textLength="12.2" clip-path="url(#breeze-static-checks-line-73)">│</text><text class="breeze-static-checks-r4" x="24.4" y="1801.2" textLength="12.2" clip-path="url(#breeze-static-checks-line-73)">-</text><text class="breeze-static-checks-r4" x="36.6" y="1801.2" textLength="73.2" clip-path="url(#breeze-static-checks-line-73)">-image</text><text class="breeze-static-checks-r4" x="109.8" y="1801.2" textLength="48.8" clip-path="ur [...] +</text><text class="breeze-static-checks-r5" x="0" y="1825.6" textLength="12.2" clip-path="url(#breeze-static-checks-line-74)">│</text><text class="breeze-static-checks-r7" x="414.8" y="1825.6" textLength="963.8" clip-path="url(#breeze-static-checks-line-74)">(TEXT)                                      [...] +</text><text class="breeze-static-checks-r5" x="0" y="1850" textLength="12.2" clip-path="url(#breeze-static-checks-line-75)">│</text><text class="breeze-static-checks-r5" x="414.8" y="1850" textLength="963.8" clip-path="url(#breeze-static-checks-line-75)">[default: latest]                                    [...] +</text><text class="breeze-static-checks-r5" x="0" y="1874.4" textLength="12.2" clip-path="url(#breeze-static-checks-line-76)">│</text><text class="breeze-static-checks-r4" x="24.4" y="1874.4" textLength="12.2" clip-path="url(#breeze-static-checks-line-76)">-</text><text class="breeze-static-checks-r4" x="36.6" y="1874.4" textLength="85.4" clip-path="url(#breeze-static-checks-line-76)">-github</text><text class="breeze-static-checks-r4" x="122" y="1874.4" textLength="134.2" clip-path="ur [...] +</text><text class="breeze-static-checks-r5" x="0" y="1898.8" textLength="12.2" clip-path="url(#breeze-static-checks-line-77)">│</text><text class="breeze-static-checks-r4" x="24.4" y="1898.8" textLength="12.2" clip-path="url(#breeze-static-checks-line-77)">-</text><text class="breeze-static-checks-r4" x="36.6" y="1898.8" textLength="97.6" clip-path="url(#breeze-static-checks-line-77)">-builder</text><text class="breeze-static-checks-r1" x="414.8" y="1898.8" textLength="756.4" clip-path= [...] +</text><text class="breeze-static-checks-r5" x="0" y="1923.2" textLength="12.2" clip-path="url(#breeze-static-checks-line-78)">│</text><text class="breeze-static-checks-r5" x="414.8" y="1923.2" textLength="756.4" clip-path="url(#breeze-static-checks-line-78)">[default: autodetect]                                  &# [...] +</text><text class="breeze-static-checks-r5" x="0" y="1947.6" textLength="1464" clip-path="url(#breeze-static-checks-line-79)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="breeze-static-checks-r1" x="1464" y="1947.6" textLength="12.2" clip-path="url(#breeze-static-checks-line-79)"> +</text><text class="breeze-static-checks-r5" x="0" y="1972" textLength="24.4" clip-path="url(#breeze-static-checks-line-80)">╭─</text><text class="breeze-static-checks-r5" x="24.4" y="1972" textLength="195.2" clip-path="url(#breeze-static-checks-line-80)"> Common options </text><text class="breeze-static-checks-r5" x="219.6" y="1972" textLength="1220" clip-path="url(#breeze-static-checks-line-80)">──────────────────────────────────────────────────────────────────────────── [...] +</text><text class="breeze-static-checks-r5" x="0" y="1996.4" textLength="12.2" clip-path="url(#breeze-static-checks-line-81)">│</text><text class="breeze-static-checks-r4" x="24.4" y="1996.4" textLength="12.2" clip-path="url(#breeze-static-checks-line-81)">-</text><text class="breeze-static-checks-r4" x="36.6" y="1996.4" textLength="48.8" clip-path="url(#breeze-static-checks-line-81)">-dry</text><text class="breeze-static-checks-r4" x="85.4" y="1996.4" textLength="48.8" clip-path="url(# [...] +</text><text class="breeze-static-checks-r5" x="0" y="2020.8" textLength="12.2" clip-path="url(#breeze-static-checks-line-82)">│</text><text class="breeze-static-checks-r4" x="24.4" y="2020.8" textLength="12.2" clip-path="url(#breeze-static-checks-line-82)">-</text><text class="breeze-static-checks-r4" x="36.6" y="2020.8" textLength="97.6" clip-path="url(#breeze-static-checks-line-82)">-verbose</text><text class="breeze-static-checks-r6" x="158.6" y="2020.8" textLength="24.4" clip-path=" [...] +</text><text class="breeze-static-checks-r5" x="0" y="2045.2" textLength="12.2" clip-path="url(#breeze-static-checks-line-83)">│</text><text class="breeze-static-checks-r4" x="24.4" y="2045.2" textLength="12.2" clip-path="url(#breeze-static-checks-line-83)">-</text><text class="breeze-static-checks-r4" x="36.6" y="2045.2" textLength="61" clip-path="url(#breeze-static-checks-line-83)">-help</text><text class="breeze-static-checks-r6" x="158.6" y="2045.2" textLength="24.4" clip-path="url(# [...] +</text><text class="breeze-static-checks-r5" x="0" y="2069.6" textLength="1464" clip-path="url(#breeze-static-checks-line-84)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="breeze-static-checks-r1" x="1464" y="2069.6" textLength="12.2" clip-path="url(#breeze-static-checks-line-84)"> </text> </g> </g> diff --git a/dev/breeze/doc/images/output_static-checks.txt b/dev/breeze/doc/images/output_static-checks.txt index 66ca569ffb..10228461e0 100644 --- a/dev/breeze/doc/images/output_static-checks.txt +++ b/dev/breeze/doc/images/output_static-checks.txt @@ -1 +1 @@ -e8fa3d7a6215d2565dc536cbc50e0465 +4dc5653769bd03c4d07f6d998cdcb679 diff --git a/dev/breeze/src/airflow_breeze/pre_commit_ids.py b/dev/breeze/src/airflow_breeze/pre_commit_ids.py index 516fc85c51..867f16e0a9 100644 --- a/dev/breeze/src/airflow_breeze/pre_commit_ids.py +++ b/dev/breeze/src/airflow_breeze/pre_commit_ids.py @@ -76,6 +76,7 @@ PRE_COMMIT_LIST = [ "check-start-date-not-used-in-defaults", "check-system-tests-present", "check-system-tests-tocs", + "check-template-context-variable-in-sync", "check-tests-in-the-right-folders", "check-tests-unittest-testcase", "check-urlparse-usage-in-code", diff --git a/docs/apache-airflow/authoring-and-scheduling/datasets.rst b/docs/apache-airflow/authoring-and-scheduling/datasets.rst index 1102420dd4..5324a11bbc 100644 --- a/docs/apache-airflow/authoring-and-scheduling/datasets.rst +++ b/docs/apache-airflow/authoring-and-scheduling/datasets.rst @@ -99,8 +99,8 @@ The identifier does not have to be absolute; it can be a scheme-less, relative U Non-absolute identifiers are considered plain strings that do not carry any semantic meanings to Airflow. -Extra information ------------------ +Extra information on Dataset +---------------------------- If needed, an extra dictionary can be included in a Dataset: @@ -111,7 +111,7 @@ If needed, an extra dictionary can be included in a Dataset: extra={"team": "trainees"}, ) -This extra information does not affect a dataset's identity. This means a DAG will be triggered by a dataset with an identical URI, even if the extra dict is different: +This can be used to supply custom description to the dataset, such as who has ownership to the target file, or what the file is for. The extra information does not affect a dataset's identity. This means a DAG will be triggered by a dataset with an identical URI, even if the extra dict is different: .. code-block:: python @@ -224,6 +224,29 @@ If one dataset is updated multiple times before all consumed datasets have been } +Attaching extra information to an emitting Dataset Event +-------------------------------------------------------- + +.. versionadded:: 2.10.0 + +A task with a dataset outlet can optionally attach extra information before it emits a dataset event. This is different +from `Extra information on Dataset`_. Extra information on a dataset statically describes the entity pointed to by the dataset URI; extra information on the *dataset event* instead should be used to annotate the triggering data change, such as how many rows in the database are changed by the update, or the date range covered by it. + +The easiest way to attach extra information to the dataset event is by accessing ``dataset_events`` in a task's execution context: + +.. code-block:: python + + example_s3_dataset = Dataset("s3://dataset/example.csv") + + + @task(outlets=[example_s3_dataset]) + def write_to_s3(*, dataset_events): + df = ... # Get a Pandas DataFrame to write. + # Write df to dataset... + dataset_events[example_s3_dataset].extras = {"row_count": len(df)} + +This can also be done in classic operators by either subclassing the operator and overriding ``execute``, or by supplying a pre- or post-execution function. + Fetching information from a Triggering Dataset Event ---------------------------------------------------- @@ -234,7 +257,7 @@ Example: .. code-block:: python - example_snowflake_dataset = Dataset("snowflake://my_db.my_schema.my_table") + example_snowflake_dataset = Dataset("snowflake://my_db/my_schema/my_table") with DAG(dag_id="load_snowflake_data", schedule="@hourly", ...): SQLExecuteQueryOperator( diff --git a/docs/apache-airflow/templates-ref.rst b/docs/apache-airflow/templates-ref.rst index dd05fcc831..4d3014268b 100644 --- a/docs/apache-airflow/templates-ref.rst +++ b/docs/apache-airflow/templates-ref.rst @@ -74,6 +74,8 @@ Variable Type Description ``{{ var.value }}`` Airflow variables. See `Airflow Variables in Templates`_ below. ``{{ var.json }}`` Airflow variables. See `Airflow Variables in Templates`_ below. ``{{ conn }}`` Airflow connections. See `Airflow Connections in Templates`_ below. +``{{ dataset_events }}`` dict[str, ...] | Accessors to attach information to dataset events that will be emitted by the current task. + | See :doc:`Datasets <authoring-and-scheduling/datasets>`. Added in version 2.10. ``{{ task_instance_key_str }}`` str | A unique, human-readable key to the task instance. The format is | ``{dag_id}__{task_id}__{ds_nodash}``. ``{{ conf }}`` AirflowConfigParser | The full configuration object representing the content of your diff --git a/scripts/ci/pre_commit/pre_commit_template_context_key_sync.py b/scripts/ci/pre_commit/pre_commit_template_context_key_sync.py old mode 100644 new mode 100755 diff --git a/tests/models/test_taskinstance.py b/tests/models/test_taskinstance.py index 120856dbbf..e618731142 100644 --- a/tests/models/test_taskinstance.py +++ b/tests/models/test_taskinstance.py @@ -35,6 +35,7 @@ from uuid import uuid4 import pendulum import pytest import time_machine +from sqlalchemy import select from airflow import settings from airflow.decorators import task, task_group @@ -2281,7 +2282,7 @@ class TestTaskInstance: task_instance.run() assert task_instance.current_state() == TaskInstanceState.SUCCESS - def test_outlet_datasets_skipped(self, create_task_instance): + def test_outlet_datasets_skipped(self): """ Verify that when we have an outlet dataset on a task, and the task is skipped, a DatasetDagRunQueue is not logged, and a DatasetEvent is @@ -2311,7 +2312,69 @@ class TestTaskInstance: # check that no dataset events were generated assert session.query(DatasetEvent).count() == 0 - def test_changing_of_dataset_when_ddrq_is_already_populated(self, dag_maker, session): + def test_outlet_dataset_extra(self, dag_maker, session): + from airflow.datasets import Dataset + + with dag_maker(schedule=None, session=session) as dag: + + @task(outlets=Dataset("test_outlet_dataset_extra_1")) + def write1(*, dataset_events): + dataset_events["test_outlet_dataset_extra_1"].extra = {"foo": "bar"} + + write1() + + def _write2_post_execute(context, _): + context["dataset_events"]["test_outlet_dataset_extra_2"].extra = {"x": 1} + + BashOperator( + task_id="write2", + bash_command=":", + outlets=Dataset("test_outlet_dataset_extra_2"), + post_execute=_write2_post_execute, + ) + + dr: DagRun = dag_maker.create_dagrun() + for ti in dr.get_task_instances(session=session): + ti.refresh_from_task(dag.get_task(ti.task_id)) + ti.run(session=session) + + events = dict(iter(session.execute(select(DatasetEvent.source_task_id, DatasetEvent)))) + assert set(events) == {"write1", "write2"} + + assert events["write1"].source_dag_id == dr.dag_id + assert events["write1"].source_run_id == dr.run_id + assert events["write1"].source_task_id == "write1" + assert events["write1"].dataset.uri == "test_outlet_dataset_extra_1" + assert events["write1"].extra == {"foo": "bar"} + + assert events["write2"].source_dag_id == dr.dag_id + assert events["write2"].source_run_id == dr.run_id + assert events["write2"].source_task_id == "write2" + assert events["write2"].dataset.uri == "test_outlet_dataset_extra_2" + assert events["write2"].extra == {"x": 1} + + def test_outlet_dataset_extra_ignore_different(self, dag_maker, session): + from airflow.datasets import Dataset + + with dag_maker(schedule=None, session=session): + + @task(outlets=Dataset("test_outlet_dataset_extra")) + def write(*, dataset_events): + dataset_events["test_outlet_dataset_extra"].extra = {"one": 1} + dataset_events["different_uri"].extra = {"foo": "bar"} # Will be silently dropped. + + write() + + dr: DagRun = dag_maker.create_dagrun() + dr.get_task_instance("write").run(session=session) + + event = session.scalars(select(DatasetEvent)).one() + assert event.source_dag_id == dr.dag_id + assert event.source_run_id == dr.run_id + assert event.source_task_id == "write" + assert event.extra == {"one": 1} + + def test_changing_of_dataset_when_ddrq_is_already_populated(self, dag_maker): """ Test that when a task that produces dataset has ran, that changing the consumer dag dataset will not cause primary key blank-out diff --git a/tests/operators/test_python.py b/tests/operators/test_python.py index 578302a836..b8876f97ec 100644 --- a/tests/operators/test_python.py +++ b/tests/operators/test_python.py @@ -834,6 +834,7 @@ class BaseTestPythonVirtualenvOperator(BasePythonTest): "ti", "var", # Accessor for Variable; var->json and var->value. "conn", # Accessor for Connection. + "dataset_events", # Accessor for DatasetEvent. ] ti = create_task_instance(dag_id=self.dag_id, task_id=self.task_id, schedule=None)