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

potiuk pushed a commit to branch v3-4-test
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/v3-4-test by this push:
     new 471367f59ac [v3-4-test] Stop generating constraints for PRs that do 
not use them (#76685) (#76849)
471367f59ac is described below

commit 471367f59ac8757567c2b7032711fc208e64b986
Author: Jarek Potiuk <[email protected]>
AuthorDate: Tue Nov 10 21:54:45 2026 +0100

    [v3-4-test] Stop generating constraints for PRs that do not use them 
(#76685) (#76849)
    
    Generate constraints ran for every PR that builds the CI image, about 149
    jobs a day, though for 46% of those runs nothing reads its output. The PROD
    image, its only reader on an ordinary PR, reads just the source-providers
    file. The PyPI step asks PyPI for 108 providers and resolves 725 packages
    against what it serves, which a change that touches no dependency cannot
    affect.
    
    That step turned PyPI outages into red PRs: of the 51 jobs that failed from
    2026-10-16 to 2026-11-05, 40 were network errors and 38 were on PRs that
    touched no dependency files. The only two failures no other job caught were
    in PRs that changed dependency files, which still run it. Canary runs still
    cover the state of PyPI.
    
    Claude-Session: https://claude.ai/code/session_01BVx6g6v9hW7wi1tV4bqvrp
    (cherry picked from commit e1150d774f8ac3462771138a0e03932d6af83735)
    Generated-by: Claude Opus 5
    
    Co-authored-by: Shahar Epstein <[email protected]>
---
 .github/workflows/ci-amd.yml                       |  6 +-
 .github/workflows/ci-arm.yml                       |  6 +-
 dev/breeze/doc/ci/04_selective_checks.md           |  8 ++
 dev/breeze/doc/ci/05_workflows.md                  |  9 ++-
 .../src/airflow_breeze/utils/selective_checks.py   | 18 +++++
 dev/breeze/tests/test_selective_checks.py          | 93 ++++++++++++++++++++++
 6 files changed, 135 insertions(+), 5 deletions(-)

diff --git a/.github/workflows/ci-amd.yml b/.github/workflows/ci-amd.yml
index 64879e8598a..10cd2a85960 100644
--- a/.github/workflows/ci-amd.yml
+++ b/.github/workflows/ci-amd.yml
@@ -297,6 +297,8 @@ jobs:
       excluded-providers-as-string: ${{ 
steps.selective-checks.outputs.excluded-providers-as-string }}
       force-pip: ${{ steps.selective-checks.outputs.force-pip }}
       full-tests-needed: ${{ steps.selective-checks.outputs.full-tests-needed 
}}
+      generate-constraints: ${{ 
steps.selective-checks.outputs.generate-constraints }}
+      generate-pypi-constraints: ${{ 
steps.selective-checks.outputs.generate-pypi-constraints }}
       has-migrations: ${{ steps.selective-checks.outputs.has-migrations }}
       helm-test-packages: ${{ 
steps.selective-checks.outputs.helm-test-packages }}
       helm-test-kubernetes-versions: ${{ 
steps.selective-checks.outputs.helm-test-kubernetes-versions }}
@@ -564,13 +566,13 @@ jobs:
     name: "Generate constraints"
     needs: [build-info, build-ci-images]
     uses: ./.github/workflows/generate-constraints.yml
-    if: needs.build-info.outputs.ci-image-build == 'true'
+    if: needs.build-info.outputs.generate-constraints == 'true'
     with:
       runners: ${{ needs.build-info.outputs.runner-type }}
       platform: ${{ needs.build-info.outputs.platform }}
       python-versions-list-as-string: ${{ 
needs.build-info.outputs.python-versions-list-as-string }}
       python-versions: ${{ needs.build-info.outputs.python-versions }}
-      generate-pypi-constraints: "true"
+      generate-pypi-constraints: ${{ 
needs.build-info.outputs.generate-pypi-constraints }}
       # generate no providers constraints only in canary builds - they take 
quite some time to generate
       # they are not needed for regular builds, they are only needed to update 
constraints in canaries
       generate-no-providers-constraints: ${{ 
needs.build-info.outputs.canary-run }}
diff --git a/.github/workflows/ci-arm.yml b/.github/workflows/ci-arm.yml
index e35600faa6a..0c877c17767 100644
--- a/.github/workflows/ci-arm.yml
+++ b/.github/workflows/ci-arm.yml
@@ -173,6 +173,8 @@ jobs:
       excluded-providers-as-string: ${{ 
steps.selective-checks.outputs.excluded-providers-as-string }}
       force-pip: ${{ steps.selective-checks.outputs.force-pip }}
       full-tests-needed: ${{ steps.selective-checks.outputs.full-tests-needed 
}}
+      generate-constraints: ${{ 
steps.selective-checks.outputs.generate-constraints }}
+      generate-pypi-constraints: ${{ 
steps.selective-checks.outputs.generate-pypi-constraints }}
       has-migrations: ${{ steps.selective-checks.outputs.has-migrations }}
       helm-test-packages: ${{ 
steps.selective-checks.outputs.helm-test-packages }}
       helm-test-kubernetes-versions: ${{ 
steps.selective-checks.outputs.helm-test-kubernetes-versions }}
@@ -440,13 +442,13 @@ jobs:
     name: "Generate constraints"
     needs: [build-info, build-ci-images]
     uses: ./.github/workflows/generate-constraints.yml
-    if: needs.build-info.outputs.ci-image-build == 'true'
+    if: needs.build-info.outputs.generate-constraints == 'true'
     with:
       runners: ${{ needs.build-info.outputs.runner-type }}
       platform: ${{ needs.build-info.outputs.platform }}
       python-versions-list-as-string: ${{ 
needs.build-info.outputs.python-versions-list-as-string }}
       python-versions: ${{ needs.build-info.outputs.python-versions }}
-      generate-pypi-constraints: "true"
+      generate-pypi-constraints: ${{ 
needs.build-info.outputs.generate-pypi-constraints }}
       # generate no providers constraints only in canary builds - they take 
quite some time to generate
       # they are not needed for regular builds, they are only needed to update 
constraints in canaries
       generate-no-providers-constraints: ${{ 
needs.build-info.outputs.canary-run }}
diff --git a/dev/breeze/doc/ci/04_selective_checks.md 
b/dev/breeze/doc/ci/04_selective_checks.md
index 4c14a33e999..28222b79c76 100644
--- a/dev/breeze/doc/ci/04_selective_checks.md
+++ b/dev/breeze/doc/ci/04_selective_checks.md
@@ -510,6 +510,12 @@ together using `pytest-xdist` (pytest-xdist distributes 
the tests among parallel
 * If no Source files are changed - no tests are run and no further rules below 
are checked.
 * `CI Image building` is enabled if either test are run, docs are build.
 * `PROD Image building` is enabled when kubernetes tests are run.
+* `Generate constraints` runs when the PROD image is built, because that build 
reads the
+  `constraints-source-providers` file, or when `generate-pypi-constraints` is 
on. The PyPI constraints and
+  the distributions they need are generated only when the CI image is built 
and `pyproject.toml` or `uv.lock`
+  changed, full tests are needed (which covers the job's own workflow and 
script), the run is a canary, or
+  the `upgrade to newer dependencies` label is set. Any other change leaves 
nothing for this resolution to
+  check except the state of PyPI, which the canary runs cover.
 * In case of `Providers` test in regular PRs, additional check is done in 
order to determine which
   providers are affected and the actual selection is made based on that:
   * if directly provider code is changed (either in the provider, test or 
system tests) then this provider
@@ -709,6 +715,8 @@ GitHub Actions to pass the list of parameters to a command 
to execute
 | excluded-providers-as-string                            | List of providers 
that should be excluded from the build as space-separated string                
      | amazon google                            | *    |
 | force-pip                                               | Whether pip should 
be forced in the image build instead of uv ("true"/"false")                     
     | false                                    |      |
 | full-tests-needed                                       | Whether this build 
runs complete set of tests or only subset (for faster PR builds) \[1\]          
     | false                                    |      |
+| generate-constraints                                    | Whether the 
Generate constraints job runs                                                   
            | false                                    |      |
+| generate-pypi-constraints                               | Whether that job 
also resolves the constraints against PyPI                                      
       | false                                    |      |
 | generated-dependencies-changed                          | Whether generated 
dependencies have changed ("true"/"false")                                      
      | false                                    |      |
 | has-migrations                                          | Whether the PR has 
migrations ("true"/"false")                                                     
     | false                                    |      |
 | hatch-build-changed                                     | When hatch 
build.py changed in the PR.                                                     
             | false                                    |      |
diff --git a/dev/breeze/doc/ci/05_workflows.md 
b/dev/breeze/doc/ci/05_workflows.md
index 8ea6810bb20..698513a035e 100644
--- a/dev/breeze/doc/ci/05_workflows.md
+++ b/dev/breeze/doc/ci/05_workflows.md
@@ -448,7 +448,7 @@ Here's what each workflow group does and when it runs:
 | **Basic Tests**                    | Runs quick validation: Breeze, UI, 
static checks            | Yes     | Yes     | Yes       |
 | **CI Image Build**                 | Builds Docker images for testing        
                    | Yes     | Yes     | Yes       |
 | **Additional CI Image Checks**     | Validates CI images meet quality 
standards                  | Yes     | Yes     | Yes       |
-| **Generate Constraints**           | Creates dependency version lock files   
                    | Yes     | Yes     | Yes       |
+| **Generate Constraints**           | Creates dependency version lock files   
                    | Yes (6) | Yes     | Yes (6)   |
 | **CI Image Checks**                | Runs static analysis and builds docs 
(AMD only)             | Yes     | Yes     | Yes       |
 | **Provider Tests**                 | Tests integration with external 
services                    | Yes     | Yes     | Yes (1)   |
 | **Unit Tests (Multiple Groups)**   | Runs core functionality tests with 
different databases      | Yes     | Yes     | Yes (1)   |
@@ -541,6 +541,13 @@ The summary runs only when the run upgrades to newer 
dependencies: on scheduled
 dispatched runs, when a `pyproject.toml` file changes, or on a PR with the
 `upgrade to newer dependencies` label. It needs the CI image, so it never runs 
without one.
 
+**`(6)` Generate Constraints**
+
+The job runs only when the PROD image is built or when the constraints are 
resolved against PyPI. It
+resolves against PyPI when dependencies change, when full tests are needed, on 
canary runs, and on a PR
+with the `upgrade to newer dependencies` label. A run that only builds the 
PROD image generates just the
+`constraints-source-providers` file that image reads.
+
 ## Runners
 
 Two kinds of GitHub-hosted runner are used, and which one a job gets depends 
on what
diff --git a/dev/breeze/src/airflow_breeze/utils/selective_checks.py 
b/dev/breeze/src/airflow_breeze/utils/selective_checks.py
index f8c3524e801..d04fabb16e9 100644
--- a/dev/breeze/src/airflow_breeze/utils/selective_checks.py
+++ b/dev/breeze/src/airflow_breeze/utils/selective_checks.py
@@ -2168,6 +2168,24 @@ class SelectiveChecks:
             return True
         return False
 
+    @cached_property
+    def generate_pypi_constraints(self) -> bool:
+        # Without a dependency or tooling change this resolution only reports 
on the state of PyPI,
+        # which the canary runs cover. Their dependency report and 
`update-constraints` read its output.
+        if not self.ci_image_build:
+            return False
+        return (
+            self.upgrade_to_newer_dependencies
+            or self.is_uv_lock_update
+            or self.full_tests_needed
+            or self._is_canary_run()
+        )
+
+    @cached_property
+    def generate_constraints(self) -> bool:
+        # The PROD image build reads the source-providers constraints the job 
always generates.
+        return self.generate_pypi_constraints or self.prod_image_build
+
     @cached_property
     def docs_list_as_string(self) -> str | None:
         _ALL_DOCS_LIST = ""
diff --git a/dev/breeze/tests/test_selective_checks.py 
b/dev/breeze/tests/test_selective_checks.py
index 8ec11a13d1c..025c3a3c8a8 100644
--- a/dev/breeze/tests/test_selective_checks.py
+++ b/dev/breeze/tests/test_selective_checks.py
@@ -3703,6 +3703,97 @@ def test_upgrade_to_newer_dependencies(
     assert_outputs_are_printed(expected_outputs, str(stderr))
 
 
[email protected](
+    ("files", "pr_labels", "github_event", "expected_constraints", 
"expected_pypi"),
+    [
+        pytest.param(
+            
("providers/edge3/src/airflow/providers/edge3/models/edge_job.py",),
+            (),
+            GithubEvents.PULL_REQUEST,
+            "false",
+            "false",
+            id="provider-source-needs-neither",
+        ),
+        pytest.param(
+            ("airflow-core/docs/index.rst",),
+            (),
+            GithubEvents.PULL_REQUEST,
+            "false",
+            "false",
+            id="docs-build-the-ci-image-only",
+        ),
+        pytest.param(
+            
("providers/edge3/src/airflow/providers/edge3/models/edge_job.py",),
+            ("area:e2e-tests",),
+            GithubEvents.PULL_REQUEST,
+            "true",
+            "false",
+            id="prod-image-needs-source-constraints-only",
+        ),
+        pytest.param(
+            ("providers/edge3/pyproject.toml",),
+            (),
+            GithubEvents.PULL_REQUEST,
+            "true",
+            "true",
+            id="provider-pyproject-resolves-against-pypi",
+        ),
+        pytest.param(
+            ("uv.lock", "airflow-core/src/airflow/models/dag.py"),
+            (),
+            GithubEvents.PULL_REQUEST,
+            "true",
+            "true",
+            id="uv-lock-resolves-against-pypi",
+        ),
+        pytest.param(
+            ("scripts/in_container/run_generate_constraints.py",),
+            (),
+            GithubEvents.PULL_REQUEST,
+            "true",
+            "true",
+            id="constraints-script-resolves-against-pypi",
+        ),
+        pytest.param(
+            
("providers/edge3/src/airflow/providers/edge3/models/edge_job.py",),
+            ("upgrade to newer dependencies",),
+            GithubEvents.PULL_REQUEST,
+            "true",
+            "true",
+            id="upgrade-label-resolves-against-pypi",
+        ),
+        pytest.param(
+            
("providers/edge3/src/airflow/providers/edge3/models/edge_job.py",),
+            ("canary",),
+            GithubEvents.PULL_REQUEST,
+            "true",
+            "true",
+            id="canary-label-resolves-against-pypi",
+        ),
+        pytest.param(
+            
("providers/edge3/src/airflow/providers/edge3/models/edge_job.py",),
+            (),
+            GithubEvents.SCHEDULE,
+            "true",
+            "true",
+            id="schedule-resolves-against-pypi",
+        ),
+    ],
+)
+def test_generate_constraints(files, pr_labels, github_event, 
expected_constraints, expected_pypi):
+    checks = SelectiveChecks(
+        files=files,
+        commit_ref="HEAD",
+        github_event=github_event,
+        default_branch="main",
+        pr_labels=pr_labels,
+    )
+    assert_outputs_are_printed(
+        {"generate-constraints": expected_constraints, 
"generate-pypi-constraints": expected_pypi},
+        str(checks),
+    )
+
+
 @pytest.mark.parametrize(
     ("files", "expected_outputs"),
     [
@@ -5949,6 +6040,8 @@ def 
test_component_branch_upgrades_dependencies_only_with_the_ci_image(context,
     checks = _component_branch_checks(context, ("airflow-ctl/pyproject.toml", 
"chart/pyproject.toml"))
 
     assert checks.upgrade_to_newer_dependencies is expected
+    assert checks.generate_constraints is expected
+    assert checks.generate_pypi_constraints is expected
 
 
 @pytest.mark.parametrize(

Reply via email to