This is an automated email from the ASF dual-hosted git repository.
potiuk 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 23bc655c13d Add pre-commit checks for shared distributions usage.
(#53769)
23bc655c13d is described below
commit 23bc655c13d848e6eaae24cf226be1f31f794184
Author: Jarek Potiuk <[email protected]>
AuthorDate: Sat Jul 26 10:22:14 2025 +0200
Add pre-commit checks for shared distributions usage. (#53769)
The pre-commit will make sure that usages of shared
distributions are correctly done in all the distributions using them when
[tool.airflow] shared_distributions added in pyproject.toml:
It checks:
* whether _shared folder exists
* whether the shared library is properly symlinked (adds symlinks if
missing)
* whether hatchling force-include is configured (adds
it if missing0
* synchronizes dependencies of shared distributions and updates them in
using projects
---
.pre-commit-config.yaml | 13 +-
contributing-docs/08_static_code_checks.rst | 4 +-
dev/breeze/doc/images/output_static-checks.svg | 130 ++++----
dev/breeze/doc/images/output_static-checks.txt | 2 +-
dev/breeze/src/airflow_breeze/pre_commit_ids.py | 3 +-
....py => check_shared_distributions_structure.py} | 4 +-
.../pre_commit/check_shared_distributions_usage.py | 368 +++++++++++++++++++++
shared/README.md | 85 ++++-
shared/timezones/pyproject.toml | 2 +-
task-sdk/pyproject.toml | 9 +-
task-sdk/src/airflow/sdk/_shared/__init__.py | 16 -
11 files changed, 531 insertions(+), 105 deletions(-)
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index a54c40be51d..142049efe7c 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -388,13 +388,20 @@ repos:
always_run: true
pass_filenames: false
additional_dependencies: ['rich>=12.4.4', 'pyyaml>=6.0.2',
'tomli>=2.0.1']
- - id: check-shared-projects
- name: Check shared projects structure
- entry: ./scripts/ci/pre_commit/check_shared_projects.py
+ - id: check-shared-distributions-structure
+ name: Check shared distributions structure
+ entry: ./scripts/ci/pre_commit/check_shared_distributions_structure.py
language: python
additional_dependencies: ['rich>=12.4.4', 'tomli>=2.0.1']
pass_filenames: false
include: ^shared/.*$
+ - id: check-shared-distributions-usage
+ name: Check shared distributions usage
+ entry: ./scripts/ci/pre_commit/check_shared_distributions_usage.py
+ language: python
+ additional_dependencies: ['rich>=12.4.4', 'tomli>=2.0.1']
+ pass_filenames: false
+ include: ^shared/.*$|^.*/pyproject.toml$|^.*/_shared/.*$
- id: validate-operators-init
name: No templated field logic checks in operator __init__
description: Prevent templated field logic checks in operators'
__init__
diff --git a/contributing-docs/08_static_code_checks.rst
b/contributing-docs/08_static_code_checks.rst
index 6f0861493fb..2e115abab16 100644
--- a/contributing-docs/08_static_code_checks.rst
+++ b/contributing-docs/08_static_code_checks.rst
@@ -229,7 +229,9 @@ require Breeze Docker image to be built locally.
+-----------------------------------------------------------+--------------------------------------------------------+---------+
| check-sdk-imports | Check for SDK
imports in core files | * |
+-----------------------------------------------------------+--------------------------------------------------------+---------+
-| check-shared-projects | Check shared
projects structure | |
+| check-shared-distributions-structure | Check shared
distributions structure | |
++-----------------------------------------------------------+--------------------------------------------------------+---------+
+| check-shared-distributions-usage | Check shared
distributions usage | |
+-----------------------------------------------------------+--------------------------------------------------------+---------+
| check-significant-newsfragments-are-valid | Check
significant newsfragments are valid | |
+-----------------------------------------------------------+--------------------------------------------------------+---------+
diff --git a/dev/breeze/doc/images/output_static-checks.svg
b/dev/breeze/doc/images/output_static-checks.svg
index a55ac921a94..95009de3741 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 2294.7999999999997"
xmlns="http://www.w3.org/2000/svg">
+<svg class="rich-terminal" viewBox="0 0 1482 2319.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="2243.7999999999997" />
+ <rect x="0" y="0" width="1463.0" height="2268.2" />
</clipPath>
<clipPath id="breeze-static-checks-line-0">
<rect x="0" y="1.5" width="1464" height="24.65"/>
@@ -318,9 +318,12 @@
<clipPath id="breeze-static-checks-line-90">
<rect x="0" y="2197.5" width="1464" height="24.65"/>
</clipPath>
+<clipPath id="breeze-static-checks-line-91">
+ <rect x="0" y="2221.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="2292.8" 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="2317.2" 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"/>
@@ -362,66 +365,67 @@
</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-provider-docs-valid | check-provider-yaml-valid |                          </text><
[...]
</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-providers-subpackages-init-file-exist | check-pydevd-left-in-code |        </text><text
class="breeze-static-checks-r5" x="1451.8" y="752" textLength="12.2"
clip-path="url(#bree [...]
</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-pytest-mark-db-test-in-providers | check-revision-heads-map |              </text><text
class="breeze-static-checks-r5" x="1451.8" y="776.4" te [...]
-</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-safe-filter-usage-in-html | check-sdk-imports | check-shared-projects |    </text><text
class="breeze-static-checks-r5" x="1451.8" y="800.8" textLength="12.2"
clip-path="url(#breeze-s [...]
-</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-significant-newsfragments-are-valid |                               
[...]
-</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-sql-dependency-common-data-structure |                               
[...]
-</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)">check-start-date-not-used-in-defaults | check-system-tests-present |             </text><text
class="breeze-static-checks-r5" x="1451.8" y="874" textLength="1 [...]
-</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)">check-system-tests-tocs | check-taskinstance-tis-attrs |                         </text><text
[...]
-</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)">check-template-context-variable-in-sync | check-template-fields-valid |          </text><text
class="breeze-static-checks-r5" x="1451.8" y="922.8" textLength="12.2" clip
[...]
-</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)">check-tests-in-the-right-folders | check-tests-unittest-testcase |               </text><text
class="breeze-static-checks-r5" x="1451.8" y="947. [...]
-</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)">check-urlparse-usage-in-code | check-xml | check-zip-file-is-not-committed |     </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)">codespell | compile-fab-assets | compile-ui-assets | compile-ui-assets-dev |     </text><text
class="breeze-static-checks-r5" x="1451.8" y="996" textLength="12.2"
clip-path="url( [...]
-</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)">create-missing-init-py-files-tests | debug-statements | detect-private-key |     </text><text
class="breeze-static-checks-r5" x="1451.8" y="1020.4" textLength="12.2"
clip-path="url(# [...]
-</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)">doctoc | end-of-file-fixer | fix-encoding-pragma | flynt | gci |                 </text><text
class="b [...]
-</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)">generate-airflow-diagrams | generate-airflowctl-datamodels |                     </text><text
class="breeze-stat [...]
-</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)">generate-airflowctl-help-images | generate-openapi-spec |                        </text><text
cla [...]
-</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)">generate-openapi-spec-fab | generate-pypi-readme | generate-tasksdk-datamodels | </text><text
class="breeze-static-checks-r5" x="1451.8" y="1118" textLength="12.2"
clip-path="url(#breeze-static-checks-line- [...]
-</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)">generate-volumes-for-sources | go-mockery | go-mod-tidy | gofmt | identity |     </text><text
class="breeze-static-checks-r5" x="1451.8" y="1142.4" textLength="12 [...]
-</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)">insert-license | kubeconform | lint-chart-schema | lint-dockerfile |             </text><text
class="breeze-static-checks-r5" x="145 [...]
-</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)">lint-helm-chart | lint-json-schema | lint-markdown | mixed-line-ending |         </text><text
class="breeze-static-checks-r5" x="1451.8" y="1191.2" text [...]
-</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)">mypy-airflow-core | mypy-airflow-ctl | mypy-dev | mypy-devel-common |            </text><text
class="breeze-static-checks-r5" x="1451.8" [...]
-</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)">mypy-providers | mypy-task-sdk | pretty-format-json |                          &#
[...]
-</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)">prevent-deprecated-sqlalchemy-usage | provider-version-compat | pylint |         </text><text
class="breeze-static-checks-r5" x="1451.8" y="1264.4" textLength="12 [...]
-</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)">python-no-log-warn | replace-bad-characters | rst-backticks | ruff | ruff-format </text><text
class="breeze-static-checks-r5" x="1451.8" y="1288.8" textLength="12.2"
clip-path="url(#breez [...]
-</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)">| shellcheck | trailing-whitespace | ts-compile-lint-simple-auth-manager-ui |    </text><text
class="breeze-static-checks-r5" x="1451.8" y="1313.2" textLength="12.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-r7" x="451.4" y="1337.6" textLength="988.2"
clip-path="url(#breeze-static-checks-line-54)">ts-compile-lint-ui | update-black-version | update-breeze-cmd-output |           </text><text
class="breeze-static-checks-r5" x="1451.8" y="1337.6" text [...]
-</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="988.2"
clip-path="url(#breeze-static-checks-line-55)">update-breeze-readme-config-hash | update-chart-dependencies | update-er-diagram </text><text
class="breeze-static-checks-r5" x="1451.8" y="1362" textLength="12.2"
clip-path="url(#breeze-static-checks-line-55)"> [...]
-</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="988.2"
clip-path="url(#breeze-static-checks-line-56)">| update-in-the-wild-to-be-sorted | update-inlined-dockerfile-scripts |          </text><text
class="breeze-static-checks-r5" x="1451.8" y="1386.4" textLength="12 [...]
-</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="988.2"
clip-path="url(#breeze-static-checks-line-57)">update-installed-providers-to-be-sorted | update-installers-and-pre-commit |     </text><text
class="breeze-static-checks-r5" x="1451.8" y="1410.8" textLength="12.2"
clip-path="url(#breeze-sta [...]
-</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-r7" x="451.4" y="1435.2" textLength="988.2"
clip-path="url(#breeze-static-checks-line-58)">update-local-yml-file | update-migration-references |                           &#
[...]
-</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-r7" x="451.4" y="1459.6" textLength="988.2"
clip-path="url(#breeze-static-checks-line-59)">update-providers-build-files | update-providers-dependencies |                   </text><text
class="breeze-static-checks- [...]
-</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-r7" x="451.4" y="1484" textLength="988.2"
clip-path="url(#breeze-static-checks-line-60)">update-pyproject-toml | update-reproducible-source-date-epoch |                  </text><text
class="breeze-static-checks-r5" x="14 [...]
-</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-r7" x="451.4" y="1508.4" textLength="988.2"
clip-path="url(#breeze-static-checks-line-61)">update-spelling-wordlist-to-be-sorted | update-supported-versions |              </text><text
class="breeze-static-checks-r5" x="1451.8" y="1508.4" [...]
-</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="451.4" y="1532.8" textLength="988.2"
clip-path="url(#breeze-static-checks-line-62)">update-vendored-in-k8s-json-schema | update-version | validate-chart-annotations </text><text
class="breeze-static-checks-r5" x="1451.8" y="1532.8" textLength="12.2"
clip-path="url(#breeze-static-checks-line [...]
-</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="451.4" y="1557.2" textLength="988.2"
clip-path="url(#breeze-static-checks-line-63)">| validate-operators-init | yamllint | zizmor)                           
[...]
-</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="268.4"
clip-path="url(#breeze-static-checks-line-64)">--show-diff-on-failure</text><text
class="breeze-static-checks-r6" x="402.6" y="1581.6" textLength="24.4"
clip-path="url(#breeze-static-checks-line-64)">-s</text><text
class="breeze-static-checks-r1" x="451.4" y="1581.6" textLength=" [...]
-</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="292.8"
clip-path="url(#breeze-static-checks-line-65)">--initialize-environment</text><text
class="breeze-static-checks-r1" x="451.4" y="1606" textLength="549"
clip-path="url(#breeze-static-checks-line-65)">Initialize environment before running checks.</text><text
class=" [...]
-</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="353.8"
clip-path="url(#breeze-static-checks-line-66)">--max-initialization-attempts</text><text
class="breeze-static-checks-r1" x="451.4" y="1630.4" textLength="854"
clip-path="url(#breeze-static-checks-line-66)">Maximum number of attempts to initialize env
[...]
-</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-r7" x="451.4" y="1654.8" textLength="854"
clip-path="url(#breeze-static-checks-line-67)">(INTEGER RANGE)                                   
[...]
-</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-r5" x="451.4" y="1679.2" textLength="854"
clip-path="url(#breeze-static-checks-line-68)">[default: 3; 1<=x<=10]                                
[...]
-</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)"> Selecting files to run the checks on </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="73.2"
clip-path="url(#breeze-static-checks-line-71)">--file</text><text
class="breeze-static-checks-r6" x="256.2" y="1752.4" textLength="24.4"
clip-path="url(#breeze-static-checks-line-71)">-f</text><text
class="breeze-static-checks-r1" x="305" y="1752.4" textLength="427"
clip-path="url [...]
-</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="134.2"
clip-path="url(#breeze-static-checks-line-72)">--all-files</text><text
class="breeze-static-checks-r6" x="256.2" y="1776.8" textLength="24.4"
clip-path="url(#breeze-static-checks-line-72)">-a</text><text
class="breeze-static-checks-r1" x="305" y="1776.8" textLength="292.8" clip-p
[...]
-</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="146.4"
clip-path="url(#breeze-static-checks-line-73)">--commit-ref</text><text
class="breeze-static-checks-r6" x="256.2" y="1801.2" textLength="24.4"
clip-path="url(#breeze-static-checks-line-73)">-r</text><text
class="breeze-static-checks-r1" x="305" y="1801.2" textLength="1134.6" clip
[...]
-</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-r1" x="305" y="1825.6" textLength="183"
clip-path="url(#breeze-static-checks-line-74)">exclusive with </text><text
class="breeze-static-checks-r4" x="488" y="1825.6" textLength="158.6"
clip-path="url(#breeze-static-checks-line-74)">--last-commit</text><text
class="breeze-static-checks-r1" x="646.6" y="1825.6" te [...]
-</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-r7" x="305" y="1850" textLength="1134.6"
clip-path="url(#breeze-static-checks-line-75)">(TEXT)                                      
[...]
-</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="158.6"
clip-path="url(#breeze-static-checks-line-76)">--last-commit</text><text
class="breeze-static-checks-r6" x="256.2" y="1874.4" textLength="24.4"
clip-path="url(#breeze-static-checks-line-76)">-c</text><text
class="breeze-static-checks-r1" x="305" y="1874.4" textLength="793" clip-p [...]
-</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="207.4"
clip-path="url(#breeze-static-checks-line-77)">--only-my-changes</text><text
class="breeze-static-checks-r6" x="256.2" y="1898.8" textLength="24.4"
clip-path="url(#breeze-static-checks-line-77)">-m</text><text
class="breeze-static-checks-r1" x="305" y="1898.8" textLength="1134.6" [...]
-</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-r1" x="305" y="1923.2" textLength="1134.6"
clip-path="url(#breeze-static-checks-line-78)">branch and HEAD of your branch.                             
[...]
-</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="463.6"
clip-path="url(#breeze-static-checks-line-80)"> Building image before running checks </text><text
class="breeze-static-checks-r5" x="488" y="1972" textLength="951.6"
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="317.2"
clip-path="url(#breeze-static-checks-line-81)">--skip-image-upgrade-check</text><text
class="breeze-static-checks-r1" x="414.8" y="1996.4" textLength="536.8"
clip-path="url(#breeze-static-checks-line-81)">Skip checking if the CI image is up 
[...]
-</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="158.6"
clip-path="url(#breeze-static-checks-line-82)">--force-build</text><text
class="breeze-static-checks-r1" x="414.8" y="2020.8" textLength="707.6"
clip-path="url(#breeze-static-checks-line-82)">Force image build no matter if it is determined&
[...]
-</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="231.8"
clip-path="url(#breeze-static-checks-line-83)">--github-repository</text><text
class="breeze-static-checks-r6" x="366" y="2045.2" textLength="24.4"
clip-path="url(#breeze-static-checks-line-83)">-g</text><text
class="breeze-static-checks-r1" x="414.8" y="2045.2" textLength="585.6 [...]
-</text><text class="breeze-static-checks-r5" x="0" y="2069.6"
textLength="12.2" clip-path="url(#breeze-static-checks-line-84)">│</text><text
class="breeze-static-checks-r4" x="24.4" y="2069.6" textLength="109.8"
clip-path="url(#breeze-static-checks-line-84)">--builder</text><text
class="breeze-static-checks-r1" x="414.8" y="2069.6" textLength="756.4"
clip-path="url(#breeze-static-checks-line-84)">Buildx builder used to perform `docker buildx build` 
[...]
-</text><text class="breeze-static-checks-r5" x="0" y="2094" textLength="12.2"
clip-path="url(#breeze-static-checks-line-85)">│</text><text
class="breeze-static-checks-r5" x="414.8" y="2094" textLength="756.4"
clip-path="url(#breeze-static-checks-line-85)">[default: autodetect]                                   
[...]
-</text><text class="breeze-static-checks-r5" x="0" y="2118.4"
textLength="1464"
clip-path="url(#breeze-static-checks-line-86)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
class="breeze-static-checks-r1" x="1464" y="2118.4" textLength="12.2"
clip-path="url(#breeze-static-checks-line-86)">
-</text><text class="breeze-static-checks-r5" x="0" y="2142.8"
textLength="24.4" clip-path="url(#breeze-static-checks-line-87)">╭─</text><text
class="breeze-static-checks-r5" x="24.4" y="2142.8" textLength="195.2"
clip-path="url(#breeze-static-checks-line-87)"> Common options </text><text
class="breeze-static-checks-r5" x="219.6" y="2142.8" textLength="1220"
clip-path="url(#breeze-static-checks-line-87)">──────────────────────────────────────────────────────────────────────
[...]
-</text><text class="breeze-static-checks-r5" x="0" y="2167.2"
textLength="12.2" clip-path="url(#breeze-static-checks-line-88)">│</text><text
class="breeze-static-checks-r4" x="24.4" y="2167.2" textLength="109.8"
clip-path="url(#breeze-static-checks-line-88)">--dry-run</text><text
class="breeze-static-checks-r6" x="158.6" y="2167.2" textLength="24.4"
clip-path="url(#breeze-static-checks-line-88)">-D</text><text
class="breeze-static-checks-r1" x="207.4" y="2167.2" textLength="719.8" clip-p
[...]
-</text><text class="breeze-static-checks-r5" x="0" y="2191.6"
textLength="12.2" clip-path="url(#breeze-static-checks-line-89)">│</text><text
class="breeze-static-checks-r4" x="24.4" y="2191.6" textLength="109.8"
clip-path="url(#breeze-static-checks-line-89)">--verbose</text><text
class="breeze-static-checks-r6" x="158.6" y="2191.6" textLength="24.4"
clip-path="url(#breeze-static-checks-line-89)">-v</text><text
class="breeze-static-checks-r1" x="207.4" y="2191.6" textLength="585.6" clip-p
[...]
-</text><text class="breeze-static-checks-r5" x="0" y="2216" textLength="12.2"
clip-path="url(#breeze-static-checks-line-90)">│</text><text
class="breeze-static-checks-r4" x="24.4" y="2216" textLength="73.2"
clip-path="url(#breeze-static-checks-line-90)">--help</text><text
class="breeze-static-checks-r6" x="158.6" y="2216" textLength="24.4"
clip-path="url(#breeze-static-checks-line-90)">-h</text><text
class="breeze-static-checks-r1" x="207.4" y="2216" textLength="329.4"
clip-path="url(#br [...]
-</text><text class="breeze-static-checks-r5" x="0" y="2240.4"
textLength="1464"
clip-path="url(#breeze-static-checks-line-91)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
class="breeze-static-checks-r1" x="1464" y="2240.4" textLength="12.2"
clip-path="url(#breeze-static-checks-line-91)">
+</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-safe-filter-usage-in-html | check-sdk-imports |                           
[...]
+</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-shared-distributions-structure | check-shared-distributions-usage |        </text><text
class="breeze-static-checks-r5" x="1451.8" y="825.2" textLength="12.2"
clip-path="url [...]
+</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-significant-newsfragments-are-valid |                               
[...]
+</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)">check-sql-dependency-common-data-structure |                               
[...]
+</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)">check-start-date-not-used-in-defaults | check-system-tests-present |             </text><text
class="breeze-static-checks-r5" x="1451.8" y="898.4" textLen [...]
+</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)">check-system-tests-tocs | check-taskinstance-tis-attrs |                         </text><text
[...]
+</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)">check-template-context-variable-in-sync | check-template-fields-valid |          </text><text
class="breeze-static-checks-r5" x="1451.8" y="947.2" textLength="12.2" clip
[...]
+</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)">check-tests-in-the-right-folders | check-tests-unittest-testcase |               </text><text
class="breeze-static-checks-r5" x="1451.8" y="971. [...]
+</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)">check-urlparse-usage-in-code | check-xml | check-zip-file-is-not-committed |     </text><text
class="breeze-static-checks-r5" x="1451.8" y="996" textLength="12.2"
clip-path="url(#breeze-st [...]
+</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)">codespell | compile-fab-assets | compile-ui-assets | compile-ui-assets-dev |     </text><text
class="breeze-static-checks-r5" x="1451.8" y="1020.4" textLength="12.2" clip-p
[...]
+</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)">create-missing-init-py-files-tests | debug-statements | detect-private-key |     </text><text
class="breeze-static-checks-r5" x="1451.8" y="1044.8" textLength="12.2"
clip-path="url(# [...]
+</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)">doctoc | end-of-file-fixer | fix-encoding-pragma | flynt | gci |                 </text><text
class="b [...]
+</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)">generate-airflow-diagrams | generate-airflowctl-datamodels |                     </text><text
class="breeze-stat [...]
+</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)">generate-airflowctl-help-images | generate-openapi-spec |                        </text><text
class=" [...]
+</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)">generate-openapi-spec-fab | generate-pypi-readme | generate-tasksdk-datamodels | </text><text
class="breeze-static-checks-r5" x="1451.8" y="1142.4" textLength="12.2"
clip-path="url(#breeze-static-checks [...]
+</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)">generate-volumes-for-sources | go-mockery | go-mod-tidy | gofmt | identity |     </text><text
class="breeze-static-checks-r5" x="1451.8" y="1166.8" textLength="12 [...]
+</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)">insert-license | kubeconform | lint-chart-schema | lint-dockerfile |             </text><text
class="breeze-static-checks-r5" x="145 [...]
+</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)">lint-helm-chart | lint-json-schema | lint-markdown | mixed-line-ending |         </text><text
class="breeze-static-checks-r5" x="1451.8" y="1215.6" text [...]
+</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)">mypy-airflow-core | mypy-airflow-ctl | mypy-dev | mypy-devel-common |            </text><text
class="breeze-static-checks-r5" x="1451.8" y="1 [...]
+</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)">mypy-providers | mypy-task-sdk | pretty-format-json |                         
[...]
+</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)">prevent-deprecated-sqlalchemy-usage | provider-version-compat | pylint |         </text><text
class="breeze-static-checks-r5" x="1451.8" y="1288.8" textLength="12 [...]
+</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)">python-no-log-warn | replace-bad-characters | rst-backticks | ruff | ruff-format </text><text
class="breeze-static-checks-r5" x="1451.8" y="1313.2" textLength="12.2"
clip-path="url(#breez [...]
+</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="988.2"
clip-path="url(#breeze-static-checks-line-54)">| shellcheck | trailing-whitespace | ts-compile-lint-simple-auth-manager-ui |    </text><text
class="breeze-static-checks-r5" x="1451.8" y="1337.6" textLength="12.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-r7" x="451.4" y="1362" textLength="988.2"
clip-path="url(#breeze-static-checks-line-55)">ts-compile-lint-ui | update-black-version | update-breeze-cmd-output |           </text><text
class="breeze-static-checks-r5" x="1451.8" y="1362" textLength [...]
+</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="988.2"
clip-path="url(#breeze-static-checks-line-56)">update-breeze-readme-config-hash | update-chart-dependencies | update-er-diagram </text><text
class="breeze-static-checks-r5" x="1451.8" y="1386.4" textLength="12.2"
clip-path="url(#breeze-static-checks-line [...]
+</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="988.2"
clip-path="url(#breeze-static-checks-line-57)">| update-in-the-wild-to-be-sorted | update-inlined-dockerfile-scripts |          </text><text
class="breeze-static-checks-r5" x="1451.8" y="1410.8" textLength="12 [...]
+</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-r7" x="451.4" y="1435.2" textLength="988.2"
clip-path="url(#breeze-static-checks-line-58)">update-installed-providers-to-be-sorted | update-installers-and-pre-commit |     </text><text
class="breeze-static-checks-r5" x="1451.8" y="1435.2" textLength="12.2"
clip-path="url(#breeze-sta [...]
+</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-r7" x="451.4" y="1459.6" textLength="988.2"
clip-path="url(#breeze-static-checks-line-59)">update-local-yml-file | update-migration-references |                           &#
[...]
+</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-r7" x="451.4" y="1484" textLength="988.2"
clip-path="url(#breeze-static-checks-line-60)">update-providers-build-files | update-providers-dependencies |                   </text><text
class="breeze-static-checks-r5" [...]
+</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-r7" x="451.4" y="1508.4" textLength="988.2"
clip-path="url(#breeze-static-checks-line-61)">update-pyproject-toml | update-reproducible-source-date-epoch |                  </text><text
class="breeze-static-checks-r5" x [...]
+</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="451.4" y="1532.8" textLength="988.2"
clip-path="url(#breeze-static-checks-line-62)">update-spelling-wordlist-to-be-sorted | update-supported-versions |              </text><text
class="breeze-static-checks-r5" x="1451.8" y="1532.8" [...]
+</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="451.4" y="1557.2" textLength="988.2"
clip-path="url(#breeze-static-checks-line-63)">update-vendored-in-k8s-json-schema | update-version | validate-chart-annotations </text><text
class="breeze-static-checks-r5" x="1451.8" y="1557.2" textLength="12.2"
clip-path="url(#breeze-static-checks-line [...]
+</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="451.4" y="1581.6" textLength="988.2"
clip-path="url(#breeze-static-checks-line-64)">| validate-operators-init | yamllint | zizmor)                           
[...]
+</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="268.4"
clip-path="url(#breeze-static-checks-line-65)">--show-diff-on-failure</text><text
class="breeze-static-checks-r6" x="402.6" y="1606" textLength="24.4"
clip-path="url(#breeze-static-checks-line-65)">-s</text><text
class="breeze-static-checks-r1" x="451.4" y="1606" textLength="524.6" c [...]
+</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="292.8"
clip-path="url(#breeze-static-checks-line-66)">--initialize-environment</text><text
class="breeze-static-checks-r1" x="451.4" y="1630.4" textLength="549"
clip-path="url(#breeze-static-checks-line-66)">Initialize environment before running checks.</text><text
c [...]
+</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="353.8"
clip-path="url(#breeze-static-checks-line-67)">--max-initialization-attempts</text><text
class="breeze-static-checks-r1" x="451.4" y="1654.8" textLength="854"
clip-path="url(#breeze-static-checks-line-67)">Maximum number of attempts to initialize env
[...]
+</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-r7" x="451.4" y="1679.2" textLength="854"
clip-path="url(#breeze-static-checks-line-68)">(INTEGER RANGE)                                   
[...]
+</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-r5" x="451.4" y="1703.6" textLength="854"
clip-path="url(#breeze-static-checks-line-69)">[default: 3; 1<=x<=10]                                
[...]
+</text><text class="breeze-static-checks-r5" x="0" y="1728" textLength="1464"
clip-path="url(#breeze-static-checks-line-70)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
class="breeze-static-checks-r1" x="1464" y="1728" textLength="12.2"
clip-path="url(#breeze-static-checks-line-70)">
+</text><text class="breeze-static-checks-r5" x="0" y="1752.4"
textLength="24.4" clip-path="url(#breeze-static-checks-line-71)">╭─</text><text
class="breeze-static-checks-r5" x="24.4" y="1752.4" textLength="463.6"
clip-path="url(#breeze-static-checks-line-71)"> Selecting files to run the checks on </text><text
class="breeze-static-checks-r5" x="488" y="1752.4" textLength="951.6"
clip-path="url(#breeze-static-checks-line-71)">──────────────────────── [...]
+</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="73.2"
clip-path="url(#breeze-static-checks-line-72)">--file</text><text
class="breeze-static-checks-r6" x="256.2" y="1776.8" textLength="24.4"
clip-path="url(#breeze-static-checks-line-72)">-f</text><text
class="breeze-static-checks-r1" x="305" y="1776.8" textLength="427"
clip-path="url [...]
+</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="134.2"
clip-path="url(#breeze-static-checks-line-73)">--all-files</text><text
class="breeze-static-checks-r6" x="256.2" y="1801.2" textLength="24.4"
clip-path="url(#breeze-static-checks-line-73)">-a</text><text
class="breeze-static-checks-r1" x="305" y="1801.2" textLength="292.8" clip-p
[...]
+</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="146.4"
clip-path="url(#breeze-static-checks-line-74)">--commit-ref</text><text
class="breeze-static-checks-r6" x="256.2" y="1825.6" textLength="24.4"
clip-path="url(#breeze-static-checks-line-74)">-r</text><text
class="breeze-static-checks-r1" x="305" y="1825.6" textLength="1134.6" clip
[...]
+</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-r1" x="305" y="1850" textLength="183"
clip-path="url(#breeze-static-checks-line-75)">exclusive with </text><text
class="breeze-static-checks-r4" x="488" y="1850" textLength="158.6"
clip-path="url(#breeze-static-checks-line-75)">--last-commit</text><text
class="breeze-static-checks-r1" x="646.6" y="1850" textLength [...]
+</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-r7" x="305" y="1874.4" textLength="1134.6"
clip-path="url(#breeze-static-checks-line-76)">(TEXT)                                      
[...]
+</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="158.6"
clip-path="url(#breeze-static-checks-line-77)">--last-commit</text><text
class="breeze-static-checks-r6" x="256.2" y="1898.8" textLength="24.4"
clip-path="url(#breeze-static-checks-line-77)">-c</text><text
class="breeze-static-checks-r1" x="305" y="1898.8" textLength="793" clip-p [...]
+</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="207.4"
clip-path="url(#breeze-static-checks-line-78)">--only-my-changes</text><text
class="breeze-static-checks-r6" x="256.2" y="1923.2" textLength="24.4"
clip-path="url(#breeze-static-checks-line-78)">-m</text><text
class="breeze-static-checks-r1" x="305" y="1923.2" textLength="1134.6" [...]
+</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-r1" x="305" y="1947.6" textLength="1134.6"
clip-path="url(#breeze-static-checks-line-79)">branch and HEAD of your branch.                             
[...]
+</text><text class="breeze-static-checks-r5" x="0" y="1972" textLength="1464"
clip-path="url(#breeze-static-checks-line-80)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
class="breeze-static-checks-r1" x="1464" y="1972" textLength="12.2"
clip-path="url(#breeze-static-checks-line-80)">
+</text><text class="breeze-static-checks-r5" x="0" y="1996.4"
textLength="24.4" clip-path="url(#breeze-static-checks-line-81)">╭─</text><text
class="breeze-static-checks-r5" x="24.4" y="1996.4" textLength="463.6"
clip-path="url(#breeze-static-checks-line-81)"> Building image before running checks </text><text
class="breeze-static-checks-r5" x="488" y="1996.4" textLength="951.6"
clip-path="url(#breeze-static-checks-line-81)">──────────────────────────────────
[...]
+</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="317.2"
clip-path="url(#breeze-static-checks-line-82)">--skip-image-upgrade-check</text><text
class="breeze-static-checks-r1" x="414.8" y="2020.8" textLength="536.8"
clip-path="url(#breeze-static-checks-line-82)">Skip checking if the CI image is up 
[...]
+</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="158.6"
clip-path="url(#breeze-static-checks-line-83)">--force-build</text><text
class="breeze-static-checks-r1" x="414.8" y="2045.2" textLength="707.6"
clip-path="url(#breeze-static-checks-line-83)">Force image build no matter if it is determined&
[...]
+</text><text class="breeze-static-checks-r5" x="0" y="2069.6"
textLength="12.2" clip-path="url(#breeze-static-checks-line-84)">│</text><text
class="breeze-static-checks-r4" x="24.4" y="2069.6" textLength="231.8"
clip-path="url(#breeze-static-checks-line-84)">--github-repository</text><text
class="breeze-static-checks-r6" x="366" y="2069.6" textLength="24.4"
clip-path="url(#breeze-static-checks-line-84)">-g</text><text
class="breeze-static-checks-r1" x="414.8" y="2069.6" textLength="585.6 [...]
+</text><text class="breeze-static-checks-r5" x="0" y="2094" textLength="12.2"
clip-path="url(#breeze-static-checks-line-85)">│</text><text
class="breeze-static-checks-r4" x="24.4" y="2094" textLength="109.8"
clip-path="url(#breeze-static-checks-line-85)">--builder</text><text
class="breeze-static-checks-r1" x="414.8" y="2094" textLength="756.4"
clip-path="url(#breeze-static-checks-line-85)">Buildx builder used to perform `docker buildx build` comma
[...]
+</text><text class="breeze-static-checks-r5" x="0" y="2118.4"
textLength="12.2" clip-path="url(#breeze-static-checks-line-86)">│</text><text
class="breeze-static-checks-r5" x="414.8" y="2118.4" textLength="756.4"
clip-path="url(#breeze-static-checks-line-86)">[default: autodetect]                                  &#
[...]
+</text><text class="breeze-static-checks-r5" x="0" y="2142.8"
textLength="1464"
clip-path="url(#breeze-static-checks-line-87)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
class="breeze-static-checks-r1" x="1464" y="2142.8" textLength="12.2"
clip-path="url(#breeze-static-checks-line-87)">
+</text><text class="breeze-static-checks-r5" x="0" y="2167.2"
textLength="24.4" clip-path="url(#breeze-static-checks-line-88)">╭─</text><text
class="breeze-static-checks-r5" x="24.4" y="2167.2" textLength="195.2"
clip-path="url(#breeze-static-checks-line-88)"> Common options </text><text
class="breeze-static-checks-r5" x="219.6" y="2167.2" textLength="1220"
clip-path="url(#breeze-static-checks-line-88)">──────────────────────────────────────────────────────────────────────
[...]
+</text><text class="breeze-static-checks-r5" x="0" y="2191.6"
textLength="12.2" clip-path="url(#breeze-static-checks-line-89)">│</text><text
class="breeze-static-checks-r4" x="24.4" y="2191.6" textLength="109.8"
clip-path="url(#breeze-static-checks-line-89)">--dry-run</text><text
class="breeze-static-checks-r6" x="158.6" y="2191.6" textLength="24.4"
clip-path="url(#breeze-static-checks-line-89)">-D</text><text
class="breeze-static-checks-r1" x="207.4" y="2191.6" textLength="719.8" clip-p
[...]
+</text><text class="breeze-static-checks-r5" x="0" y="2216" textLength="12.2"
clip-path="url(#breeze-static-checks-line-90)">│</text><text
class="breeze-static-checks-r4" x="24.4" y="2216" textLength="109.8"
clip-path="url(#breeze-static-checks-line-90)">--verbose</text><text
class="breeze-static-checks-r6" x="158.6" y="2216" textLength="24.4"
clip-path="url(#breeze-static-checks-line-90)">-v</text><text
class="breeze-static-checks-r1" x="207.4" y="2216" textLength="585.6"
clip-path="url [...]
+</text><text class="breeze-static-checks-r5" x="0" y="2240.4"
textLength="12.2" clip-path="url(#breeze-static-checks-line-91)">│</text><text
class="breeze-static-checks-r4" x="24.4" y="2240.4" textLength="73.2"
clip-path="url(#breeze-static-checks-line-91)">--help</text><text
class="breeze-static-checks-r6" x="158.6" y="2240.4" textLength="24.4"
clip-path="url(#breeze-static-checks-line-91)">-h</text><text
class="breeze-static-checks-r1" x="207.4" y="2240.4" textLength="329.4"
clip-path= [...]
+</text><text class="breeze-static-checks-r5" x="0" y="2264.8"
textLength="1464"
clip-path="url(#breeze-static-checks-line-92)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
class="breeze-static-checks-r1" x="1464" y="2264.8" textLength="12.2"
clip-path="url(#breeze-static-checks-line-92)">
</text>
</g>
</g>
diff --git a/dev/breeze/doc/images/output_static-checks.txt
b/dev/breeze/doc/images/output_static-checks.txt
index 67be6534ca2..eb0a67ef7cc 100644
--- a/dev/breeze/doc/images/output_static-checks.txt
+++ b/dev/breeze/doc/images/output_static-checks.txt
@@ -1 +1 @@
-1f034b17785eb25440aaf27d698faf93
+02f8c77e55afc39ebdae547e32621874
diff --git a/dev/breeze/src/airflow_breeze/pre_commit_ids.py
b/dev/breeze/src/airflow_breeze/pre_commit_ids.py
index 6ce4c035db0..e08366b9287 100644
--- a/dev/breeze/src/airflow_breeze/pre_commit_ids.py
+++ b/dev/breeze/src/airflow_breeze/pre_commit_ids.py
@@ -78,7 +78,8 @@ PRE_COMMIT_LIST = [
"check-revision-heads-map",
"check-safe-filter-usage-in-html",
"check-sdk-imports",
- "check-shared-projects",
+ "check-shared-distributions-structure",
+ "check-shared-distributions-usage",
"check-significant-newsfragments-are-valid",
"check-sql-dependency-common-data-structure",
"check-start-date-not-used-in-defaults",
diff --git a/scripts/ci/pre_commit/check_shared_projects.py
b/scripts/ci/pre_commit/check_shared_distributions_structure.py
similarity index 98%
rename from scripts/ci/pre_commit/check_shared_projects.py
rename to scripts/ci/pre_commit/check_shared_distributions_structure.py
index c0c04c39b83..444f70566c9 100755
--- a/scripts/ci/pre_commit/check_shared_projects.py
+++ b/scripts/ci/pre_commit/check_shared_distributions_structure.py
@@ -169,7 +169,7 @@ def check_ruff_lint_rules(ruff: dict, shared_path: Path) ->
bool:
return False
-def check_shared_project(shared_path: Path) -> bool:
+def check_shared_distribution(shared_path: Path) -> bool:
pyproject_path = shared_path / "pyproject.toml"
console.print(f"\n[bold blue]Checking:[/bold blue]
[magenta]{shared_path.name}[/magenta] shared project")
if not check_pyproject_exists(shared_path):
@@ -207,7 +207,7 @@ def main() -> None:
all_ok = True
for shared_project in SHARED_DIR.iterdir():
if shared_project.is_dir():
- ok = check_shared_project(shared_project)
+ ok = check_shared_distribution(shared_project)
if not ok:
all_ok = False
if not all_ok:
diff --git a/scripts/ci/pre_commit/check_shared_distributions_usage.py
b/scripts/ci/pre_commit/check_shared_distributions_usage.py
new file mode 100755
index 00000000000..f2763d0ade2
--- /dev/null
+++ b/scripts/ci/pre_commit/check_shared_distributions_usage.py
@@ -0,0 +1,368 @@
+#!/usr/bin/env python3
+# 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.
+
+"""
+Pre-commit check to verify and sync shared_distributions in pyproject.toml
files.
+Ensures only valid shared libraries are referenced and that they exist in the
shared/ folder.
+"""
+
+from __future__ import annotations
+
+import os
+import re
+import sys
+from pathlib import Path
+
+try:
+ import tomllib
+except ImportError:
+ import tomli as tomllib
+
+sys.path.insert(0, str(Path(__file__).parent.resolve())) # for
common_precommit_utils import
+from common_precommit_utils import AIRFLOW_ROOT_PATH, console,
insert_documentation
+
+SHARED_DIR = AIRFLOW_ROOT_PATH / "shared"
+
+
+def find_pyproject_files_with_tool_airflow(root: Path) -> list[Path]:
+ pyproject_files = []
+ for pyproject in root.glob("**/pyproject.toml"):
+ console.print(f"[bold blue]Checking:[/bold blue] {pyproject}", end="")
+ try:
+ with open(pyproject, "rb") as f:
+ data = tomllib.load(f)
+ if "tool" in data and "airflow" in data["tool"]:
+ console.print(" [green]tool.airflow section found[/green]")
+ pyproject_files.append(pyproject)
+ else:
+ console.print(" [yellow]tool.airflow section NOT
found[/yellow]")
+ except Exception as e:
+ console.print(f" [red]Error reading {pyproject}: {e}[/red]")
+ continue
+ return pyproject_files
+
+
+def get_shared_distributions(pyproject_path: Path) -> list[str]:
+ with open(pyproject_path, "rb") as f:
+ data = tomllib.load(f)
+ return data.get("tool", {}).get("airflow", {}).get("shared_distributions",
[])
+
+
+def verify_shared_distributions(shared_distributions: list[str], shared_dir:
Path) -> list[str]:
+ errors = []
+ for dist in shared_distributions:
+ console.print(f" Checking shared distribution:
[magenta]{dist}[/magenta]", end="")
+ if not re.match(r"^apache-airflow-shared-.+", dist):
+ errors.append(
+ f"Invalid shared distribution name: {dist}. Must start with
'apache-airflow-shared-'."
+ )
+ console.print(" [red]Invalid name[/red]")
+ continue
+ subfolder = dist.replace("apache-airflow-shared-", "")
+ if not (shared_dir / subfolder).is_dir():
+ errors.append(f"Shared distribution '{dist}' does not correspond
to a subfolder in 'shared/'.")
+ console.print(" [red]NOK[/red]")
+ else:
+ console.print(" [green]OK[/green]")
+ return errors
+
+
+def find_shared_folder_in_src(pyproject_path: Path) -> Path | None:
+ """
+ Checks if a _shared directory exists anywhere under the src folder of the
project.
+ Returns the Path if found, or None if missing.
+ """
+ project_dir = pyproject_path.parent
+ src_dir = project_dir / "src"
+ if not src_dir.is_dir():
+ console.print(
+ f" [red]The src directory not found in {project_dir}. Cannot
check for _shared folder.[/red]"
+ )
+ return None
+ console.print(f" Searching for _shared directory under src: {src_dir}. ",
end="")
+ for path in src_dir.rglob("_shared"):
+ if path.is_dir():
+ console.print("[green]OK[/green]")
+ console.print(f" The _shared directory found: {path}")
+ return path
+ console.print("[red]Not found.[/red]")
+ return None
+
+
+def check_force_include(pyproject: Path, shared_distributions: list[str],
shared_folder: Path) -> list[str]:
+ """
+ Checks that for each shared distribution, a proper force-include entry
exists in tool.hatch.build.targets.sdist.force-include.
+ If missing, adds it automatically to pyproject.toml.
+ Returns a list of error messages for missing or incorrect entries.
+ """
+ errors: list[str] = []
+ try:
+ with open(pyproject, "rb") as f:
+ data = tomllib.load(f)
+ except Exception as e:
+ return [f"Error reading pyproject.toml: {e}"]
+ force_include = (
+ data.get("tool", {})
+ .get("hatch", {})
+ .get("build", {})
+ .get("targets", {})
+ .get("sdist", {})
+ .get("force-include", {})
+ )
+ pyproject_text = pyproject.read_text()
+ updated = False
+ console.print(f" Checking force-include entries in {pyproject} ", end="")
+ for dist in shared_distributions:
+ dist_name = dist.replace("apache-airflow-shared-", "")
+ shared_src = f"../shared/{dist_name}/src/airflow_shared/{dist_name}"
+ found = False
+ for src, _ in force_include.items():
+ if src == shared_src:
+ found = True
+ break
+ if not found:
+ # Add missing entry to pyproject.toml
+ rel_dest =
f"{shared_folder.relative_to(pyproject.parent)}/{dist_name}"
+ entry = f'"{shared_src}" = "{rel_dest}"\n'
+ # Find or create the
[tool.hatch.build.targets.sdist.force-include] section
+ section_header = "[tool.hatch.build.targets.sdist.force-include]"
+ if section_header in pyproject_text:
+ # Insert entry after section header
+ lines = pyproject_text.splitlines(keepends=True)
+ for i, line in enumerate(lines):
+ if line.strip() == section_header:
+ # Insert after header and any existing entries
+ insert_at = i + 1
+ while (
+ insert_at < len(lines)
+ and lines[insert_at].startswith("[") is False
+ and lines[insert_at].strip()
+ ):
+ insert_at += 1
+ lines.insert(insert_at, entry)
+ break
+ pyproject_text = "".join(lines)
+ else:
+ # Add new section at the end
+ pyproject_text += f"\n{section_header}\n{entry}"
+ pyproject.write_text(pyproject_text)
+ updated = True
+ console.print(f"[yellow]Added missing force-include entry for
{dist} in {pyproject}[/yellow]")
+ else:
+ console.print("[green]OK[green]")
+ if updated:
+ # Reload data for next checks if needed
+ pass
+ return errors
+
+
+def get_workspace_pyproject_toml_files(main_pyproject_path: Path) ->
list[Path]:
+ """
+ Parse the main pyproject.toml using tomllib to get the list of workspace
project folders from [tool.uv.sources].
+ """
+ try:
+ with open(main_pyproject_path, "rb") as f:
+ data = tomllib.load(f)
+ members = data.get("tool", {}).get("uv", {}).get("workspace",
{}).get("members", [])
+ pyproject_toml_candidates = [AIRFLOW_ROOT_PATH / member /
"pyproject.toml" for member in members]
+ return [file for file in pyproject_toml_candidates if file.is_file()]
+ except Exception as e:
+ console.print(f"[red]Error reading workspace projects from
{main_pyproject_path}: {e}[/red]")
+ return []
+
+
+def ensure_symlinks(shared_folder: Path, shared_distributions: list[str]) ->
list[str]:
+ """
+ Ensure symlinks for shared distributions exist in the _shared folder.
Create if missing.
+ Returns a list of errors if symlink creation fails.
+ """
+ errors: list[str] = []
+ for distribution in shared_distributions:
+ subfolder = distribution.replace("apache-airflow-shared-", "")
+ symlink_path = shared_folder / subfolder
+ console.print(f" Checking for symlink:
[magenta]{subfolder}[/magenta]. ", end="")
+ target_path = SHARED_DIR / subfolder / "src" / "airflow_shared" /
subfolder
+ # Make symlink relative
+ rel_target_path = os.path.relpath(target_path, symlink_path.parent)
+ if not symlink_path.exists():
+ try:
+ os.symlink(rel_target_path, symlink_path)
+ console.print(f"[yellow]Created symlink: {symlink_path} ->
{rel_target_path}[/yellow]")
+ except Exception as e:
+ errors.append(f"Failed to create symlink for {distribution}:
{e}")
+ console.print(
+ f"[red]Failed to create symlink: {symlink_path} ->
{rel_target_path}: {e}[/red]"
+ )
+ elif not symlink_path.is_symlink() or symlink_path.resolve() !=
target_path.resolve():
+ try:
+ if symlink_path.exists():
+ if symlink_path.is_symlink():
+ symlink_path.unlink()
+ else:
+ errors.append(
+ f"{symlink_path} exists and is not a symlink.
Please remove it manually."
+ )
+ console.print(
+ f"[red]{symlink_path} exists and is not a symlink.
Please remove it manually.[/red]"
+ )
+ continue
+ os.symlink(rel_target_path, symlink_path)
+ console.print(f"[green]Fixed symlink: {symlink_path} ->
{rel_target_path}[green]")
+ except Exception as e:
+ errors.append(f"Failed to fix symlink for {distribution}: {e}")
+ console.print(f"[red]Failed to fix symlink: {symlink_path} ->
{rel_target_path}: {e}[/red]")
+ else:
+ console.print(f"[green]OK: {symlink_path} ->
{rel_target_path}[green]")
+ return errors
+
+
+def find_dependencies_array_range(lines: list[str]) -> tuple[int | None, int |
None]:
+ """
+ Finds the start and end line indices of the dependencies array in a
pyproject.toml file.
+ Returns (dep_start, dep_end) or (None, None) if not found.
+ """
+ dep_start, dep_end = None, None
+ for i, line in enumerate(lines):
+ if re.match(r"^dependencies\s*=\s*\[", line.strip()):
+ dep_start = i
+ for j in range(i, len(lines)):
+ if lines[j].strip() == "]":
+ dep_end = j
+ break
+ break
+ return dep_start, dep_end
+
+
+def add_shared_dependencies_block(
+ project_pyproject_path: Path, dep_end: int, header: str, footer: str,
content: list[str], dist_name: str
+) -> None:
+ """
+ Insert a shared dependencies block at the end of the dependencies array in
pyproject.toml.
+ """
+ lines = project_pyproject_path.read_text().splitlines(keepends=True)
+ new_lines = (
+ lines[:dep_end] + [" " + header + "\n"] + content + [" " +
footer + "\n"] + lines[dep_end:]
+ )
+ project_pyproject_path.write_text("".join(new_lines))
+ console.print(f"[yellow]Added shared dependencies for {dist_name} in
{project_pyproject_path}[/yellow]")
+
+
+def sync_shared_dependencies(project_pyproject_path: Path,
shared_distributions: list[str]) -> None:
+ """
+ Synchronize dependencies from shared distributions into the project's
pyproject.toml.
+ Updates or inserts blocks marked with start/end comments for each shared
distribution using insert_documentation.
+ Adds the block if missing.
+ """
+ for dist in shared_distributions:
+ dist_name = dist.replace("apache-airflow-shared-", "")
+ console.print(
+ f" Synchronizing shared dependencies for
[magenta]{dist_name}[/magenta] in {project_pyproject_path} ",
+ end="",
+ )
+ shared_pyproject = SHARED_DIR / dist_name / "pyproject.toml"
+ if not shared_pyproject.exists():
+ continue
+ with open(shared_pyproject, "rb") as f:
+ shared_data = tomllib.load(f)
+ shared_deps = shared_data.get("project", {}).get("dependencies", [])
+ if shared_deps:
+ header = f"# Start of shared {dist_name} dependencies"
+ footer = f"# End of shared {dist_name} dependencies"
+ content = [f' "{dep}",\n' for dep in shared_deps]
+ # Check if header exists in file
+ file_text = project_pyproject_path.read_text()
+ if header not in file_text:
+ # Insert at end of dependencies array
+ lines = file_text.splitlines(keepends=True)
+ dep_start, dep_end = find_dependencies_array_range(lines)
+ if dep_start is not None and dep_end is not None:
+ add_shared_dependencies_block(
+ project_pyproject_path, dep_end, header, footer,
content, dist_name
+ )
+ else:
+ console.print(
+ f"[red]Failed to determine dependencies array range in
{project_pyproject_path}[/red]"
+ )
+ else:
+ insert_documentation(project_pyproject_path, content, header,
footer, add_comment=False)
+ console.print("[green]OK[/green]")
+
+
+def main() -> None:
+ errors: dict[str, list[str]] = {}
+ pyproject_files = get_workspace_pyproject_toml_files(AIRFLOW_ROOT_PATH /
"pyproject.toml")
+ console.print(
+ "\n[bold blue]Checking for shared distributions for projects in
airflow workspace.[/bold blue]\n"
+ )
+ if not pyproject_files:
+ console.print("[red]No pyproject.toml files found in workspace
projects.[/red]")
+ sys.exit(1)
+ found_shared_distributions_usages = {}
+ for pyproject_file in pyproject_files:
+ console.print(f"[bold blue]Verifying shared_distributions in:[/bold
blue] {pyproject_file}", end="")
+ shared_distributions = get_shared_distributions(pyproject_file)
+ if shared_distributions:
+ console.print(f" {shared_distributions}")
+ else:
+ console.print(" No shared_distributions found")
+ continue
+ found_shared_distributions_usages[pyproject_file] =
shared_distributions
+ console.print()
+ console.print("Found shared_distributions usages in the following
pyproject.toml files:")
+ for pyproject_file, shared_distributions in
found_shared_distributions_usages.items():
+ console.print(f" [magenta]{pyproject_file}[/magenta]:
{shared_distributions}")
+ console.print()
+ for pyproject_file in found_shared_distributions_usages:
+ shared_distributions =
found_shared_distributions_usages[pyproject_file]
+ file_errors = verify_shared_distributions(shared_distributions,
SHARED_DIR)
+ sync_shared_dependencies(pyproject_file, shared_distributions)
+ shared_folder = find_shared_folder_in_src(pyproject_file)
+ if not shared_folder:
+ if str(pyproject_file) not in errors:
+ errors[str(pyproject_file)] = []
+ errors[str(pyproject_file)].append(f"Could not find shared folder
in {pyproject_file}")
+ if file_errors:
+ if str(pyproject_file) not in errors:
+ errors[str(pyproject_file)] = []
+ errors[str(pyproject_file)].extend(file_errors)
+ if shared_folder:
+ symlink_errors = ensure_symlinks(shared_folder,
shared_distributions)
+ if symlink_errors:
+ if str(pyproject_file) not in errors:
+ errors[str(pyproject_file)] = []
+ errors[str(pyproject_file)].extend(symlink_errors)
+ force_include_errors = check_force_include(pyproject_file,
shared_distributions, shared_folder)
+ if force_include_errors:
+ if str(pyproject_file) not in errors:
+ errors[str(pyproject_file)] = []
+ errors[str(pyproject_file)].extend(force_include_errors)
+ if errors:
+ console.print("\n[bold red]Shared distributions verification
failed:[/bold red]")
+ for file, errs in errors.items():
+ console.print(f"\n[red]{file}:[/red]")
+ for err in errs:
+ console.print(f" [red]- {err}[/red]")
+ sys.exit(1)
+ console.print(
+ "\n[bold green]All shared distributions are valid, _shared directories
exist under "
+ "src, and force-include entries are correct.[/bold green]"
+ )
+
+
+if __name__ == "__main__":
+ main()
diff --git a/shared/README.md b/shared/README.md
index baa57598546..f6152f911db 100644
--- a/shared/README.md
+++ b/shared/README.md
@@ -19,27 +19,47 @@
# Shared Python Code for Airflow Components
-This folder contains code that is shared across two or more of the Airflow
distributions, such as airflow-core and task-sdk.
+This folder contains code shared across two or more of the Airflow
distributions, such as
+airflow-core and task-sdk.
## Be Thoughtful about what you add under here
-Not every piece of code that is used in two distributions should be
automatically placed in one of the shared libraries, and sometimes "just
duplicate it" is the right approach to take. For example if it's just a 5 or 10
line function and it's used in two places, it might be easier to future
developers to understand if the function is in two places.
+Not every piece of code used in two distributions should be automatically
placed in one of the
+shared libraries, and sometimes "just duplicate it" is the right approach to
take. For example,
+if it's just a 5 or 10 line function and it's used in two places, it might be
easier for future
+developers to understand if the function is in two places.
-There is no hard rules about what should or shouldn't be in these libraries,
so try to apply your best judgement.
+There is no hard rule about what should or shouldn't be in these libraries, so
try to apply your
+best judgement.
## Mechanics of sharing
-The primary mechanism by which we share code is to use in-repo symlinks. This
means that there is a single copy of the code existing in the repo (no need for
a pre-commit to update other copies, resulting in larger diffs).
+The primary mechanism by which we share code is to use in-repo symlinks. This
means that there is
+a single copy of the code existing in the repo (no need for a pre-commit to
update other copies,
+resulting in larger diffs).
-The primary reason we are using this sharing approach, rather than the perhaps
more traditional approach of depending on a shared distribution from PyPI is
around compatibility. Lets say that we have a library to parse AirflowConfig
(including all the sources, env, `_cmd` handling, etc etc.). If that code is
used by two distributions that are installed in the same Python environment,
then either we introduce another source of "version hell" or we have to make
every change both backwards [...]
+The primary reason we are using this sharing approach, rather than the perhaps
more traditional
+approach of depending on a shared distribution from PyPI is around
compatibility. Let's say that
+we have a library to parse AirflowConfig (including all the sources, env,
`_cmd` handling, etc
+etc.). If that code is used by two distributions that are installed in the
same Python
+environment, then either we introduce another source of "version hell" or we
have to make every
+change both backwards and forwards compatible.
-Instead by using an approach similar to vendoring (or if you compare it to C
libraries - static linking), where each dist ships with a copy of the code of
the version it knows it works with it allows them to co-exist in the same env
without problems, and also reduces the cognitive load on developers when making
changes to the shared libs -- as long as it passes the CI it should be good
(and we don't have to test an ever increasing matrix of versions to ensure
compatibility.)
+Instead, by using an approach similar to vendoring (or if you compare it to C
libraries - static
+linking), where each dist ships with a copy of the code of the version it
knows it works with,
+it allows them to co-exist in the same env without problems, and also reduces
the cognitive load
+on developers when making changes to the shared libs -- as long as it passes
the CI it should be
+good (and we don't have to test an ever increasing matrix of versions to
ensure compatibility.)
-### Shared libraries _must_ use relative imports for other shared libraries**
+### Shared libraries _must_ use relative imports for other shared libraries
-The one caveat to this is due to the side-effect that these shared modules are
going to be imported from different locations (for example
`airflow._shared.timezones.timezone` and
`airflow.sdk._shared.timezones.timezone`) then any imports inside the shared
code to other parts of shared libraries _must_ make use of relative imports,
and possibly needs `# noqa: TID252`
+The one caveat to this is due to the side-effect that these shared modules are
going to be
+imported from different locations (for example
`airflow._shared.timezones.timezone` and
+`airflow.sdk._shared.timezones.timezone`) then any imports inside the shared
code to other parts
+of shared libraries _must_ make use of relative imports, and possibly needs `#
noqa: TID252`
-For example, to use the shared timezone library from another shared library,
lets say `shared/logging/src/airflow_shared/logging/config.py` you would have
+For example, to use the shared timezone library from another shared library,
let's say
+`shared/logging/src/airflow_shared/logging/config.py` you would have
```python
from ..timezones.timezone import is_naive # noqa: TID252
@@ -49,8 +69,9 @@ This is different to the rest of the airflow codebase where
relative imports are
### Shared libraries should be grouped one level beneath `airflow_shared/`
-In order to make both the relative imports above function and to provider
simple structure, we want to arrange things in single named chunks, where the
folder name is also the "shared library name". For example
-
+In order to make both the relative imports above function and to provide
simple structure, we
+want to arrange things in single named chunks, where the folder name
+is also the "shared library name". For example:
```
shared
@@ -73,19 +94,51 @@ shared
## Use In Editable installs/Git checkouts
-In the editable checkout (which is what you get by default when using `uv
sync` etc), the shared libraries are symlinked directly in place, for example
`task-sdk/src/airflow/sdk/_shared/timezones` is symlinked to
`shared/timezones/src/airflow_shared/timezones`. This means that python
automatically finds the files underneath the module as if they lived directly
under `airflow.sdk._shared.timezones.X`.
+In the editable checkout (which is what you get by default when using `uv
sync` etc), the shared
+libraries are symlinked directly in place, for example
`task-sdk/src/airflow/sdk/_shared/timezones` is
+symlinked to `shared/timezones/src/airflow_shared/timezones`. This means that
python automatically finds
+the files underneath the module as if they lived directly under
`airflow.sdk._shared.timezones.X`.
### Including In built distributions
-Symlinks will not work when we build the main distributions for PyPI and other
channels (since the symlinks would be to files outside of the distribution
otherwise). This is specific to sdist; building wheel resolves the symlinks.
+Symlinks will not work when we build the main distributions for PyPI and other
channels (since the symlinks
+would be to files outside the distribution otherwise). This is specific to
sdist; building wheel
+resolves the symlinks.
+
+We have a convention that when you include a shared code in your distribution,
you should such shared
+distributions (full name of the form `apache-airlow-shared-<name>` it to the
`tool.airflow` section of
+your pyproject.toml:
+
+```toml
+[tool.airflow]
+
+shared_distributions = [
+ "apache-airflow-shared-timezones",
+]
+```
+
+This allows `pre-commit` check to automatically verify if the shared
distributio is properly configured in
+your source tree and `pyproject.toml`.
+
+You should also have `_shared` folder created in your source tree to be able
to symlink the shared
+distributions code.
+
+Once you do it, pre-commit will automatically check and either correct the
distribution using the shared one,
+but you can also do it manually (for each shared distribution used):
+
+* creating a symlink of the sub-folder of `airflow_shared` in the `shared`
folder to the
+ corresponding shared distribution, e.g.
+ `ln -s ../../shared/timezones/src/airflow_shared/timezones
src/airflow/sdk/_shared/timezones`
-To make this work we need to configure hatchling to include extra files. For
example in the Task SDK we have
+* adding force-include in your `pyproject.toml` to have hatchling copy the
source files instead of
+ symling when `.sdist` distribution is built:
```toml
[tool.hatch.build.targets.sdist.force-include]
-"../shared/logging/src/airflow_shared/logging" =
"src/airflow/sdk/_shared/logging"
"../shared/timezones/src/airflow_shared/timezones" =
"src/airflow/sdk/_shared/timezones"
```
-We will need one entry in that table for each module we include directly below
`src/airflow/sdk/_shared`.
+* pre-commit will automatically synchronize all dependencies of the shared
distributions to
+ your pyproject.toml with appropriate comments allowing it to be
automatically synced
+ when shared distribution dependencies are updated in the future.
diff --git a/shared/timezones/pyproject.toml b/shared/timezones/pyproject.toml
index bc68f6467c8..51928c22492 100644
--- a/shared/timezones/pyproject.toml
+++ b/shared/timezones/pyproject.toml
@@ -24,7 +24,7 @@ classifiers = [
]
dependencies = [
- "pendulum",
+ "pendulum>=3.1.0",
]
[build-system]
diff --git a/task-sdk/pyproject.toml b/task-sdk/pyproject.toml
index de87a4a5f31..910a8419f37 100644
--- a/task-sdk/pyproject.toml
+++ b/task-sdk/pyproject.toml
@@ -56,7 +56,6 @@ dependencies = [
"jinja2>=3.1.5",
"methodtools>=0.4.7",
"msgspec>=0.19.0",
- 'pendulum>=3.1.0',
"python-dateutil>=2.7.0",
"psutil>=6.1.0",
"structlog>=25.4.0",
@@ -65,6 +64,9 @@ dependencies = [
"requests>=2.31.0,<3",
"types-requests>=2.31.0",
"tenacity>=8.3.0",
+ # Start of shared timezones dependencies
+ "pendulum>=3.1.0",
+ # End of shared timezones dependencies
]
[project.urls]
@@ -226,3 +228,8 @@ pythonpath = "tests"
# Keep temporary directories (created by `tmp_path`) for 2 recent runs only
failed tests.
tmp_path_retention_count = "2"
tmp_path_retention_policy = "failed"
+
+[tool.airflow]
+shared_distributions = [
+ "apache-airflow-shared-timezones",
+]
diff --git a/task-sdk/src/airflow/sdk/_shared/__init__.py
b/task-sdk/src/airflow/sdk/_shared/__init__.py
deleted file mode 100644
index 13a83393a91..00000000000
--- a/task-sdk/src/airflow/sdk/_shared/__init__.py
+++ /dev/null
@@ -1,16 +0,0 @@
-# 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.