This is an automated email from the ASF dual-hosted git repository.

eladkal 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 8961bab6b2 Prevent templated field logic checks in __init__ of 
operators automatically (#33786)
8961bab6b2 is described below

commit 8961bab6b2ed387bea1193fb3199a1c56ba4455c
Author: Shahar Epstein <[email protected]>
AuthorDate: Sat Jan 20 16:58:56 2024 +0200

    Prevent templated field logic checks in __init__ of operators automatically 
(#33786)
    
    * Implement validation of missing template fields
    
    * Update output static checks
---
 .pre-commit-config.yaml                            |  25 +++
 STATIC_CODE_CHECKS.rst                             |   2 +
 dev/breeze/src/airflow_breeze/pre_commit_ids.py    |   1 +
 docs/apache-airflow/howto/custom-operator.rst      |  82 +++++++
 images/breeze/output_static-checks.svg             |  74 ++++---
 images/breeze/output_static-checks.txt             |   2 +-
 .../pre_commit_validate_operators_init.py          | 236 +++++++++++++++++++++
 7 files changed, 386 insertions(+), 36 deletions(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 676e8b0e4f..cf1f8218cf 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -293,6 +293,31 @@ repos:
     # changes quickly - especially when we want the early modifications from 
the first local group
     # to be applied before the non-local pre-commits are run
     hooks:
+      - id: validate-operators-init
+        name: Prevent templated field logic checks in operators' __init__
+        language: python
+        entry: ./scripts/ci/pre_commit/pre_commit_validate_operators_init.py
+        pass_filenames: true
+        files: ^airflow/providers/.*/(operators|transfers|sensors)/.*\.py$
+        additional_dependencies: [ 'rich>=12.4.4' ]
+        # TODO: Handle the provider-specific exclusions and remove them from 
the list, see:
+        #       https://github.com/apache/airflow/issues/36484
+        exclude: |
+          (?x)^(
+              ^.*__init__\.py$|
+              ^airflow\/providers\/google\/cloud\/operators\/bigquery\.py$|
+              ^airflow\/providers\/amazon\/aws\/transfers\/gcs_to_s3\.py$|
+              ^airflow\/providers\/databricks\/operators\/databricks\.py$|
+              
^airflow\/providers\/google\/cloud\/operators\/cloud_storage_transfer_service\.py$|
+              
^airflow\/providers\/google\/cloud\/transfers\/bigquery_to_mysql\.py$|
+              
^airflow\/providers\/google\/cloud\/operators\/vertex_ai\/auto_ml\.py$|
+              ^airflow\/providers\/amazon\/aws\/transfers\/redshift_to_s3\.py$|
+              ^airflow\/providers\/google\/cloud\/operators\/compute\.py$|
+              
^airflow\/providers\/google\/cloud\/operators\/vertex_ai\/custom_job\.py$|
+              ^airflow\/providers\/cncf\/kubernetes\/operators\/pod\.py$|
+              ^airflow\/providers\/amazon\/aws\/operators\/emr\.py$|
+              ^airflow\/providers\/amazon\/aws\/operators\/eks\.py$
+          )$
       - id: ruff
         name: Run 'ruff' for extremely fast Python linting
         description: "Run 'ruff' for extremely fast Python linting"
diff --git a/STATIC_CODE_CHECKS.rst b/STATIC_CODE_CHECKS.rst
index b03b143321..dd56039f2d 100644
--- a/STATIC_CODE_CHECKS.rst
+++ b/STATIC_CODE_CHECKS.rst
@@ -414,6 +414,8 @@ require Breeze Docker image to be built locally.
 
+-----------------------------------------------------------+--------------------------------------------------------------+---------+
 | update-version                                            | Update version 
to the latest version in the documentation    |         |
 
+-----------------------------------------------------------+--------------------------------------------------------------+---------+
+| validate-operators-init                                   | Prevent 
templated field logic checks in operators' __init__  |         |
++-----------------------------------------------------------+--------------------------------------------------------------+---------+
 | yamllint                                                  | Check YAML files 
with yamllint                               |         |
 
+-----------------------------------------------------------+--------------------------------------------------------------+---------+
 
diff --git a/dev/breeze/src/airflow_breeze/pre_commit_ids.py 
b/dev/breeze/src/airflow_breeze/pre_commit_ids.py
index e27068f5c9..ad6614d452 100644
--- a/dev/breeze/src/airflow_breeze/pre_commit_ids.py
+++ b/dev/breeze/src/airflow_breeze/pre_commit_ids.py
@@ -133,5 +133,6 @@ PRE_COMMIT_LIST = [
     "update-supported-versions",
     "update-vendored-in-k8s-json-schema",
     "update-version",
+    "validate-operators-init",
     "yamllint",
 ]
diff --git a/docs/apache-airflow/howto/custom-operator.rst 
b/docs/apache-airflow/howto/custom-operator.rst
index 012409ec74..ce32654a6b 100644
--- a/docs/apache-airflow/howto/custom-operator.rst
+++ b/docs/apache-airflow/howto/custom-operator.rst
@@ -269,6 +269,88 @@ Currently available lexers:
 
 If you use a non-existing lexer then the value of the template field will be 
rendered as a pretty-printed object.
 
+Limitations
+^^^^^^^^^^^
+To prevent misuse, the following limitations must be observed when defining 
and assigning templated fields in the
+operator's constructor (when such exists, otherwise - see below):
+
+1. Templated fields' corresponding parameters passed into the constructor must 
be named exactly
+as the fields. The following example is invalid, as the parameter passed into 
the constructor is not the same as the
+templated field:
+
+.. code-block:: python
+
+        class HelloOperator(BaseOperator):
+            template_fields = "field_a"
+
+            def __init__(field_a_id) -> None:  # <- should be def 
__init__(field_a)-> None
+                self.field_a = field_a_id  # <- should be self.field_a = 
field_a
+
+2. Templated fields' instance members must be assigned with their 
corresponding parameter from the constructor,
+either by a direct assignment or by calling the parent's constructor (in which 
these fields are
+defined as ``template_fields``) with explicit an assignment of the parameter.
+The following example is invalid, as the instance member ``self.field_a`` is 
not assigned at all, despite being a
+templated field:
+
+.. code-block:: python
+
+        class HelloOperator(BaseOperator):
+            template_fields = ("field_a", "field_b")
+
+            def __init__(field_a, field_b) -> None:
+                self.field_b = field_b
+
+
+The following example is also invalid, as the instance member ``self.field_a`` 
of ``MyHelloOperator`` is initialized
+implicitly as part of the ``kwargs`` passed to its parent constructor:
+
+.. code-block:: python
+
+        class HelloOperator(BaseOperator):
+            template_fields = "field_a"
+
+            def __init__(field_a) -> None:
+                self.field_a = field_a
+
+
+        class MyHelloOperator(HelloOperator):
+            template_fields = ("field_a", "field_b")
+
+            def __init__(field_b, **kwargs) -> None:  # <- should be def 
__init__(field_a, field_b, **kwargs)
+                super().__init__(**kwargs)  # <- should be 
super().__init__(field_a=field_a, **kwargs)
+                self.field_b = field_b
+
+3. Applying actions on the parameter during the assignment in the constructor 
is not allowed.
+Any action on the value should be applied in the ``execute()`` method.
+Therefore, the following example is invalid:
+
+.. code-block:: python
+
+        class HelloOperator(BaseOperator):
+            template_fields = "field_a"
+
+            def __init__(field_a) -> None:
+                self.field_a = field_a.lower()  # <- assignment should be only 
self.field_a = field_a
+
+When an operator inherits from a base operator and does not have a constructor 
defined on its own, the limitations above
+do not apply. However, the templated fields must be set properly in the parent 
according to those limitations.
+
+Thus, the following example is valid:
+
+.. code-block:: python
+
+        class HelloOperator(BaseOperator):
+            template_fields = "field_a"
+
+            def __init__(field_a) -> None:
+                self.field_a = field_a
+
+
+        class MyHelloOperator(HelloOperator):
+            template_fields = "field_a"
+
+The limitations above are enforced by a pre-commit named 
'validate-operators-init'.
+
 Add template fields with subclassing
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 A common use case for creating a custom operator is for simply augmenting 
existing ``template_fields``.
diff --git a/images/breeze/output_static-checks.svg 
b/images/breeze/output_static-checks.svg
index 68665eb531..f05cd5435b 100644
--- a/images/breeze/output_static-checks.svg
+++ b/images/breeze/output_static-checks.svg
@@ -1,4 +1,4 @@
-<svg class="rich-terminal" viewBox="0 0 1482 2050.8" 
xmlns="http://www.w3.org/2000/svg";>
+<svg class="rich-terminal" viewBox="0 0 1482 2075.2" 
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="1999.8" />
+      <rect x="0" y="0" width="1463.0" height="2024.1999999999998" />
     </clipPath>
     <clipPath id="breeze-static-checks-line-0">
     <rect x="0" y="1.5" width="1464" height="24.65"/>
@@ -288,9 +288,12 @@
 <clipPath id="breeze-static-checks-line-80">
     <rect x="0" y="1953.5" width="1464" height="24.65"/>
             </clipPath>
+<clipPath id="breeze-static-checks-line-81">
+    <rect x="0" y="1977.9" 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="2048.8" rx="8"/><text 
class="breeze-static-checks-title" fill="#c5c8c6" text-anchor="middle" x="740" 
y="27">Command:&#160;static-checks</text>
+    <rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1" 
x="1" y="1" width="1480" height="2073.2" rx="8"/><text 
class="breeze-static-checks-title" fill="#c5c8c6" text-anchor="middle" x="740" 
y="27">Command:&#160;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"/>
@@ -350,38 +353,39 @@
 </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-local-yml-file&#160;|&#160;update-migration-references&#160;|&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#
 [...]
 </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-providers-dependencies&#160;|&#160;update-reproducible-source-date-epoch&#160;|&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</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-spelling-wordlist-to-be-sorted&#160;|&#160;update-supported-versions&#160;|&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text
 class="breeze-static-checks-r5" x="1451.8" y="1215.6" [...]
-</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-vendored-in-k8s-json-schema&#160;|&#160;update-version&#160;|&#160;yamllint)&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text
 class="breeze-static-checks-r5"  [...]
-</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-r4" x="24.4" y="1264.4" textLength="12.2" 
clip-path="url(#breeze-static-checks-line-51)">-</text><text 
class="breeze-static-checks-r4" x="36.6" y="1264.4" textLength="61" 
clip-path="url(#breeze-static-checks-line-51)">-show</text><text 
class="breeze-static-checks-r4" x="97.6" y="1264.4" textLength="195.2" 
clip-path="url(# [...]
-</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-r4" x="24.4" y="1288.8" textLength="12.2" 
clip-path="url(#breeze-static-checks-line-52)">-</text><text 
class="breeze-static-checks-r4" x="36.6" y="1288.8" textLength="134.2" 
clip-path="url(#breeze-static-checks-line-52)">-initialize</text><text 
class="breeze-static-checks-r4" x="170.8" y="1288.8" textLength="146.4" clip-p 
[...]
-</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="48.8" 
clip-path="url(#breeze-static-checks-line-53)">-max</text><text 
class="breeze-static-checks-r4" x="85.4" y="1313.2" textLength="292.8" 
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-r7" x="451.4" y="1337.6" textLength="854" 
clip-path="url(#breeze-static-checks-line-54)">(INTEGER&#160;RANGE)&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#16
 [...]
-</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-r5" x="451.4" y="1362" textLength="854" 
clip-path="url(#breeze-static-checks-line-55)">[default:&#160;3;&#160;1&lt;=x&lt;=10]&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#
 [...]
-</text><text class="breeze-static-checks-r5" x="0" y="1386.4" 
textLength="1464" 
clip-path="url(#breeze-static-checks-line-56)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-static-checks-r1" x="1464" y="1386.4" textLength="12.2" 
clip-path="url(#breeze-static-checks-line-56)">
-</text><text class="breeze-static-checks-r5" x="0" y="1410.8" 
textLength="24.4" clip-path="url(#breeze-static-checks-line-57)">╭─</text><text 
class="breeze-static-checks-r5" x="24.4" y="1410.8" textLength="463.6" 
clip-path="url(#breeze-static-checks-line-57)">&#160;Selecting&#160;files&#160;to&#160;run&#160;the&#160;checks&#160;on&#160;</text><text
 class="breeze-static-checks-r5" x="488" y="1410.8" textLength="951.6" 
clip-path="url(#breeze-static-checks-line-57)">──────────────────────── [...]
-</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-r4" x="24.4" y="1435.2" textLength="12.2" 
clip-path="url(#breeze-static-checks-line-58)">-</text><text 
class="breeze-static-checks-r4" x="36.6" y="1435.2" textLength="61" 
clip-path="url(#breeze-static-checks-line-58)">-file</text><text 
class="breeze-static-checks-r6" x="256.2" y="1435.2" textLength="24.4" 
clip-path="url(# [...]
-</text><text class="breeze-static-checks-r5" x="0" y="1459.6" 
textLength="12.2" clip-path="url(#breeze-static-checks-line-59)">│</text><text 
class="breeze-static-checks-r4" x="24.4" y="1459.6" textLength="12.2" 
clip-path="url(#breeze-static-checks-line-59)">-</text><text 
class="breeze-static-checks-r4" x="36.6" y="1459.6" textLength="48.8" 
clip-path="url(#breeze-static-checks-line-59)">-all</text><text 
class="breeze-static-checks-r4" x="85.4" y="1459.6" textLength="73.2" 
clip-path="url(# [...]
-</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="85.4" 
clip-path="url(#breeze-static-checks-line-60)">-commit</text><text 
class="breeze-static-checks-r4" x="122" y="1484" textLength="48.8" 
clip-path="url(#breeze [...]
-</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-r1" x="305" y="1508.4" textLength="183" 
clip-path="url(#breeze-static-checks-line-61)">exclusive&#160;with&#160;</text><text
 class="breeze-static-checks-r4" x="488" y="1508.4" textLength="12.2" 
clip-path="url(#breeze-static-checks-line-61)">-</text><text 
class="breeze-static-checks-r4" x="500.2" y="1508.4" textLength="61" [...]
-</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-r7" x="305" y="1532.8" textLength="1134.6" 
clip-path="url(#breeze-static-checks-line-62)">(TEXT)&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160
 [...]
-</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="61" 
clip-path="url(#breeze-static-checks-line-63)">-last</text><text 
class="breeze-static-checks-r4" x="97.6" y="1557.2" textLength="85.4" 
clip-path="url(#b [...]
-</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-r4" x="24.4" y="1581.6" textLength="12.2" 
clip-path="url(#breeze-static-checks-line-64)">-</text><text 
class="breeze-static-checks-r4" x="36.6" y="1581.6" textLength="61" 
clip-path="url(#breeze-static-checks-line-64)">-only</text><text 
class="breeze-static-checks-r4" x="97.6" y="1581.6" textLength="134.2" 
clip-path="url(# [...]
-</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-r1" x="305" y="1606" textLength="1134.6" 
clip-path="url(#breeze-static-checks-line-65)">branch&#160;and&#160;HEAD&#160;of&#160;your&#160;branch.&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&
 [...]
-</text><text class="breeze-static-checks-r5" x="0" y="1630.4" 
textLength="1464" 
clip-path="url(#breeze-static-checks-line-66)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-static-checks-r1" x="1464" y="1630.4" textLength="12.2" 
clip-path="url(#breeze-static-checks-line-66)">
-</text><text class="breeze-static-checks-r5" x="0" y="1654.8" 
textLength="24.4" clip-path="url(#breeze-static-checks-line-67)">╭─</text><text 
class="breeze-static-checks-r5" x="24.4" y="1654.8" textLength="463.6" 
clip-path="url(#breeze-static-checks-line-67)">&#160;Building&#160;image&#160;before&#160;running&#160;checks&#160;</text><text
 class="breeze-static-checks-r5" x="488" y="1654.8" textLength="951.6" 
clip-path="url(#breeze-static-checks-line-67)">──────────────────────────────────
 [...]
-</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-r4" x="24.4" y="1679.2" textLength="12.2" 
clip-path="url(#breeze-static-checks-line-68)">-</text><text 
class="breeze-static-checks-r4" x="36.6" y="1679.2" textLength="61" 
clip-path="url(#breeze-static-checks-line-68)">-skip</text><text 
class="breeze-static-checks-r4" x="97.6" y="1679.2" textLength="244" 
clip-path="url(#br [...]
-</text><text class="breeze-static-checks-r5" x="0" y="1703.6" 
textLength="12.2" clip-path="url(#breeze-static-checks-line-69)">│</text><text 
class="breeze-static-checks-r4" x="24.4" y="1703.6" textLength="12.2" 
clip-path="url(#breeze-static-checks-line-69)">-</text><text 
class="breeze-static-checks-r4" x="36.6" y="1703.6" textLength="73.2" 
clip-path="url(#breeze-static-checks-line-69)">-force</text><text 
class="breeze-static-checks-r4" x="109.8" y="1703.6" textLength="73.2" 
clip-path="ur [...]
-</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="73.2" 
clip-path="url(#breeze-static-checks-line-70)">-image</text><text 
class="breeze-static-checks-r4" x="109.8" y="1728" textLength="48.8" 
clip-path="url(#breez [...]
-</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-r7" x="414.8" y="1752.4" textLength="963.8" 
clip-path="url(#breeze-static-checks-line-71)">(TEXT)&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#16
 [...]
-</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-r5" x="414.8" y="1776.8" textLength="963.8" 
clip-path="url(#breeze-static-checks-line-72)">[default:&#160;latest]&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;
 [...]
-</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="85.4" 
clip-path="url(#breeze-static-checks-line-73)">-github</text><text 
class="breeze-static-checks-r4" x="122" y="1801.2" textLength="134.2" 
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-r4" x="24.4" y="1825.6" textLength="12.2" 
clip-path="url(#breeze-static-checks-line-74)">-</text><text 
class="breeze-static-checks-r4" x="36.6" y="1825.6" textLength="97.6" 
clip-path="url(#breeze-static-checks-line-74)">-builder</text><text 
class="breeze-static-checks-r1" x="414.8" y="1825.6" textLength="756.4" 
clip-path= [...]
-</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="756.4" 
clip-path="url(#breeze-static-checks-line-75)">[default:&#160;autodetect]&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;
 [...]
-</text><text class="breeze-static-checks-r5" x="0" y="1874.4" 
textLength="1464" 
clip-path="url(#breeze-static-checks-line-76)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-static-checks-r1" x="1464" y="1874.4" textLength="12.2" 
clip-path="url(#breeze-static-checks-line-76)">
-</text><text class="breeze-static-checks-r5" x="0" y="1898.8" 
textLength="24.4" clip-path="url(#breeze-static-checks-line-77)">╭─</text><text 
class="breeze-static-checks-r5" x="24.4" y="1898.8" textLength="195.2" 
clip-path="url(#breeze-static-checks-line-77)">&#160;Common&#160;options&#160;</text><text
 class="breeze-static-checks-r5" x="219.6" y="1898.8" textLength="1220" 
clip-path="url(#breeze-static-checks-line-77)">──────────────────────────────────────────────────────────────────────
 [...]
-</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-r4" x="24.4" y="1923.2" textLength="12.2" 
clip-path="url(#breeze-static-checks-line-78)">-</text><text 
class="breeze-static-checks-r4" x="36.6" y="1923.2" textLength="48.8" 
clip-path="url(#breeze-static-checks-line-78)">-dry</text><text 
class="breeze-static-checks-r4" x="85.4" y="1923.2" textLength="48.8" 
clip-path="url(# [...]
-</text><text class="breeze-static-checks-r5" x="0" y="1947.6" 
textLength="12.2" clip-path="url(#breeze-static-checks-line-79)">│</text><text 
class="breeze-static-checks-r4" x="24.4" y="1947.6" textLength="12.2" 
clip-path="url(#breeze-static-checks-line-79)">-</text><text 
class="breeze-static-checks-r4" x="36.6" y="1947.6" textLength="97.6" 
clip-path="url(#breeze-static-checks-line-79)">-verbose</text><text 
class="breeze-static-checks-r6" x="158.6" y="1947.6" textLength="24.4" 
clip-path=" [...]
-</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="61" 
clip-path="url(#breeze-static-checks-line-80)">-help</text><text 
class="breeze-static-checks-r6" x="158.6" y="1972" textLength="24.4" 
clip-path="url(#breeze-s [...]
-</text><text class="breeze-static-checks-r5" x="0" y="1996.4" 
textLength="1464" 
clip-path="url(#breeze-static-checks-line-81)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-static-checks-r1" x="1464" y="1996.4" textLength="12.2" 
clip-path="url(#breeze-static-checks-line-81)">
+</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-vendored-in-k8s-json-schema&#160;|&#160;update-version&#160;|&#160;validate-operators-init&#160;|&#160;&#160;</text><text
 class="breeze-static-checks-r5" x="1451.8" y="1240" textLength="12.2" 
clip-path="url(#breeze-static-checks- [...]
+</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)">yamllint)&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&
 [...]
+</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-r4" x="24.4" y="1288.8" textLength="12.2" 
clip-path="url(#breeze-static-checks-line-52)">-</text><text 
class="breeze-static-checks-r4" x="36.6" y="1288.8" textLength="61" 
clip-path="url(#breeze-static-checks-line-52)">-show</text><text 
class="breeze-static-checks-r4" x="97.6" y="1288.8" textLength="195.2" 
clip-path="url(# [...]
+</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="134.2" 
clip-path="url(#breeze-static-checks-line-53)">-initialize</text><text 
class="breeze-static-checks-r4" x="170.8" y="1313.2" textLength="146.4" clip-p 
[...]
+</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="48.8" 
clip-path="url(#breeze-static-checks-line-54)">-max</text><text 
class="breeze-static-checks-r4" x="85.4" y="1337.6" textLength="292.8" 
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-r7" x="451.4" y="1362" textLength="854" 
clip-path="url(#breeze-static-checks-line-55)">(INTEGER&#160;RANGE)&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#
 [...]
+</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-r5" x="451.4" y="1386.4" textLength="854" 
clip-path="url(#breeze-static-checks-line-56)">[default:&#160;3;&#160;1&lt;=x&lt;=10]&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#16
 [...]
+</text><text class="breeze-static-checks-r5" x="0" y="1410.8" 
textLength="1464" 
clip-path="url(#breeze-static-checks-line-57)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-static-checks-r1" x="1464" y="1410.8" textLength="12.2" 
clip-path="url(#breeze-static-checks-line-57)">
+</text><text class="breeze-static-checks-r5" x="0" y="1435.2" 
textLength="24.4" clip-path="url(#breeze-static-checks-line-58)">╭─</text><text 
class="breeze-static-checks-r5" x="24.4" y="1435.2" textLength="463.6" 
clip-path="url(#breeze-static-checks-line-58)">&#160;Selecting&#160;files&#160;to&#160;run&#160;the&#160;checks&#160;on&#160;</text><text
 class="breeze-static-checks-r5" x="488" y="1435.2" textLength="951.6" 
clip-path="url(#breeze-static-checks-line-58)">──────────────────────── [...]
+</text><text class="breeze-static-checks-r5" x="0" y="1459.6" 
textLength="12.2" clip-path="url(#breeze-static-checks-line-59)">│</text><text 
class="breeze-static-checks-r4" x="24.4" y="1459.6" textLength="12.2" 
clip-path="url(#breeze-static-checks-line-59)">-</text><text 
class="breeze-static-checks-r4" x="36.6" y="1459.6" textLength="61" 
clip-path="url(#breeze-static-checks-line-59)">-file</text><text 
class="breeze-static-checks-r6" x="256.2" y="1459.6" textLength="24.4" 
clip-path="url(# [...]
+</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="48.8" 
clip-path="url(#breeze-static-checks-line-60)">-all</text><text 
class="breeze-static-checks-r4" x="85.4" y="1484" textLength="73.2" 
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="85.4" 
clip-path="url(#breeze-static-checks-line-61)">-commit</text><text 
class="breeze-static-checks-r4" x="122" y="1508.4" textLength="48.8" 
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-r1" x="305" y="1532.8" textLength="183" 
clip-path="url(#breeze-static-checks-line-62)">exclusive&#160;with&#160;</text><text
 class="breeze-static-checks-r4" x="488" y="1532.8" textLength="12.2" 
clip-path="url(#breeze-static-checks-line-62)">-</text><text 
class="breeze-static-checks-r4" x="500.2" y="1532.8" textLength="61" [...]
+</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-r7" x="305" y="1557.2" textLength="1134.6" 
clip-path="url(#breeze-static-checks-line-63)">(TEXT)&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160
 [...]
+</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-r4" x="24.4" y="1581.6" textLength="12.2" 
clip-path="url(#breeze-static-checks-line-64)">-</text><text 
class="breeze-static-checks-r4" x="36.6" y="1581.6" textLength="61" 
clip-path="url(#breeze-static-checks-line-64)">-last</text><text 
class="breeze-static-checks-r4" x="97.6" y="1581.6" textLength="85.4" 
clip-path="url(#b [...]
+</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)">-only</text><text 
class="breeze-static-checks-r4" x="97.6" y="1606" textLength="134.2" 
clip-path="url(#breeze-s [...]
+</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-r1" x="305" y="1630.4" textLength="1134.6" 
clip-path="url(#breeze-static-checks-line-66)">branch&#160;and&#160;HEAD&#160;of&#160;your&#160;branch.&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#1
 [...]
+</text><text class="breeze-static-checks-r5" x="0" y="1654.8" 
textLength="1464" 
clip-path="url(#breeze-static-checks-line-67)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-static-checks-r1" x="1464" y="1654.8" textLength="12.2" 
clip-path="url(#breeze-static-checks-line-67)">
+</text><text class="breeze-static-checks-r5" x="0" y="1679.2" 
textLength="24.4" clip-path="url(#breeze-static-checks-line-68)">╭─</text><text 
class="breeze-static-checks-r5" x="24.4" y="1679.2" textLength="463.6" 
clip-path="url(#breeze-static-checks-line-68)">&#160;Building&#160;image&#160;before&#160;running&#160;checks&#160;</text><text
 class="breeze-static-checks-r5" x="488" y="1679.2" textLength="951.6" 
clip-path="url(#breeze-static-checks-line-68)">──────────────────────────────────
 [...]
+</text><text class="breeze-static-checks-r5" x="0" y="1703.6" 
textLength="12.2" clip-path="url(#breeze-static-checks-line-69)">│</text><text 
class="breeze-static-checks-r4" x="24.4" y="1703.6" textLength="12.2" 
clip-path="url(#breeze-static-checks-line-69)">-</text><text 
class="breeze-static-checks-r4" x="36.6" y="1703.6" textLength="61" 
clip-path="url(#breeze-static-checks-line-69)">-skip</text><text 
class="breeze-static-checks-r4" x="97.6" y="1703.6" textLength="244" 
clip-path="url(#br [...]
+</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="73.2" 
clip-path="url(#breeze-static-checks-line-70)">-force</text><text 
class="breeze-static-checks-r4" x="109.8" y="1728" textLength="73.2" 
clip-path="url(#breez [...]
+</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)">-image</text><text 
class="breeze-static-checks-r4" x="109.8" y="1752.4" textLength="48.8" 
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-r7" x="414.8" y="1776.8" textLength="963.8" 
clip-path="url(#breeze-static-checks-line-72)">(TEXT)&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#16
 [...]
+</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-r5" x="414.8" y="1801.2" textLength="963.8" 
clip-path="url(#breeze-static-checks-line-73)">[default:&#160;latest]&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;
 [...]
+</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-r4" x="24.4" y="1825.6" textLength="12.2" 
clip-path="url(#breeze-static-checks-line-74)">-</text><text 
class="breeze-static-checks-r4" x="36.6" y="1825.6" textLength="85.4" 
clip-path="url(#breeze-static-checks-line-74)">-github</text><text 
class="breeze-static-checks-r4" x="122" y="1825.6" textLength="134.2" 
clip-path="ur [...]
+</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="97.6" 
clip-path="url(#breeze-static-checks-line-75)">-builder</text><text 
class="breeze-static-checks-r1" x="414.8" y="1850" textLength="756.4" 
clip-path="url(#br [...]
+</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-r5" x="414.8" y="1874.4" textLength="756.4" 
clip-path="url(#breeze-static-checks-line-76)">[default:&#160;autodetect]&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#
 [...]
+</text><text class="breeze-static-checks-r5" x="0" y="1898.8" 
textLength="1464" 
clip-path="url(#breeze-static-checks-line-77)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-static-checks-r1" x="1464" y="1898.8" textLength="12.2" 
clip-path="url(#breeze-static-checks-line-77)">
+</text><text class="breeze-static-checks-r5" x="0" y="1923.2" 
textLength="24.4" clip-path="url(#breeze-static-checks-line-78)">╭─</text><text 
class="breeze-static-checks-r5" x="24.4" y="1923.2" textLength="195.2" 
clip-path="url(#breeze-static-checks-line-78)">&#160;Common&#160;options&#160;</text><text
 class="breeze-static-checks-r5" x="219.6" y="1923.2" textLength="1220" 
clip-path="url(#breeze-static-checks-line-78)">──────────────────────────────────────────────────────────────────────
 [...]
+</text><text class="breeze-static-checks-r5" x="0" y="1947.6" 
textLength="12.2" clip-path="url(#breeze-static-checks-line-79)">│</text><text 
class="breeze-static-checks-r4" x="24.4" y="1947.6" textLength="12.2" 
clip-path="url(#breeze-static-checks-line-79)">-</text><text 
class="breeze-static-checks-r4" x="36.6" y="1947.6" textLength="48.8" 
clip-path="url(#breeze-static-checks-line-79)">-dry</text><text 
class="breeze-static-checks-r4" x="85.4" y="1947.6" textLength="48.8" 
clip-path="url(# [...]
+</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="97.6" 
clip-path="url(#breeze-static-checks-line-80)">-verbose</text><text 
class="breeze-static-checks-r6" x="158.6" y="1972" textLength="24.4" 
clip-path="url(#bre [...]
+</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="61" 
clip-path="url(#breeze-static-checks-line-81)">-help</text><text 
class="breeze-static-checks-r6" x="158.6" y="1996.4" textLength="24.4" 
clip-path="url(# [...]
+</text><text class="breeze-static-checks-r5" x="0" y="2020.8" 
textLength="1464" 
clip-path="url(#breeze-static-checks-line-82)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-static-checks-r1" x="1464" y="2020.8" textLength="12.2" 
clip-path="url(#breeze-static-checks-line-82)">
 </text>
     </g>
     </g>
diff --git a/images/breeze/output_static-checks.txt 
b/images/breeze/output_static-checks.txt
index 0fa29b780f..7a064e6df4 100644
--- a/images/breeze/output_static-checks.txt
+++ b/images/breeze/output_static-checks.txt
@@ -1 +1 @@
-51f5d6cec17591b624d4995d40c18db4
+42639ed45de3e438c5e9f95bf3d8b300
diff --git a/scripts/ci/pre_commit/pre_commit_validate_operators_init.py 
b/scripts/ci/pre_commit/pre_commit_validate_operators_init.py
new file mode 100755
index 0000000000..aab3a4d47b
--- /dev/null
+++ b/scripts/ci/pre_commit/pre_commit_validate_operators_init.py
@@ -0,0 +1,236 @@
+#!/usr/bin/env python
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+from __future__ import annotations
+
+import ast
+import sys
+from typing import Any
+
+from rich.console import Console
+
+console = Console(color_system="standard", width=200)
+BASE_OPERATOR_CLASS_NAME = "BaseOperator"
+
+
+def _is_operator(class_node: ast.ClassDef) -> bool:
+    """
+    Check if a given class node is an operator, based of the string suffix of 
the base IDs
+    (ends with "BaseOperator").
+    TODO: Enhance this function to work with nested inheritance trees through 
dynamic imports.
+
+    :param class_node: The class node to check.
+    :return: True if the class definition is of an operator, False otherwise.
+    """
+    for base in class_node.bases:
+        if isinstance(base, ast.Name) and 
base.id.endswith(BASE_OPERATOR_CLASS_NAME):
+            return True
+    return False
+
+
+def _extract_template_fields(class_node: ast.ClassDef) -> list[str]:
+    """
+    This method takes a class node as input and extracts the template fields 
from it.
+    Template fields are identified by an assignment statement where the target 
is a variable
+    named "template_fields" and the value is a tuple of constants.
+
+    :param class_node: The class node representing the class for which 
template fields need to be extracted.
+    :return: A list of template fields extracted from the class node.
+    """
+    for class_item in class_node.body:
+        if isinstance(class_item, ast.Assign):
+            for target in class_item.targets:
+                if (
+                    isinstance(target, ast.Name)
+                    and target.id == "template_fields"
+                    and isinstance(class_item.value, ast.Tuple)
+                ):
+                    return [elt.value for elt in class_item.value.elts if 
isinstance(elt, ast.Constant)]
+        elif isinstance(class_item, ast.AnnAssign):
+            if (
+                isinstance(class_item.target, ast.Name)
+                and class_item.target.id == "template_fields"
+                and isinstance(class_item.value, ast.Tuple)
+            ):
+                return [elt.value for elt in class_item.value.elts if 
isinstance(elt, ast.Constant)]
+    return []
+
+
+def _handle_parent_constructor_kwargs(
+    template_fields: list[str],
+    ctor_stmt: ast.stmt,
+    missing_assignments: list[str],
+    invalid_assignments: list[str],
+) -> list[str]:
+    """
+    This method checks if template fields are correctly assigned in a call to 
class parent's
+    constructor call.
+    It handles both the detection of missing assignments and invalid 
assignments.
+    It assumes that if the call is valid - the parent class will correctly 
assign the template
+    field.
+    TODO: Enhance this function to work with nested inheritance trees through 
dynamic imports.
+
+    :param missing_assignments: List[str] - List of template fields that have 
not been assigned a value.
+    :param ctor_stmt: ast.Expr - AST node representing the constructor 
statement.
+    :param invalid_assignments: List[str] - List of template fields that have 
been assigned incorrectly.
+    :param template_fields: List[str] - List of template fields to be assigned.
+
+    :return: List[str] - List of template fields that are still missing 
assignments.
+    """
+    if isinstance(ctor_stmt, ast.Expr):
+        if (
+            isinstance(ctor_stmt.value, ast.Call)
+            and isinstance(ctor_stmt.value.func, ast.Attribute)
+            and isinstance(ctor_stmt.value.func.value, ast.Call)
+            and isinstance(ctor_stmt.value.func.value.func, ast.Name)
+            and ctor_stmt.value.func.value.func.id == "super"
+        ):
+            for arg in ctor_stmt.value.keywords:
+                if arg.arg is not None and arg.arg in template_fields:
+                    if not isinstance(arg.value, ast.Name) or arg.arg != 
arg.value.id:
+                        invalid_assignments.append(arg.arg)
+            assigned_targets = [arg.arg for arg in ctor_stmt.value.keywords if 
arg.arg is not None]
+            return list(set(missing_assignments) - set(assigned_targets))
+    return missing_assignments
+
+
+def _handle_constructor_statement(
+    template_fields: list[str],
+    ctor_stmt: ast.stmt,
+    missing_assignments: list[str],
+    invalid_assignments: list[str],
+) -> list[str]:
+    """
+    This method handles a single constructor statement by doing the following 
actions:
+        1. Removing assigned fields of template_fields from 
missing_assignments.
+        2. Detecting invalid assignments of template fields and adding them to 
invalid_assignments.
+
+    :param template_fields: Tuple of template fields.
+    :param ctor_stmt: Constructor statement (for example, self.field_name = 
param_name)
+    :param missing_assignments: List of missing assignments.
+    :param invalid_assignments: List of invalid assignments.
+    :return: List of missing assignments after handling the assigned targets.
+    """
+    assigned_template_fields: list[str] = []
+    if isinstance(ctor_stmt, ast.Assign):
+        if isinstance(ctor_stmt.targets[0], ast.Attribute):
+            for target in ctor_stmt.targets:
+                if isinstance(target, ast.Attribute) and target.attr in 
template_fields:
+                    if isinstance(ctor_stmt.value, ast.BoolOp) and 
isinstance(ctor_stmt.value.op, ast.Or):
+                        _handle_assigned_field(
+                            assigned_template_fields, invalid_assignments, 
target, ctor_stmt.value.values[0]
+                        )
+                    else:
+                        _handle_assigned_field(
+                            assigned_template_fields, invalid_assignments, 
target, ctor_stmt.value
+                        )
+        elif isinstance(ctor_stmt.targets[0], ast.Tuple) and 
isinstance(ctor_stmt.value, ast.Tuple):
+            for target, value in zip(ctor_stmt.targets[0].elts, 
ctor_stmt.value.elts):
+                if isinstance(target, ast.Attribute):
+                    _handle_assigned_field(assigned_template_fields, 
invalid_assignments, target, value)
+    elif isinstance(ctor_stmt, ast.AnnAssign):
+        if isinstance(ctor_stmt.target, ast.Attribute) and 
ctor_stmt.target.attr in template_fields:
+            _handle_assigned_field(
+                assigned_template_fields, invalid_assignments, 
ctor_stmt.target, ctor_stmt.value
+            )
+    return list(set(missing_assignments) - set(assigned_template_fields))
+
+
+def _handle_assigned_field(
+    assigned_template_fields: list[str], invalid_assignments: list[str], 
target: ast.Attribute, value: Any
+) -> None:
+    """
+    Handle an assigned field by its value.
+
+    :param assigned_template_fields: A list to store the valid assigned fields.
+    :param invalid_assignments: A list to store the invalid assignments.
+    :param target: The target field.
+    :param value: The value of the field.
+    """
+    if not isinstance(value, ast.Name):
+        invalid_assignments.append(target.attr)
+    else:
+        assigned_template_fields.append(target.attr)
+
+
+def _check_constructor_template_fields(class_node: ast.ClassDef, 
template_fields: list[str]) -> int:
+    """
+    This method checks a class's constructor for missing or invalid 
assignments of template fields.
+    When there isn't a constructor - it assumes that the template fields are 
defined in the parent's
+    constructor correctly.
+    TODO: Enhance this function to work with nested inheritance trees through 
dynamic imports.
+
+    :param class_node: the AST node representing the class definition
+    :param template_fields: a tuple of template fields
+    :return: the number of invalid template fields found
+    """
+    count = 0
+    class_name = class_node.name
+    missing_assignments = template_fields.copy()
+    invalid_assignments: list[str] = []
+    init_flag: bool = False
+    for class_item in class_node.body:
+        if isinstance(class_item, ast.FunctionDef) and class_item.name == 
"__init__":
+            init_flag = True
+            for ctor_stmt in class_item.body:
+                missing_assignments = _handle_parent_constructor_kwargs(
+                    template_fields, ctor_stmt, missing_assignments, 
invalid_assignments
+                )
+                missing_assignments = _handle_constructor_statement(
+                    template_fields, ctor_stmt, missing_assignments, 
invalid_assignments
+                )
+
+    if init_flag and missing_assignments:
+        count += len(missing_assignments)
+        console.print(
+            f"{class_name}'s constructor lacks direct assignments for "
+            f"instance members corresponding to the following template fields "
+            f"(i.e., self.field_name = field_name or 
super.__init__(field_name=field_name, ...) ):"
+        )
+        console.print(f"[red]{missing_assignments}[/red]")
+
+    if invalid_assignments:
+        count += len(invalid_assignments)
+        console.print(
+            f"{class_name}'s constructor contains invalid assignments to the 
following instance "
+            f"members that should be corresponding to template fields "
+            f"(i.e., self.field_name = field_name):"
+        )
+        console.print(f"[red]{[f'self.{entry}' for entry in 
invalid_assignments]}[/red]")
+    return count
+
+
+def main():
+    """
+    Check missing or invalid template fields in constructors of providers' 
operators.
+
+    :return: The total number of errors found.
+    """
+    err = 0
+    for path in sys.argv[1:]:
+        console.print(f"[yellow]{path}[/yellow]")
+        tree = ast.parse(open(path).read())
+        for node in ast.walk(tree):
+            if isinstance(node, ast.ClassDef) and 
_is_operator(class_node=node):
+                template_fields = _extract_template_fields(node) or []
+                err += _check_constructor_template_fields(node, 
template_fields)
+    return err
+
+
+if __name__ == "__main__":
+    sys.exit(main())

Reply via email to