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

ash 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 bb6be350c0f Auto-generate the Pydantic datamodels for TaskSDK in 
pre-commit (#47026)
bb6be350c0f is described below

commit bb6be350c0f40ea683a04eaccef3366fbf602dfd
Author: Ash Berlin-Taylor <[email protected]>
AuthorDate: Tue Feb 25 15:01:33 2025 +0000

    Auto-generate the Pydantic datamodels for TaskSDK in pre-commit (#47026)
    
    We have so far been sort-of running this manually when we remember, which is
    obviously error prone and forgotten once or twice.
    
    This automates it so it can't be forgotten.
    
    In order to get the license text in there, and to format it with ruff (not
    just Black) I had to add a very simple custom code formatter.
---
 .pre-commit-config.yaml                            |  9 ++
 airflow/api_fastapi/execution_api/app.py           |  8 ++
 .../execution_api/datamodels/taskinstance.py       |  2 +-
 contributing-docs/08_static_code_checks.rst        |  2 +
 dev/breeze/doc/images/output_static-checks.svg     | 34 ++++----
 dev/breeze/doc/images/output_static-checks.txt     |  2 +-
 dev/breeze/src/airflow_breeze/pre_commit_ids.py    |  1 +
 dev/datamodel_code_formatter.py                    | 66 +++++++++++++++
 task_sdk/dev/generate_models.py                    | 99 ++++++++++++++++++++++
 task_sdk/pyproject.toml                            | 12 ++-
 .../src/airflow/sdk/api/datamodels/_generated.py   |  8 +-
 .../airflow/sdk/definitions/_internal/templater.py | 11 ++-
 .../src/airflow/sdk/execution_time/task_runner.py  |  2 +-
 13 files changed, 227 insertions(+), 29 deletions(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index ab73a9dfe8f..f10e2c69a7c 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1448,6 +1448,15 @@ repos:
         entry: ./scripts/ci/pre_commit/update_fastapi_api_spec.py
         pass_filenames: false
         files: ^airflow/api_fastapi/.*\.py$
+        exclude: ^airflow/api_fastapi/execution_api/.*
+        additional_dependencies: ['rich>=12.4.4']
+      - id: generate-tasksdk-datamodels
+        name: Generate Datamodels for TaskSDK client
+        language: python
+        entry: uv run --active --group codegen --project 
apache-airflow-task-sdk --directory task_sdk -s dev/generate_models.py
+        pass_filenames: false
+        files: ^airflow/api_fastapi/execution_api/.*\.py$
+        require_serial: true
         additional_dependencies: ['rich>=12.4.4']
       - id: update-er-diagram
         name: Update ER diagram
diff --git a/airflow/api_fastapi/execution_api/app.py 
b/airflow/api_fastapi/execution_api/app.py
index cee567f2d49..a9bbf4dd788 100644
--- a/airflow/api_fastapi/execution_api/app.py
+++ b/airflow/api_fastapi/execution_api/app.py
@@ -72,6 +72,14 @@ def create_task_execution_api_app() -> FastAPI:
             if schema_name not in openapi_schema["components"]["schemas"]:
                 openapi_schema["components"]["schemas"][schema_name] = schema
 
+        # The `JsonValue` component is missing any info. causes issues when 
generating models
+        openapi_schema["components"]["schemas"]["JsonValue"] = {
+            "title": "Any valid JSON value",
+            "anyOf": [
+                {"type": t} for t in ("string", "number", "integer", "object", 
"array", "boolean", "null")
+            ],
+        }
+
         app.openapi_schema = openapi_schema
         return app.openapi_schema
 
diff --git a/airflow/api_fastapi/execution_api/datamodels/taskinstance.py 
b/airflow/api_fastapi/execution_api/datamodels/taskinstance.py
index acf56833666..a176a2f9d5b 100644
--- a/airflow/api_fastapi/execution_api/datamodels/taskinstance.py
+++ b/airflow/api_fastapi/execution_api/datamodels/taskinstance.py
@@ -227,7 +227,7 @@ class DagRun(StrictBaseModel):
     run_after: UtcDateTime
     start_date: UtcDateTime
     end_date: UtcDateTime | None
-    clear_number: int
+    clear_number: int = 0
     run_type: DagRunType
     conf: Annotated[dict[str, Any], Field(default_factory=dict)]
 
diff --git a/contributing-docs/08_static_code_checks.rst 
b/contributing-docs/08_static_code_checks.rst
index e45e7d718fe..74e1a692e48 100644
--- a/contributing-docs/08_static_code_checks.rst
+++ b/contributing-docs/08_static_code_checks.rst
@@ -288,6 +288,8 @@ require Breeze Docker image to be built locally.
 
+-----------------------------------------------------------+--------------------------------------------------------+---------+
 | generate-pypi-readme                                      | Generate PyPI 
README                                   |         |
 
+-----------------------------------------------------------+--------------------------------------------------------+---------+
+| generate-tasksdk-datamodels                               | Generate 
Datamodels for TaskSDK client                 | *       |
++-----------------------------------------------------------+--------------------------------------------------------+---------+
 | generate-volumes-for-sources                              | Generate volumes 
for docker compose                    |         |
 
+-----------------------------------------------------------+--------------------------------------------------------+---------+
 | identity                                                  | Print checked 
files                                    |         |
diff --git a/dev/breeze/doc/images/output_static-checks.svg 
b/dev/breeze/doc/images/output_static-checks.svg
index 20a07c78d0e..42e49195398 100644
--- a/dev/breeze/doc/images/output_static-checks.svg
+++ b/dev/breeze/doc/images/output_static-checks.svg
@@ -369,23 +369,23 @@
 </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)">compile-www-assets-dev&#160;|&#160;create-missing-init-py-files-tests&#160;|&#160;debug-statements&#160;|&#160;</text><text
 class="breeze-static-checks-r5" x="1451.8" y="1044.8" textLength="12.2" 
clip-path="url(#breeze-static-checks [...]
 </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)">detect-private-key&#160;|&#160;doctoc&#160;|&#160;end-of-file-fixer&#160;|&#160;fix-encoding-pragma&#160;|&#160;flynt&#160;|&#160;&#160;</text><text
 class="breeze-static-checks-r5" x="1451.8" y="1069.2" textLength="12.2" 
clip-path=" [...]
 </text><text class="breeze-static-checks-r5" x="0" y="1093.6" 
textLength="12.2" clip-path="url(#breeze-static-checks-line-44)">│</text><text 
class="breeze-static-checks-r7" x="451.4" y="1093.6" textLength="988.2" 
clip-path="url(#breeze-static-checks-line-44)">generate-airflow-diagrams&#160;|&#160;generate-openapi-spec&#160;|&#160;generate-pypi-readme&#160;|&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text
 class="breeze-static-checks-r5" x="1451.8" y="1093.6" textLength="12.2" clip-p 
[...]
-</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-volumes-for-sources&#160;|&#160;identity&#160;|&#160;insert-license&#160;|&#160;kubeconform&#160;|&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text
 class="breeze-static-checks-r5" x="1451.8" y="1118" textLength [...]
-</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)">lint-chart-schema&#160;|&#160;lint-css&#160;|&#160;lint-dockerfile&#160;|&#160;lint-helm-chart&#160;|&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text
 class="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)">lint-json-schema&#160;|&#160;lint-markdown&#160;|&#160;lint-openapi&#160;|&#160;mixed-line-ending&#160;|&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text
 class="breeze-static-checks-r5" x="1451.8"  [...]
-</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)">mypy-airflow&#160;|&#160;mypy-dev&#160;|&#160;mypy-docs&#160;|&#160;mypy-providers&#160;|&#160;mypy-task-sdk&#160;|&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</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)">pretty-format-json&#160;|&#160;pylint&#160;|&#160;python-no-log-warn&#160;|&#160;replace-bad-characters&#160;|&#160;&#160;&#160;&#160;&#160;&#160;</text><text
 class="breeze-static-checks-r5" x="1451.8" y="1215.6" textLength="12.2" c [...]
-</text><text class="breeze-static-checks-r5" x="0" y="1240" textLength="12.2" 
clip-path="url(#breeze-static-checks-line-50)">│</text><text 
class="breeze-static-checks-r7" x="451.4" y="1240" textLength="988.2" 
clip-path="url(#breeze-static-checks-line-50)">rst-backticks&#160;|&#160;ruff&#160;|&#160;ruff-format&#160;|&#160;shellcheck&#160;|&#160;trailing-whitespace&#160;|&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</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)">ts-compile-format-lint-ui&#160;|&#160;ts-compile-format-lint-www&#160;|&#160;update-black-version&#160;|&#160;&#160;</text><text
 class="breeze-static-checks-r5" x="1451.8" y="1264.4" textLength="12.2" 
clip-path="url(#breeze-static-c [...]
-</text><text class="breeze-static-checks-r5" x="0" y="1288.8" 
textLength="12.2" clip-path="url(#breeze-static-checks-line-52)">│</text><text 
class="breeze-static-checks-r7" x="451.4" y="1288.8" textLength="988.2" 
clip-path="url(#breeze-static-checks-line-52)">update-breeze-cmd-output&#160;|&#160;update-breeze-readme-config-hash&#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-ch [...]
-</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)">update-chart-dependencies&#160;|&#160;update-er-diagram&#160;|&#160;update-extras&#160;|&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text
 class="breeze-static-ch [...]
-</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)">update-in-the-wild-to-be-sorted&#160;|&#160;update-inlined-dockerfile-scripts&#160;|&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text
 class="breeze-static-checks-r5" x="1451.8" y="1337.6" textLengt [...]
-</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-installed-providers-to-be-sorted&#160;|&#160;update-installers-and-pre-commit&#160;|&#160;&#160;&#160;&#160;&#160;</text><text
 class="breeze-static-checks-r5" x="1451.8" y="1362" textLength="12.2" 
clip-path="url(#breeze-static-ch [...]
-</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-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="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-openapi-spec-tags-to-be-sorted&#160;|&#160;update-providers-build-files&#160;|&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</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-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="1435.2" textLength="12.2" c [...]
-</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-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="1459.6" [...]
-</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-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="1484" textLength="12.2" 
clip-path="url(#breeze-static-checks- [...]
-</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)">yamllint&#160;|&#160;zizmor)&#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="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-tasksdk-datamodels&#160;|&#160;generate-volumes-for-sources&#160;|&#160;identity&#160;|&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text
 class="breeze-static-checks-r5" x="1451.8" y="1118" textLength="12. [...]
+</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)">insert-license&#160;|&#160;kubeconform&#160;|&#160;lint-chart-schema&#160;|&#160;lint-css&#160;|&#160;lint-dockerfile&#160;|&#160;&#160;</text><text
 class="breeze-static-checks-r5" x="1451.8" y="1142.4" textLength="12.2" 
clip-path=" [...]
+</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)">lint-helm-chart&#160;|&#160;lint-json-schema&#160;|&#160;lint-markdown&#160;|&#160;lint-openapi&#160;|&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text
 class="breeze-static-checks-r5" x [...]
+</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)">mixed-line-ending&#160;|&#160;mypy-airflow&#160;|&#160;mypy-dev&#160;|&#160;mypy-docs&#160;|&#160;mypy-providers&#160;|&#160;&#160;&#160;&#160;&#160;&#160;&#160;</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-task-sdk&#160;|&#160;pretty-format-json&#160;|&#160;pylint&#160;|&#160;python-no-log-warn&#160;|&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text
 class="breeze-static-checks- [...]
+</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)">replace-bad-characters&#160;|&#160;rst-backticks&#160;|&#160;ruff&#160;|&#160;ruff-format&#160;|&#160;shellcheck&#160;|&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text
 class="breeze-static-checks-r5" x="1451.8" y="1240" textLength [...]
+</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)">trailing-whitespace&#160;|&#160;ts-compile-format-lint-ui&#160;|&#160;ts-compile-format-lint-www&#160;|&#160;&#160;&#160;</text><text
 class="breeze-static-checks-r5" x="1451.8" y="1264.4" textLength="12.2" 
clip-path="url(#breeze-sta [...]
+</text><text class="breeze-static-checks-r5" x="0" y="1288.8" 
textLength="12.2" clip-path="url(#breeze-static-checks-line-52)">│</text><text 
class="breeze-static-checks-r7" x="451.4" y="1288.8" textLength="988.2" 
clip-path="url(#breeze-static-checks-line-52)">update-black-version&#160;|&#160;update-breeze-cmd-output&#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="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)">update-breeze-readme-config-hash&#160;|&#160;update-chart-dependencies&#160;|&#160;update-er-diagram&#160;</text><text
 class="breeze-static-checks-r5" x="1451.8" y="1313.2" textLength="12.2" 
clip-path="url(#breeze-static-checks-line [...]
+</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)">|&#160;update-extras&#160;|&#160;update-in-the-wild-to-be-sorted&#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="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-inlined-dockerfile-scripts&#160;|&#160;update-installed-providers-to-be-sorted&#160;|&#160;&#160;&#160;&#160;</text><text
 class="breeze-static-checks-r5" x="1451.8" y="1362" textLength="12.2" 
clip-path="url(#breeze-static-checks- [...]
+</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-installers-and-pre-commit&#160;|&#160;update-local-yml-file&#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="b [...]
+</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-migration-references&#160;|&#160;update-openapi-spec-tags-to-be-sorted&#160;|&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text
 class="breeze-static-checks-r5" x="1451.8" y="1410.8" textLengt [...]
+</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-providers-build-files&#160;|&#160;update-providers-dependencies&#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- [...]
+</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-reproducible-source-date-epoch&#160;|&#160;update-spelling-wordlist-to-be-sorted&#160;|&#160;&#160;</text><text
 class="breeze-static-checks-r5" x="1451.8" y="1459.6" textLength="12.2" 
clip-path="url(#breeze-static-checks-line [...]
+</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-supported-versions&#160;|&#160;update-vendored-in-k8s-json-schema&#160;|&#160;update-version&#160;|</text><text
 class="breeze-static-checks-r5" x="1451.8" y="1484" textLength="12.2" 
clip-path="url(#breeze-static-checks-line-60)"> [...]
+</text><text class="breeze-static-checks-r5" x="0" y="1508.4" 
textLength="12.2" clip-path="url(#breeze-static-checks-line-61)">│</text><text 
class="breeze-static-checks-r7" x="451.4" y="1508.4" textLength="988.2" 
clip-path="url(#breeze-static-checks-line-61)">validate-operators-init&#160;|&#160;yamllint&#160;|&#160;zizmor)&#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="1532.8" 
textLength="12.2" clip-path="url(#breeze-static-checks-line-62)">│</text><text 
class="breeze-static-checks-r4" x="24.4" y="1532.8" textLength="268.4" 
clip-path="url(#breeze-static-checks-line-62)">--show-diff-on-failure</text><text
 class="breeze-static-checks-r6" x="402.6" y="1532.8" textLength="24.4" 
clip-path="url(#breeze-static-checks-line-62)">-s</text><text 
class="breeze-static-checks-r1" x="451.4" y="1532.8" textLength=" [...]
 </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="292.8" 
clip-path="url(#breeze-static-checks-line-63)">--initialize-environment</text><text
 class="breeze-static-checks-r1" x="451.4" y="1557.2" textLength="549" 
clip-path="url(#breeze-static-checks-line-63)">Initialize&#160;environment&#160;before&#160;running&#160;checks.</text><text
 c [...]
 </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="353.8" 
clip-path="url(#breeze-static-checks-line-64)">--max-initialization-attempts</text><text
 class="breeze-static-checks-r1" x="451.4" y="1581.6" textLength="854" 
clip-path="url(#breeze-static-checks-line-64)">Maximum&#160;number&#160;of&#160;attempts&#160;to&#160;initialize&#160;env
 [...]
diff --git a/dev/breeze/doc/images/output_static-checks.txt 
b/dev/breeze/doc/images/output_static-checks.txt
index 5ecb06b3bb6..2517d29745d 100644
--- a/dev/breeze/doc/images/output_static-checks.txt
+++ b/dev/breeze/doc/images/output_static-checks.txt
@@ -1 +1 @@
-26a564635ee0c2aaecaea40dd7e93779
+e7ca86fda9d023fbd26f9a8a1ce83467
diff --git a/dev/breeze/src/airflow_breeze/pre_commit_ids.py 
b/dev/breeze/src/airflow_breeze/pre_commit_ids.py
index b5d308f8291..ca65bb7193e 100644
--- a/dev/breeze/src/airflow_breeze/pre_commit_ids.py
+++ b/dev/breeze/src/airflow_breeze/pre_commit_ids.py
@@ -109,6 +109,7 @@ PRE_COMMIT_LIST = [
     "generate-airflow-diagrams",
     "generate-openapi-spec",
     "generate-pypi-readme",
+    "generate-tasksdk-datamodels",
     "generate-volumes-for-sources",
     "identity",
     "insert-license",
diff --git a/dev/datamodel_code_formatter.py b/dev/datamodel_code_formatter.py
new file mode 100644
index 00000000000..0f59fde22e0
--- /dev/null
+++ b/dev/datamodel_code_formatter.py
@@ -0,0 +1,66 @@
+# 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 subprocess
+from pathlib import Path
+
+import libcst as cst
+from datamodel_code_generator.format import CustomCodeFormatter
+
+
+def license_text() -> str:
+    license = (
+        Path(__file__).parents[1].joinpath("scripts", "ci", 
"license-templates", "LICENSE.txt").read_text()
+    )
+
+    return "\n".join(f"# {line}" if line else "#" for line in 
license.splitlines()) + "\n"
+
+
+class CodeFormatter(CustomCodeFormatter):
+    def apply(self, code: str) -> str:
+        code = license_text() + code
+
+        # Swap "class JsonValue[RootValue]:" for the import from pydantic
+        class JsonValueNodeRemover(cst.CSTTransformer):
+            def leave_ImportFrom(
+                self, original_node: cst.ImportFrom, updated_node: 
cst.ImportFrom
+            ) -> cst.BaseSmallStatement | 
cst.FlattenSentinel[cst.BaseSmallStatement] | cst.RemovalSentinel:
+                if original_node.module and original_node.module.value == 
"pydantic":
+                    new_names = updated_node.names + 
(cst.ImportAlias(name=cst.Name("JsonValue")),)  # type: ignore[operator]
+                    return updated_node.with_changes(names=new_names)
+                return super().leave_ImportFrom(original_node, updated_node)
+
+            def leave_ClassDef(
+                self, original_node: cst.ClassDef, updated_node: cst.ClassDef
+            ) -> cst.BaseStatement | cst.FlattenSentinel[cst.BaseStatement] | 
cst.RemovalSentinel:
+                if original_node.name.value == "JsonValue":
+                    return cst.RemoveFromParent()
+                return super().leave_ClassDef(original_node, updated_node)
+
+        source_tree = cst.parse_module(code)
+        modified_tree = source_tree.visit(JsonValueNodeRemover())
+        code = modified_tree.code
+
+        result = subprocess.check_output(
+            ["ruff", "check", "--fix-only", "--unsafe-fixes", "--quiet", 
"--preview", "-"],
+            input=code,
+            text=True,
+        )
+
+        return result
diff --git a/task_sdk/dev/generate_models.py b/task_sdk/dev/generate_models.py
new file mode 100644
index 00000000000..60a17333de9
--- /dev/null
+++ b/task_sdk/dev/generate_models.py
@@ -0,0 +1,99 @@
+# 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 json
+import os
+import sys
+from pathlib import Path
+from typing import TYPE_CHECKING
+
+from datamodel_code_generator import (
+    DataModelType,
+    DatetimeClassType,
+    InputFileType,
+    LiteralType,
+    PythonVersion,
+    generate as generate_models,
+)
+
+os.environ["_AIRFLOW__AS_LIBRARY"] = "1"
+sys.path.insert(
+    0,
+    str(Path(__file__).parents[2].joinpath("scripts", "ci", 
"pre_commit").resolve()),
+)  # make sure common utils are importable
+
+from common_precommit_utils import (
+    AIRFLOW_SOURCES_ROOT_PATH,
+)
+
+sys.path.insert(0, str(AIRFLOW_SOURCES_ROOT_PATH))  # make sure setup is 
imported from Airflow
+
+from airflow.api_fastapi.execution_api.app import create_task_execution_api_app
+
+task_sdk_root = Path(__file__).parents[1]
+
+if TYPE_CHECKING:
+    from fastapi import FastAPI
+
+
+def load_config():
+    try:
+        from tomllib import load as load_tomllib
+    except ImportError:
+        from tomli import load as load_tomllib
+
+    pyproject = task_sdk_root / "pyproject.toml"
+    # Simulate what `datamodel-code-generator` does on the CLI
+    with pyproject.open("rb") as fh:
+        cfg = load_tomllib(fh)["tool"]["datamodel-codegen"]
+
+    cfg = {k.replace("-", "_"): v for k, v in cfg.items()}
+
+    # Translate config file option names to generate() kwarg names
+    if (use_default := cfg.pop("use_default", None)) is not None:
+        cfg["apply_default_values_for_required_fields"] = use_default
+
+    if cfg.get("use_annotated"):
+        cfg["field_constraints"] = True
+
+    cfg["output"] = Path(cfg["output"])
+    cfg["output_model_type"] = DataModelType(cfg["output_model_type"])
+    cfg["output_datetime_class"] = 
DatetimeClassType(cfg["output_datetime_class"])
+    cfg["input_file_type"] = InputFileType(cfg["input_file_type"])
+    cfg["target_python_version"] = PythonVersion(cfg["target_python_version"])
+    cfg["enum_field_as_literal"] = LiteralType(cfg["enum_field_as_literal"])
+    return cfg
+
+
+def generate_file(app: FastAPI):
+    # The persisted openapi spec will list all endpoints (public and ui), this
+    # is used for code generation.
+    for route in app.routes:
+        if getattr(route, "name") == "webapp":
+            continue
+        route.__setattr__("include_in_schema", True)
+
+    os.chdir(task_sdk_root)
+
+    openapi_schema = json.dumps(app.openapi())
+    args = load_config()
+    args["input_filename"] = args.pop("url")
+    generate_models(openapi_schema, **args)
+
+
+generate_file(create_task_execution_api_app())
diff --git a/task_sdk/pyproject.toml b/task_sdk/pyproject.toml
index 6cd0215e82c..aa8c3e42124 100644
--- a/task_sdk/pyproject.toml
+++ b/task_sdk/pyproject.toml
@@ -109,11 +109,16 @@ exclude_also = [
 
 [dependency-groups]
 codegen = [
-    "datamodel-code-generator[http]>=0.26.5",
+    "datamodel-code-generator[http]>=0.28.1",
+    "apache-airflow",
+    "rich",
+    "ruff",
 ]
 
-[tool.black]
-# This is needed for datamodel-codegen to treat this as the "project" file
+[tool.uv.sources]
+# These names must match the names as defined in the pyproject.toml of the 
workspace items,
+# *not* the workspace folder paths
+apache-airflow = {workspace = true}
 
 # To use:
 #
@@ -134,6 +139,7 @@ use-schema-description=true  # Desc becomes class doc 
comment
 use-standard-collections=true # list[] not List[]
 use-subclass-enum=true # enum, not union of Literals
 use-union-operator=true # 3.9+annotations, not `Union[]`
+custom-formatters = ['dev.datamodel_code_formatter',]
 
 url = 'http://0.0.0.0:9091/execution/openapi.json'
 output = 'src/airflow/sdk/api/datamodels/_generated.py'
diff --git a/task_sdk/src/airflow/sdk/api/datamodels/_generated.py 
b/task_sdk/src/airflow/sdk/api/datamodels/_generated.py
index 7814095007c..71f110b64f4 100644
--- a/task_sdk/src/airflow/sdk/api/datamodels/_generated.py
+++ b/task_sdk/src/airflow/sdk/api/datamodels/_generated.py
@@ -1,6 +1,6 @@
 # generated by datamodel-codegen:
 #   filename:  http://0.0.0.0:9091/execution/openapi.json
-#   version:   0.26.5
+#   version:   0.28.1
 
 # Licensed to the Apache Software Foundation (ASF) under one
 # or more contributor license agreements.  See the NOTICE file
@@ -290,13 +290,13 @@ class DagRun(BaseModel):
     )
     dag_id: Annotated[str, Field(title="Dag Id")]
     run_id: Annotated[str, Field(title="Run Id")]
-    logical_date: Annotated[datetime | None, Field(title="Logical Date")]
+    logical_date: Annotated[datetime | None, Field(title="Logical Date")] = 
None
     data_interval_start: Annotated[datetime | None, Field(title="Data Interval 
Start")] = None
     data_interval_end: Annotated[datetime | None, Field(title="Data Interval 
End")] = None
     run_after: Annotated[datetime, Field(title="Run After")]
     start_date: Annotated[datetime, Field(title="Start Date")]
     end_date: Annotated[datetime | None, Field(title="End Date")] = None
-    clear_number: Annotated[int, Field(title="Clear Number")] = 0
+    clear_number: Annotated[int | None, Field(title="Clear Number")] = 0
     run_type: DagRunType
     conf: Annotated[dict[str, Any] | None, Field(title="Conf")] = None
 
@@ -311,7 +311,7 @@ class TIRunContext(BaseModel):
     """
 
     dag_run: DagRun
-    task_reschedule_count: Annotated[int, Field(title="Task Reschedule 
Count")] = 0
+    task_reschedule_count: Annotated[int | None, Field(title="Task Reschedule 
Count")] = 0
     max_tries: Annotated[int, Field(title="Max Tries")]
     variables: Annotated[list[VariableResponse] | None, 
Field(title="Variables")] = None
     connections: Annotated[list[ConnectionResponse] | None, 
Field(title="Connections")] = None
diff --git a/task_sdk/src/airflow/sdk/definitions/_internal/templater.py 
b/task_sdk/src/airflow/sdk/definitions/_internal/templater.py
index b50c4dbb3ca..a0914113496 100644
--- a/task_sdk/src/airflow/sdk/definitions/_internal/templater.py
+++ b/task_sdk/src/airflow/sdk/definitions/_internal/templater.py
@@ -27,11 +27,11 @@ import jinja2
 import jinja2.nativetypes
 import jinja2.sandbox
 
-from airflow.io.path import ObjectStoragePath
 from airflow.sdk.definitions._internal.mixins import ResolveMixin
 from airflow.utils.helpers import render_template_as_native, 
render_template_to_string
 
 if TYPE_CHECKING:
+    from airflow.io.path import ObjectStoragePath
     from airflow.models.operator import Operator
     from airflow.sdk.definitions.context import Context
     from airflow.sdk.definitions.dag import DAG
@@ -154,6 +154,13 @@ class Templater:
             *RecursionError* on circular dependencies)
         :return: Templated content
         """
+        try:
+            # Delay this to runtime, it invokes provider manager otherwise
+            from airflow.io.path import ObjectStoragePath
+        except ImportError:
+            # A placeholder class so isinstance checks work
+            class ObjectStoragePath: ...  # type: ignore[no-redef]
+
         # "content" is a bad name, but we're stuck to it being public API.
         value = content
         del content
@@ -203,7 +210,7 @@ class Templater:
         serialized_path = value.serialize()
         path_version = value.__version__
         serialized_path["path"] = 
self._render(jinja_env.from_string(serialized_path["path"]), context)
-        return ObjectStoragePath.deserialize(data=serialized_path, 
version=path_version)
+        return value.deserialize(data=serialized_path, version=path_version)
 
     def _render_nested_template_fields(
         self,
diff --git a/task_sdk/src/airflow/sdk/execution_time/task_runner.py 
b/task_sdk/src/airflow/sdk/execution_time/task_runner.py
index 6868019c38b..610c989e01f 100644
--- a/task_sdk/src/airflow/sdk/execution_time/task_runner.py
+++ b/task_sdk/src/airflow/sdk/execution_time/task_runner.py
@@ -159,7 +159,7 @@ class RuntimeTaskInstance(TaskInstance):
                 # TODO: Assess if we need to pass these through 
timezone.coerce_datetime
                 "dag_run": dag_run,  # type: ignore[typeddict-item]  # 
Removable after #46522
                 "task_instance_key_str": 
f"{self.task.dag_id}__{self.task.task_id}__{dag_run.run_id}",
-                "task_reschedule_count": 
self._ti_context_from_server.task_reschedule_count,
+                "task_reschedule_count": 
self._ti_context_from_server.task_reschedule_count or 0,
                 "prev_start_date_success": lazy_object_proxy.Proxy(
                     lambda: get_previous_dagrun_success(self.id).start_date
                 ),


Reply via email to