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 de6d83a00fd Update uv, pip and pre-commit versions automatically in 
more places. (#45398)
de6d83a00fd is described below

commit de6d83a00fd7af30586e58461428c8d3c74d55d3
Author: Jarek Potiuk <[email protected]>
AuthorDate: Sat Jan 4 22:36:45 2025 +0100

    Update uv, pip and pre-commit versions automatically in more places. 
(#45398)
---
 .github/actions/install-pre-commit/action.yml      |   7 +-
 .github/workflows/basic-tests.yml                  |   3 +-
 .pre-commit-config.yaml                            |   8 +-
 contributing-docs/08_static_code_checks.rst        |   2 +-
 dev/breeze/doc/ci/02_images.md                     |  56 +++---
 dev/breeze/doc/images/output_static-checks.svg     |   2 +-
 dev/breeze/doc/images/output_static-checks.txt     |   2 +-
 dev/breeze/src/airflow_breeze/pre_commit_ids.py    |   2 +-
 scripts/ci/pre_commit/update_installers.py         | 161 ---------------
 .../pre_commit/update_installers_and_pre_commit.py | 218 +++++++++++++++++++++
 10 files changed, 260 insertions(+), 201 deletions(-)

diff --git a/.github/actions/install-pre-commit/action.yml 
b/.github/actions/install-pre-commit/action.yml
index b4c6a6c9d54..abdd3ea98ff 100644
--- a/.github/actions/install-pre-commit/action.yml
+++ b/.github/actions/install-pre-commit/action.yml
@@ -19,19 +19,18 @@
 name: 'Install pre-commit'
 description: 'Installs pre-commit and related packages'
 inputs:
-  # TODO(potiuk): automate update of these versions
   python-version:
     description: 'Python version to use'
     default: "3.9"
   uv-version:
     description: 'uv version to use'
-    default: "0.5.14"
+    default: "0.5.14"  # Keep this comment to allow automatic replacement of 
uv version
   pre-commit-version:
     description: 'pre-commit version to use'
-    default: "4.0.1"
+    default: "4.0.1"  # Keep this comment to allow automatic replacement of 
pre-commit version
   pre-commit-uv-version:
     description: 'pre-commit-uv version to use'
-    default: "4.1.4"
+    default: "4.1.4"  # Keep this comment to allow automatic replacement of 
pre-commit-uv version
 runs:
   using: "composite"
   steps:
diff --git a/.github/workflows/basic-tests.yml 
b/.github/workflows/basic-tests.yml
index 297a912ea81..353f65d9a6c 100644
--- a/.github/workflows/basic-tests.yml
+++ b/.github/workflows/basic-tests.yml
@@ -307,11 +307,12 @@ jobs:
         run: >
           pre-commit run
           --all-files --show-diff-on-failure --color always --verbose
-          --hook-stage manual update-installers || true
+          --hook-stage manual update-installers-and-pre-commit || true
         if: always()
         env:
           UPGRADE_UV: "true"
           UPGRADE_PIP: "false"
+          UPGRADE_PRE_COMMIT: "true"
       - name: "Run automated upgrade for pip"
         run: >
           pre-commit run
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 09690edc5db..c5d0d154b88 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -197,12 +197,12 @@ repos:
         additional_dependencies: ['pyyaml']
         pass_filenames: false
         require_serial: true
-      - id: update-installers
-        name: Update installers to latest (manual)
-        entry: ./scripts/ci/pre_commit/update_installers.py
+      - id: update-installers-and-pre-commit
+        name: Update installers and pre-commit to latest (manual)
+        entry: ./scripts/ci/pre_commit/update_installers_and_pre_commit.py
         stages: ['manual']
         language: python
-        files: 
^.pre-commit-config.yaml$|^scripts/ci/pre_commit/update_installers.py$
+        files: 
^.pre-commit-config.yaml$|^scripts/ci/pre_commit/update_installers_and_pre_commit.py$
         pass_filenames: false
         require_serial: true
         additional_dependencies: ['pyyaml', 'rich>=12.4.4', 'requests']
diff --git a/contributing-docs/08_static_code_checks.rst 
b/contributing-docs/08_static_code_checks.rst
index abfb7378904..78462afe305 100644
--- a/contributing-docs/08_static_code_checks.rst
+++ b/contributing-docs/08_static_code_checks.rst
@@ -374,7 +374,7 @@ require Breeze Docker image to be built locally.
 
+-----------------------------------------------------------+--------------------------------------------------------+---------+
 | update-installed-providers-to-be-sorted                   | Sort and 
uniquify installed_providers.txt              |         |
 
+-----------------------------------------------------------+--------------------------------------------------------+---------+
-| update-installers                                         | Update 
installers to latest (manual)                   |         |
+| update-installers-and-pre-commit                          | Update 
installers and pre-commit to latest (manual)    |         |
 
+-----------------------------------------------------------+--------------------------------------------------------+---------+
 | update-local-yml-file                                     | Update mounts in 
the local yml file                    |         |
 
+-----------------------------------------------------------+--------------------------------------------------------+---------+
diff --git a/dev/breeze/doc/ci/02_images.md b/dev/breeze/doc/ci/02_images.md
index b613f67c72d..3d1d7d8b53e 100644
--- a/dev/breeze/doc/ci/02_images.md
+++ b/dev/breeze/doc/ci/02_images.md
@@ -419,33 +419,35 @@ DOCKER_BUILDKIT=1 docker build . -f Dockerfile.ci \
 The following build arguments (`--build-arg` in docker build command)
 can be used for CI images:
 
-| Build argument                    | Default value              | Description 
                                                                                
                                                               |
-|-----------------------------------|----------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| `PYTHON_BASE_IMAGE`               | `python:3.9-slim-bookworm` | Base Python 
image                                                                           
                                                               |
-| `PYTHON_MAJOR_MINOR_VERSION`      | `3.9`                      | major/minor 
version of Python (should match base image)                                     
                                                               |
-| `DEPENDENCIES_EPOCH_NUMBER`       | `2`                        | increasing 
this number will reinstall all apt dependencies                                 
                                                                |
-| `ADDITIONAL_PIP_INSTALL_FLAGS`    |                            | additional 
`pip` flags passed to the installation commands (except when reinstalling `pip` 
itself)                                                         |
-| `HOME`                            | `/root`                    | Home 
directory of the root user (CI image has root user as default)                  
                                                                      |
-| `AIRFLOW_HOME`                    | `/root/airflow`            | Airflow's 
HOME (that's where logs and sqlite databases are stored)                        
                                                                 |
-| `AIRFLOW_SOURCES`                 | `/opt/airflow`             | Mounted 
sources of Airflow                                                              
                                                                   |
-| `AIRFLOW_REPO`                    | `apache/airflow`           | the 
repository from which PIP dependencies are pre-installed                        
                                                                       |
-| `AIRFLOW_BRANCH`                  | `main`                     | the branch 
from which PIP dependencies are pre-installed                                   
                                                                |
-| `AIRFLOW_CI_BUILD_EPOCH`          | `1`                        | increasing 
this value will reinstall PIP dependencies from the repository from scratch     
                                                                |
-| `AIRFLOW_CONSTRAINTS_LOCATION`    |                            | If not 
empty, it will override the source of the constraints with the specified URL or 
file.                                                               |
-| `AIRFLOW_CONSTRAINTS_REFERENCE`   |                            | reference 
(branch or tag) from GitHub repository from which constraints are used. By 
default it is set to `constraints-main` but can be `constraints-2-X`. |
-| `AIRFLOW_EXTRAS`                  | `all`                      | extras to 
install                                                                         
                                                                 |
-| `UPGRADE_INVALIDATION_STRING`     |                            | If set to 
any random value the dependencies are upgraded to newer versions. In CI it is 
set to build id.                                                   |
-| `ADDITIONAL_AIRFLOW_EXTRAS`       |                            | additional 
extras to install                                                               
                                                                |
-| `ADDITIONAL_PYTHON_DEPS`          |                            | additional 
Python dependencies to install                                                  
                                                                |
-| `DEV_APT_COMMAND`                 |                            | Dev apt 
command executed before dev deps are installed in the first part of image       
                                                                   |
-| `ADDITIONAL_DEV_APT_COMMAND`      |                            | Additional 
Dev apt command executed before dev dep are installed in the first part of the 
image                                                            |
-| `DEV_APT_DEPS`                    |                            | Dev APT 
dependencies installed in the first part of the image (default empty means 
default dependencies are used)                                          |
-| `ADDITIONAL_DEV_APT_DEPS`         |                            | Additional 
apt dev dependencies installed in the first part of the image                   
                                                                |
-| `ADDITIONAL_DEV_APT_ENV`          |                            | Additional 
env variables defined when installing dev deps                                  
                                                                |
-| `AIRFLOW_PIP_VERSION`             | `24.3.1`                   | PIP version 
used.                                                                           
                                                               |
-| `AIRFLOW_UV_VERSION`              | `0.5.14`                   | UV version 
used.                                                                           
                                                                |
-| `AIRFLOW_USE_UV`                  | `true`                     | Whether to 
use UV for installation.                                                        
                                                                |
-| `PIP_PROGRESS_BAR`                | `on`                       | Progress 
bar for PIP installation                                                        
                                                                  |
+| Build argument                  | Default value              | Description   
                                                                                
                    |
+|---------------------------------|----------------------------|-------------------------------------------------------------------------------------------------------------------|
+| `PYTHON_BASE_IMAGE`             | `python:3.9-slim-bookworm` | Base Python 
image                                                                           
                      |
+| `PYTHON_MAJOR_MINOR_VERSION`    | `3.9`                      | major/minor 
version of Python (should match base image)                                     
                      |
+| `DEPENDENCIES_EPOCH_NUMBER`     | `2`                        | increasing 
this number will reinstall all apt dependencies                                 
                       |
+| `ADDITIONAL_PIP_INSTALL_FLAGS`  |                            | additional 
`pip` flags passed to the installation commands (except when reinstalling `pip` 
itself)                |
+| `HOME`                          | `/root`                    | Home 
directory of the root user (CI image has root user as default)                  
                             |
+| `AIRFLOW_HOME`                  | `/root/airflow`            | Airflow's 
HOME (that's where logs and sqlite databases are stored)                        
                        |
+| `AIRFLOW_SOURCES`               | `/opt/airflow`             | Mounted 
sources of Airflow                                                              
                          |
+| `AIRFLOW_REPO`                  | `apache/airflow`           | the 
repository from which PIP dependencies are pre-installed                        
                              |
+| `AIRFLOW_BRANCH`                | `main`                     | the branch 
from which PIP dependencies are pre-installed                                   
                       |
+| `AIRFLOW_CI_BUILD_EPOCH`        | `1`                        | increasing 
this value will reinstall PIP dependencies from the repository from scratch     
                       |
+| `AIRFLOW_CONSTRAINTS_LOCATION`  |                            | If not empty, 
it will override the source of the constraints with the specified URL or file.  
                    |
+| `AIRFLOW_CONSTRAINTS_REFERENCE` | `constraints-main`         | reference 
(branch or tag) from GitHub repository from which constraints are used.         
                        |
+| `AIRFLOW_EXTRAS`                | `all`                      | extras to 
install                                                                         
                        |
+| `UPGRADE_INVALIDATION_STRING`   |                            | If set to any 
random value the dependencies are upgraded to newer versions. In CI it is set 
to build id.          |
+| `ADDITIONAL_AIRFLOW_EXTRAS`     |                            | additional 
extras to install                                                               
                       |
+| `ADDITIONAL_PYTHON_DEPS`        |                            | additional 
Python dependencies to install                                                  
                       |
+| `DEV_APT_COMMAND`               |                            | Dev apt 
command executed before dev deps are installed in the first part of image       
                          |
+| `ADDITIONAL_DEV_APT_COMMAND`    |                            | Additional 
Dev apt command executed before dev dep are installed in the first part of the 
image                   |
+| `DEV_APT_DEPS`                  |                            | Dev APT 
dependencies installed in the first part of the image (default empty means 
default dependencies are used) |
+| `ADDITIONAL_DEV_APT_DEPS`       |                            | Additional 
apt dev dependencies installed in the first part of the image                   
                       |
+| `ADDITIONAL_DEV_APT_ENV`        |                            | Additional 
env variables defined when installing dev deps                                  
                       |
+| `AIRFLOW_PIP_VERSION`           | `24.3.1`                   | `pip` version 
used.                                                                           
                    |
+| `AIRFLOW_UV_VERSION`            | `0.5.14`                   | `uv` version 
used.                                                                           
                     |
+| `AIRFLOW_PRE_COMMIT_VERSION`    | `4.0.1`                    | `pre-commit` 
version used.                                                                   
                     |
+| `AIRFLOW_PRE_COMMIT_UV_VERSION` | `4.1.4`                    | 
`pre-commit-uv` version used.                                                   
                                  |
+| `AIRFLOW_USE_UV`                | `true`                     | Whether to 
use UV for installation.                                                        
                       |
+| `PIP_PROGRESS_BAR`              | `on`                       | Progress bar 
for PIP installation                                                            
                     |
 
 
 Here are some examples of how CI images can built manually. CI is always
diff --git a/dev/breeze/doc/images/output_static-checks.svg 
b/dev/breeze/doc/images/output_static-checks.svg
index a2422824dd9..bfea55c495f 100644
--- a/dev/breeze/doc/images/output_static-checks.svg
+++ b/dev/breeze/doc/images/output_static-checks.svg
@@ -371,7 +371,7 @@
 </text><text class="breeze-static-checks-r5" x="0" y="1240" textLength="12.2" 
clip-path="url(#breeze-static-checks-line-50)">│</text><text 
class="breeze-static-checks-r7" x="451.4" y="1240" textLength="988.2" 
clip-path="url(#breeze-static-checks-line-50)">update-breeze-readme-config-hash&#160;|&#160;update-chart-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-r5"  [...]
 </text><text class="breeze-static-checks-r5" x="0" y="1264.4" 
textLength="12.2" clip-path="url(#breeze-static-checks-line-51)">│</text><text 
class="breeze-static-checks-r7" x="451.4" y="1264.4" textLength="988.2" 
clip-path="url(#breeze-static-checks-line-51)">update-common-sql-api-stubs&#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;</text><text
 class="breeze-static-checks-r5" x [...]
 </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-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="1288.8" textLengt [...]
-</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-installed-providers-to-be-sorted&#160;|&#160;update-installers&#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-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="1313.2" textLength="12.2" 
clip-path="url(#breeze-sta [...]
 </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-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="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-openapi-spec-tags-to-be-sorted&#160;|&#160;update-providers-dependencies&#160;|&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text
 class="breeze-static-checks-r5" x="1451.8" y="1362" textLength="12.2" clip-pa 
[...]
 </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-providers-init-py&#160;|&#160;update-reproducible-source-date-epoch&#160;|&#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="13 [...]
diff --git a/dev/breeze/doc/images/output_static-checks.txt 
b/dev/breeze/doc/images/output_static-checks.txt
index a685635825f..38529eb9753 100644
--- a/dev/breeze/doc/images/output_static-checks.txt
+++ b/dev/breeze/doc/images/output_static-checks.txt
@@ -1 +1 @@
-1e545fdd89efbdd78a883519b6d93ce2
+6239e6a528459f731b6908ce668a8950
diff --git a/dev/breeze/src/airflow_breeze/pre_commit_ids.py 
b/dev/breeze/src/airflow_breeze/pre_commit_ids.py
index a4495df607e..8667a2cc4b7 100644
--- a/dev/breeze/src/airflow_breeze/pre_commit_ids.py
+++ b/dev/breeze/src/airflow_breeze/pre_commit_ids.py
@@ -141,7 +141,7 @@ PRE_COMMIT_LIST = [
     "update-in-the-wild-to-be-sorted",
     "update-inlined-dockerfile-scripts",
     "update-installed-providers-to-be-sorted",
-    "update-installers",
+    "update-installers-and-pre-commit",
     "update-local-yml-file",
     "update-migration-references",
     "update-openapi-spec-tags-to-be-sorted",
diff --git a/scripts/ci/pre_commit/update_installers.py 
b/scripts/ci/pre_commit/update_installers.py
deleted file mode 100755
index 28de1988240..00000000000
--- a/scripts/ci/pre_commit/update_installers.py
+++ /dev/null
@@ -1,161 +0,0 @@
-#!/usr/bin/env python
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-from __future__ import annotations
-
-import os
-import re
-import sys
-from pathlib import Path
-
-import requests
-
-sys.path.insert(0, str(Path(__file__).parent.resolve()))  # make sure 
common_precommit_utils is imported
-from common_precommit_utils import AIRFLOW_SOURCES_ROOT_PATH, console
-
-FILES_TO_UPDATE = [
-    AIRFLOW_SOURCES_ROOT_PATH / "Dockerfile",
-    AIRFLOW_SOURCES_ROOT_PATH / "Dockerfile.ci",
-    AIRFLOW_SOURCES_ROOT_PATH / "scripts" / "ci" / "install_breeze.sh",
-    AIRFLOW_SOURCES_ROOT_PATH / "scripts" / "docker" / "common.sh",
-    AIRFLOW_SOURCES_ROOT_PATH / "scripts" / "tools" / "setup_breeze",
-    AIRFLOW_SOURCES_ROOT_PATH / "pyproject.toml",
-    AIRFLOW_SOURCES_ROOT_PATH / "dev" / "breeze" / "src" / "airflow_breeze" / 
"global_constants.py",
-    AIRFLOW_SOURCES_ROOT_PATH
-    / "dev"
-    / "breeze"
-    / "src"
-    / "airflow_breeze"
-    / "commands"
-    / "release_management_commands.py",
-]
-
-
-DOC_FILES_TO_UPDATE: list[Path] = [
-    AIRFLOW_SOURCES_ROOT_PATH / "dev/" / "breeze" / "doc" / "ci" / 
"02_images.md"
-]
-
-
-def get_latest_pypi_version(package_name: str) -> str:
-    response = requests.get(f"https://pypi.org/pypi/{package_name}/json";)
-    response.raise_for_status()  # Ensure we got a successful response
-    data = response.json()
-    latest_version = data["info"]["version"]  # The version info is under the 
'info' key
-    return latest_version
-
-
-AIRFLOW_PIP_PATTERN = re.compile(r"(AIRFLOW_PIP_VERSION=)([0-9.]+)")
-AIRFLOW_PIP_QUOTED_PATTERN = re.compile(r"(AIRFLOW_PIP_VERSION = 
)(\"[0-9.]+\")")
-PIP_QUOTED_PATTERN = re.compile(r"(PIP_VERSION = )(\"[0-9.]+\")")
-PIP_QUOTED_PATTERN_NO_SPACES = re.compile(r"(PIP_VERSION=)(\"[0-9.]+\")")
-AIRFLOW_PIP_DOC_PATTERN = re.compile(r"(\| *`AIRFLOW_PIP_VERSION` *\| 
*)(`[0-9.]+`)( *\|)")
-AIRFLOW_PIP_UPGRADE_PATTERN = re.compile(r"(python -m pip install --upgrade 
pip==)([0-9.]+)")
-
-AIRFLOW_UV_PATTERN = re.compile(r"(AIRFLOW_UV_VERSION=)([0-9.]+)")
-AIRFLOW_UV_QUOTED_PATTERN = re.compile(r"(AIRFLOW_UV_VERSION = )(\"[0-9.]+\")")
-UV_QUOTED_PATTERN = re.compile(r"(UV_VERSION = )(\"[0-9.]+\")")
-UV_QUOTED_PATTERN_NO_SPACES = re.compile(r"(UV_VERSION=)(\"[0-9.]+\")")
-AIRFLOW_UV_DOC_PATTERN = re.compile(r"(\| *`AIRFLOW_UV_VERSION` *\| 
*)(`[0-9.]+`)( *\|)")
-UV_GREATER_PATTERN = re.compile(r'"(uv>=)([0-9]+)"')
-
-UPGRADE_UV: bool = os.environ.get("UPGRADE_UV", "true").lower() == "true"
-UPGRADE_PIP: bool = os.environ.get("UPGRADE_PIP", "true").lower() == "true"
-
-
-def replace_group_2_while_keeping_total_length(pattern: re.Pattern[str], 
replacement: str, text: str) -> str:
-    def replacer(match):
-        original_length = len(match.group(2))
-        padding = ""
-        if len(match.groups()) > 2:
-            padding = match.group(3)
-            new_length = len(replacement)
-            diff = new_length - original_length
-            if diff <= 0:
-                padding = " " * -diff + padding
-            else:
-                padding = padding[diff:]
-        padded_replacement = match.group(1) + replacement + padding
-        return padded_replacement.strip()
-
-    return re.sub(pattern, replacer, text)
-
-
-if __name__ == "__main__":
-    pip_version = get_latest_pypi_version("pip")
-    console.print(f"[bright_blue]Latest pip version: {pip_version}")
-    uv_version = get_latest_pypi_version("uv")
-    console.print(f"[bright_blue]Latest uv version: {uv_version}")
-
-    changed = False
-    for file in FILES_TO_UPDATE:
-        console.print(f"[bright_blue]Updating {file}")
-        file_content = file.read_text()
-        new_content = file_content
-        if UPGRADE_PIP:
-            new_content = replace_group_2_while_keeping_total_length(
-                AIRFLOW_PIP_PATTERN, pip_version, new_content
-            )
-            new_content = replace_group_2_while_keeping_total_length(
-                AIRFLOW_PIP_UPGRADE_PATTERN, pip_version, new_content
-            )
-            new_content = replace_group_2_while_keeping_total_length(
-                AIRFLOW_PIP_QUOTED_PATTERN, f'"{pip_version}"', new_content
-            )
-            new_content = replace_group_2_while_keeping_total_length(
-                PIP_QUOTED_PATTERN, f'"{pip_version}"', new_content
-            )
-            new_content = replace_group_2_while_keeping_total_length(
-                PIP_QUOTED_PATTERN_NO_SPACES, f'"{pip_version}"', new_content
-            )
-        if UPGRADE_UV:
-            new_content = replace_group_2_while_keeping_total_length(
-                AIRFLOW_UV_PATTERN, uv_version, new_content
-            )
-            new_content = replace_group_2_while_keeping_total_length(
-                UV_GREATER_PATTERN, uv_version, new_content
-            )
-            new_content = replace_group_2_while_keeping_total_length(
-                AIRFLOW_UV_QUOTED_PATTERN, f'"{uv_version}"', new_content
-            )
-            new_content = replace_group_2_while_keeping_total_length(
-                UV_QUOTED_PATTERN, f'"{uv_version}"', new_content
-            )
-            new_content = replace_group_2_while_keeping_total_length(
-                UV_QUOTED_PATTERN_NO_SPACES, f'"{uv_version}"', new_content
-            )
-        if new_content != file_content:
-            file.write_text(new_content)
-            console.print(f"[bright_blue]Updated {file}")
-            changed = True
-    for file in DOC_FILES_TO_UPDATE:
-        console.print(f"[bright_blue]Updating {file}")
-        file_content = file.read_text()
-        new_content = file_content
-        if UPGRADE_PIP:
-            new_content = replace_group_2_while_keeping_total_length(
-                AIRFLOW_PIP_DOC_PATTERN, f"`{pip_version}`", new_content
-            )
-        if UPGRADE_UV:
-            new_content = replace_group_2_while_keeping_total_length(
-                AIRFLOW_UV_DOC_PATTERN, f"`{uv_version}`", new_content
-            )
-        if new_content != file_content:
-            file.write_text(new_content)
-            console.print(f"[bright_blue]Updated {file}")
-            changed = True
-    if changed:
-        sys.exit(1)
diff --git a/scripts/ci/pre_commit/update_installers_and_pre_commit.py 
b/scripts/ci/pre_commit/update_installers_and_pre_commit.py
new file mode 100755
index 00000000000..9e5b2c68cf5
--- /dev/null
+++ b/scripts/ci/pre_commit/update_installers_and_pre_commit.py
@@ -0,0 +1,218 @@
+#!/usr/bin/env python
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+from __future__ import annotations
+
+import os
+import re
+import sys
+from enum import Enum
+from pathlib import Path
+
+import requests
+
+sys.path.insert(0, str(Path(__file__).parent.resolve()))  # make sure 
common_precommit_utils is imported
+from common_precommit_utils import AIRFLOW_SOURCES_ROOT_PATH, console
+
+# List of files to update and whether to keep total length of the original 
value when replacing.
+FILES_TO_UPDATE: list[tuple[Path, bool]] = [
+    (AIRFLOW_SOURCES_ROOT_PATH / "Dockerfile", False),
+    (AIRFLOW_SOURCES_ROOT_PATH / "Dockerfile.ci", False),
+    (AIRFLOW_SOURCES_ROOT_PATH / "scripts" / "ci" / "install_breeze.sh", 
False),
+    (AIRFLOW_SOURCES_ROOT_PATH / "scripts" / "docker" / "common.sh", False),
+    (AIRFLOW_SOURCES_ROOT_PATH / "scripts" / "tools" / "setup_breeze", False),
+    (AIRFLOW_SOURCES_ROOT_PATH / "pyproject.toml", False),
+    (AIRFLOW_SOURCES_ROOT_PATH / "dev" / "breeze" / "src" / "airflow_breeze" / 
"global_constants.py", False),
+    (
+        AIRFLOW_SOURCES_ROOT_PATH
+        / "dev"
+        / "breeze"
+        / "src"
+        / "airflow_breeze"
+        / "commands"
+        / "release_management_commands.py",
+        False,
+    ),
+    (AIRFLOW_SOURCES_ROOT_PATH / ".github" / "actions" / "install-pre-commit" 
/ "action.yml", False),
+    (AIRFLOW_SOURCES_ROOT_PATH / "dev/" / "breeze" / "doc" / "ci" / 
"02_images.md", True),
+]
+
+
+def get_latest_pypi_version(package_name: str) -> str:
+    response = requests.get(f"https://pypi.org/pypi/{package_name}/json";)
+    response.raise_for_status()  # Ensure we got a successful response
+    data = response.json()
+    latest_version = data["info"]["version"]  # The version info is under the 
'info' key
+    return latest_version
+
+
+class Quoting(Enum):
+    UNQUOTED = 0
+    SINGLE_QUOTED = 1
+    DOUBLE_QUOTED = 2
+    REVERSE_SINGLE_QUOTED = 3
+
+
+PIP_PATTERNS: list[tuple[re.Pattern, Quoting]] = [
+    (re.compile(r"(AIRFLOW_PIP_VERSION=)([0-9.]+)"), Quoting.UNQUOTED),
+    (re.compile(r"(python -m pip install --upgrade pip==)([0-9.]+)"), 
Quoting.UNQUOTED),
+    (re.compile(r"(AIRFLOW_PIP_VERSION = )(\"[0-9.]+\")"), 
Quoting.DOUBLE_QUOTED),
+    (re.compile(r"(PIP_VERSION = )(\"[0-9.]+\")"), Quoting.DOUBLE_QUOTED),
+    (re.compile(r"(PIP_VERSION=)(\"[0-9.]+\")"), Quoting.DOUBLE_QUOTED),
+    (re.compile(r"(\| *`AIRFLOW_PIP_VERSION` *\| *)(`[0-9.]+`)( *\|)"), 
Quoting.REVERSE_SINGLE_QUOTED),
+]
+
+UV_PATTERNS: list[tuple[re.Pattern, Quoting]] = [
+    (re.compile(r"(AIRFLOW_UV_VERSION=)([0-9.]+)"), Quoting.UNQUOTED),
+    (re.compile(r"(uv>=)([0-9]+)"), Quoting.UNQUOTED),
+    (re.compile(r"(AIRFLOW_UV_VERSION = )(\"[0-9.]+\")"), 
Quoting.DOUBLE_QUOTED),
+    (re.compile(r"(UV_VERSION = )(\"[0-9.]+\")"), Quoting.DOUBLE_QUOTED),
+    (re.compile(r"(UV_VERSION=)(\"[0-9.]+\")"), Quoting.DOUBLE_QUOTED),
+    (re.compile(r"(\| *`AIRFLOW_UV_VERSION` *\| *)(`[0-9.]+`)( *\|)"), 
Quoting.REVERSE_SINGLE_QUOTED),
+    (
+        re.compile(
+            r"(default: \")([0-9.]+)(\"  # Keep this comment to "
+            r"allow automatic replacement of uv version)"
+        ),
+        Quoting.UNQUOTED,
+    ),
+]
+
+PRE_COMMIT_PATTERNS: list[tuple[re.Pattern, Quoting]] = [
+    (re.compile(r"(AIRFLOW_PRE_COMMIT_VERSION=)([0-9.]+)"), Quoting.UNQUOTED),
+    (re.compile(r"(AIRFLOW_PRE_COMMIT_VERSION = )(\"[0-9.]+\")"), 
Quoting.DOUBLE_QUOTED),
+    (re.compile(r"(pre-commit>=)([0-9]+)"), Quoting.UNQUOTED),
+    (re.compile(r"(PRE_COMMIT_VERSION = )(\"[0-9.]+\")"), 
Quoting.DOUBLE_QUOTED),
+    (re.compile(r"(PRE_COMMIT_VERSION=)(\"[0-9.]+\")"), Quoting.DOUBLE_QUOTED),
+    (
+        re.compile(r"(\| *`AIRFLOW_PRE_COMMIT_VERSION` *\| *)(`[0-9.]+`)( 
*\|)"),
+        Quoting.REVERSE_SINGLE_QUOTED,
+    ),
+    (
+        re.compile(
+            r"(default: \")([0-9.]+)(\"  # Keep this comment to allow 
automatic "
+            r"replacement of pre-commit version)"
+        ),
+        Quoting.UNQUOTED,
+    ),
+]
+
+PRE_COMMIT_UV_PATTERNS: list[tuple[re.Pattern, Quoting]] = [
+    (re.compile(r"(AIRFLOW_PRE_COMMIT_UV_VERSION=)([0-9.]+)"), 
Quoting.UNQUOTED),
+    (re.compile(r"(AIRFLOW_PRE_COMMIT_UV_VERSION = )(\"[0-9.]+\")"), 
Quoting.DOUBLE_QUOTED),
+    (re.compile(r"(pre-commit-uv>=)([0-9]+)"), Quoting.UNQUOTED),
+    (re.compile(r"(PRE_COMMIT_UV_VERSION = )(\"[0-9.]+\")"), 
Quoting.DOUBLE_QUOTED),
+    (re.compile(r"(PRE_COMMIT_UV_VERSION=)(\"[0-9.]+\")"), 
Quoting.DOUBLE_QUOTED),
+    (
+        re.compile(r"(\| *`AIRFLOW_PRE_COMMIT_UV_VERSION` *\| *)(`[0-9.]+`)( 
*\|)"),
+        Quoting.REVERSE_SINGLE_QUOTED,
+    ),
+    (
+        re.compile(
+            r"(default: \")([0-9.]+)(\"  # Keep this comment to allow 
automatic "
+            r"replacement of pre-commit-uv version)"
+        ),
+        Quoting.UNQUOTED,
+    ),
+]
+
+
+def get_replacement(value: str, quoting: Quoting) -> str:
+    if quoting == Quoting.DOUBLE_QUOTED:
+        return f'"{value}"'
+    elif quoting == Quoting.SINGLE_QUOTED:
+        return f"'{value}'"
+    elif quoting == Quoting.REVERSE_SINGLE_QUOTED:
+        return f"`{value}`"
+    return value
+
+
+UPGRADE_UV: bool = os.environ.get("UPGRADE_UV", "true").lower() == "true"
+UPGRADE_PIP: bool = os.environ.get("UPGRADE_PIP", "true").lower() == "true"
+UPGRADE_PRE_COMMIT: bool = os.environ.get("UPGRADE_PRE_COMMIT", 
"true").lower() == "true"
+
+
+def replace_version(pattern: re.Pattern[str], version: str, text: str, 
keep_total_length: bool = True) -> str:
+    # Assume that the pattern has up to 3 replacement groups:
+    # 1. Prefix
+    # 2. Original version
+    # 3. Suffix
+    #
+    # (prefix)(version)(suffix)
+    # In case "keep_total_length" is set to True, the replacement will be 
padded with spaces to match
+    # the original length
+    def replacer(match):
+        prefix = match.group(1)
+        postfix = match.group(3) if len(match.groups()) > 2 else ""
+        if not keep_total_length:
+            return prefix + version + postfix
+        original_length = len(match.group(2))
+        new_length = len(version)
+        diff = new_length - original_length
+        if diff <= 0:
+            postfix = " " * -diff + postfix
+        else:
+            postfix = postfix[diff:]
+        padded_replacement = prefix + version + postfix
+        return padded_replacement.strip()
+
+    return re.sub(pattern, replacer, text)
+
+
+if __name__ == "__main__":
+    changed = False
+    for file, keep_length in FILES_TO_UPDATE:
+        console.print(f"[bright_blue]Updating {file}")
+        file_content = file.read_text()
+        new_content = file_content
+        if UPGRADE_PIP:
+            pip_version = get_latest_pypi_version("pip")
+            console.print(f"[bright_blue]Latest pip version: {pip_version}")
+            for line_pattern, quoting in PIP_PATTERNS:
+                new_content = replace_version(
+                    line_pattern, get_replacement(pip_version, quoting), 
new_content, keep_length
+                )
+        if UPGRADE_UV:
+            uv_version = get_latest_pypi_version("uv")
+            console.print(f"[bright_blue]Latest uv version: {uv_version}")
+            for line_pattern, quoting in UV_PATTERNS:
+                new_content = replace_version(
+                    line_pattern, get_replacement(uv_version, quoting), 
new_content, keep_length
+                )
+        if UPGRADE_PRE_COMMIT:
+            pre_commit_version = get_latest_pypi_version("pre-commit")
+            console.print(f"[bright_blue]Latest pre-commit version: 
{pre_commit_version}")
+            for line_pattern, quoting in PRE_COMMIT_PATTERNS:
+                new_content = replace_version(
+                    line_pattern, get_replacement(pre_commit_version, 
quoting), new_content, keep_length
+                )
+            if UPGRADE_UV:
+                pre_commit_uv_version = 
get_latest_pypi_version("pre-commit-uv")
+                console.print(f"[bright_blue]Latest pre-commit-uv version: 
{pre_commit_uv_version}")
+                for line_pattern, quoting in PRE_COMMIT_UV_PATTERNS:
+                    new_content = replace_version(
+                        line_pattern,
+                        get_replacement(pre_commit_uv_version, quoting),
+                        new_content,
+                        keep_length,
+                    )
+        if new_content != file_content:
+            file.write_text(new_content)
+            console.print(f"[bright_blue]Updated {file}")
+            changed = True
+    if changed:
+        sys.exit(1)


Reply via email to