diff --git a/.github/actions/install-pre-commit/action.yml b/.github/actions/install-pre-commit/action.yml new file mode 100644 index 0000000000000..02eea2c722917 --- /dev/null +++ b/.github/actions/install-pre-commit/action.yml @@ -0,0 +1,50 @@ +# 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. +# +--- +name: 'Install pre-commit' +description: 'Installs pre-commit and related packages' +inputs: + python-version: + description: 'Python version to use' + default: 3.9 + uv-version: + description: 'uv version to use' + default: 0.4.29 + pre-commit-version: + description: 'pre-commit version to use' + default: 4.0.1 + pre-commit-uv-version: + description: 'pre-commit-uv version to use' + default: 4.1.4 +runs: + using: "composite" + steps: + - name: Install pre-commit, uv, and pre-commit-uv + shell: bash + run: > + pip install + pre-commit==${{inputs.pre-commit-version}} + uv==${{inputs.uv-version}} + pre-commit-uv==${{inputs.pre-commit-uv-version}} + - name: Cache pre-commit envs + uses: actions/cache@v4 + with: + path: ~/.cache/pre-commit + key: "pre-commit-${{inputs.python-version}}-${{ hashFiles('.pre-commit-config.yaml') }}" + restore-keys: | + pre-commit-${{inputs.python-version}}- diff --git a/.github/workflows/basic-tests.yml b/.github/workflows/basic-tests.yml index 5b6600e560c5d..bf7e8ab7bf79b 100644 --- a/.github/workflows/basic-tests.yml +++ b/.github/workflows/basic-tests.yml @@ -285,16 +285,11 @@ jobs: - name: "Install Breeze" uses: ./.github/actions/breeze id: breeze - - name: Cache pre-commit envs - uses: actions/cache@v4 + - name: "Install pre-commit" + uses: ./.github/actions/install-pre-commit + id: pre-commit with: - path: ~/.cache/pre-commit - # yamllint disable-line rule:line-length - key: "pre-commit-${{steps.breeze.outputs.host-python-version}}-${{ hashFiles('.pre-commit-config.yaml') }}" - restore-keys: "\ - pre-commit-${{steps.breeze.outputs.host-python-version}}-\ - ${{ hashFiles('.pre-commit-config.yaml') }}\n - pre-commit-${{steps.breeze.outputs.host-python-version}}-" + python-version: ${{steps.breeze.outputs.host-python-version}} - name: Fetch incoming commit ${{ github.sha }} with its parent uses: actions/checkout@v4 with: diff --git a/.github/workflows/static-checks-mypy-docs.yml b/.github/workflows/static-checks-mypy-docs.yml index b34ad2c36f45d..be2c4f8e28645 100644 --- a/.github/workflows/static-checks-mypy-docs.yml +++ b/.github/workflows/static-checks-mypy-docs.yml @@ -126,14 +126,11 @@ jobs: - name: "Prepare breeze & CI image: ${{ inputs.default-python-version}}:${{ inputs.image-tag }}" uses: ./.github/actions/prepare_breeze_and_image id: breeze - - name: Cache pre-commit envs - uses: actions/cache@v4 + - name: "Install pre-commit" + uses: ./.github/actions/install-pre-commit + id: pre-commit with: - path: ~/.cache/pre-commit - # yamllint disable-line rule:line-length - key: "pre-commit-${{steps.breeze.outputs.host-python-version}}-${{ hashFiles('.pre-commit-config.yaml') }}" - restore-keys: | - pre-commit-${{steps.breeze.outputs.host-python-version}}- + python-version: ${{steps.breeze.outputs.host-python-version}} - name: "Static checks" run: breeze static-checks --all-files --show-diff-on-failure --color always --initialize-environment env: @@ -170,10 +167,13 @@ jobs: - name: "Prepare breeze & CI image: ${{ inputs.default-python-version }}:${{ inputs.image-tag }}" uses: ./.github/actions/prepare_breeze_and_image id: breeze + - name: "Install pre-commit" + uses: ./.github/actions/install-pre-commit + id: pre-commit + with: + python-version: ${{steps.breeze.outputs.host-python-version}} - name: "MyPy checks for ${{ matrix.mypy-check }}" - run: | - pip install pre-commit - pre-commit run --color always --verbose --hook-stage manual ${{matrix.mypy-check}} --all-files + run: pre-commit run --color always --verbose --hook-stage manual ${{matrix.mypy-check}} --all-files env: VERBOSE: "false" COLUMNS: "250" diff --git a/contributing-docs/03_contributors_quick_start.rst b/contributing-docs/03_contributors_quick_start.rst index a088e3cb0d4ca..96f80220770cc 100644 --- a/contributing-docs/03_contributors_quick_start.rst +++ b/contributing-docs/03_contributors_quick_start.rst @@ -476,7 +476,7 @@ You can still add uv support for pre-commit if you use pipx using the commands: pipx install pre-commit pipx inject - pipx inject pre-commit pre-commit-uv + pipx inject prepare_breeze_and_image Also, if you already use ``uvx`` instead of ``pipx``, use this command: diff --git a/dev/breeze/doc/01_installation.rst b/dev/breeze/doc/01_installation.rst index 1c7ad0ee62838..052dc3faca9f9 100644 --- a/dev/breeze/doc/01_installation.rst +++ b/dev/breeze/doc/01_installation.rst @@ -151,13 +151,28 @@ Docker in WSL 2 If VS Code is installed on the Windows host system then in the WSL Linux Distro you can run ``code .`` in the root directory of you Airflow repo to launch VS Code. -The pipx tool --------------- +The uv tool +----------- + +We are recommending to use the ``uv`` tool to manage your virtual environments and generally as a swiss-knife +of your Python environment (it supports installing various versions of Python, creating virtual environments, +installing packages, managing workspaces and running development tools.). + +Installing ``uv`` is described in the `uv documentation `_. +We highly recommend using ``uv`` to manage your Python environments, as it is very comprehensive, +easy to use, it is faster than any of the other tools availables (way faster!) and has a lot of features +that make it easier to work with Python. + +Alternative: pipx tool +---------------------- -We are using ``pipx`` tool to install and manage Breeze. The ``pipx`` tool is created by the creators +However, we do not want to be entirely dependent on ``uv`` as it is a software governed by a VC-backed vendor, +so we always want to provide open-source governed alternatives for our tools. If you can't or do not want to +use ``uv``, we got you covered. Another too you can use to manage development tools (and ``breeze`` development +environment is Python-Software-Foundation managed ``pipx``. The ``pipx`` tool is created by the creators of ``pip`` from `Python Packaging Authority `_ -Note that ``pipx`` >= 1.4.1 is used. +Note that ``pipx`` >= 1.4.1 should be used. Install pipx @@ -172,7 +187,7 @@ environments. This can be done automatically by the following command (follow in pipx ensurepath -In Mac +In case ``pipx`` is not in your PATH, you can run it with Python module: .. code-block:: bash diff --git a/dev/breeze/src/airflow_breeze/commands/developer_commands.py b/dev/breeze/src/airflow_breeze/commands/developer_commands.py index 0f9cec28710ec..b26230c2e1632 100644 --- a/dev/breeze/src/airflow_breeze/commands/developer_commands.py +++ b/dev/breeze/src/airflow_breeze/commands/developer_commands.py @@ -861,7 +861,7 @@ def static_checks( for attempt in range(1, 1 + max_initialization_attempts): get_console().print(f"[info]Attempt number {attempt} to install pre-commit environments") initialization_result = run_command( - [sys.executable, "-m", "pre_commit", "install", "--install-hooks"], + ["pre-commit", "install", "--install-hooks"], check=False, no_output_dump_on_exception=True, text=True, @@ -874,7 +874,7 @@ def static_checks( get_console().print("[error]Could not install pre-commit environments[/]") sys.exit(return_code) - command_to_execute = [sys.executable, "-m", "pre_commit", "run"] + command_to_execute = ["pre-commit", "run"] if not one_or_none_set([last_commit, commit_ref, only_my_changes, all_files]): get_console().print( "\n[error]You can only specify " diff --git a/dev/breeze/src/airflow_breeze/commands/release_management_commands.py b/dev/breeze/src/airflow_breeze/commands/release_management_commands.py index f906ffaa44253..61a26e9993f88 100644 --- a/dev/breeze/src/airflow_breeze/commands/release_management_commands.py +++ b/dev/breeze/src/airflow_breeze/commands/release_management_commands.py @@ -232,13 +232,14 @@ class VersionedFile(NamedTuple): AIRFLOW_PIP_VERSION = "24.3.1" AIRFLOW_UV_VERSION = "0.4.29" AIRFLOW_USE_UV = False -WHEEL_VERSION = "0.36.2" -GITPYTHON_VERSION = "3.1.40" -RICH_VERSION = "13.7.0" -NODE_VERSION = "21.2.0" -PRE_COMMIT_VERSION = "3.5.0" -HATCH_VERSION = "1.9.1" -PYYAML_VERSION = "6.0.1" +# TODO: automate thsese as well +WHEEL_VERSION = "0.44.0" +GITPYTHON_VERSION = "3.1.43" +RICH_VERSION = "13.9.4" +NODE_VERSION = "22.2.0" +PRE_COMMIT_VERSION = "4.0.1" +HATCH_VERSION = "1.13.0" +PYYAML_VERSION = "6.0.2" AIRFLOW_BUILD_DOCKERFILE = f""" FROM python:{DEFAULT_PYTHON_MAJOR_MINOR_VERSION}-slim-{ALLOWED_DEBIAN_VERSIONS[0]} diff --git a/dev/breeze/src/airflow_breeze/global_constants.py b/dev/breeze/src/airflow_breeze/global_constants.py index c80a5a9355eb8..a674b142b3c3e 100644 --- a/dev/breeze/src/airflow_breeze/global_constants.py +++ b/dev/breeze/src/airflow_breeze/global_constants.py @@ -32,10 +32,15 @@ except ImportError: get_console().print( "\n[error]Breeze doesn't support Python version <=3.8\n\n" - "[warning]Use Python 3.9 and force reinstall breeze with pipx\n\n" - " pipx install --force -e ./dev/breeze\n" + "[warning]Use Python 3.9 and force reinstall breeze:" + "" + " either with uv: \n\n" + " uv tool install --force --reinstall --editable ./dev/breeze\n\n" + "" + " or with pipx\n\n" + " pipx install --force -e ./dev/breeze --python 3.9\n" "\nTo find out more, visit [info]https://github.com/apache/airflow/" - "blob/main/dev/breeze/doc/01_installation.rst#the-pipx-tool[/]\n" + "blob/main/dev/breeze/doc/01_installation.rst[/]\n" ) sys.exit(1) from pathlib import Path @@ -253,13 +258,16 @@ def all_helm_test_packages() -> list[str]: @cache def all_task_sdk_test_packages() -> list[str]: - return sorted( - [ - candidate.name - for candidate in (AIRFLOW_SOURCES_ROOT / "task_sdk" / "tests").iterdir() - if candidate.is_dir() and candidate.name != "__pycache__" - ] - ) + try: + return sorted( + [ + candidate.name + for candidate in (AIRFLOW_SOURCES_ROOT / "task_sdk" / "tests").iterdir() + if candidate.is_dir() and candidate.name != "__pycache__" + ] + ) + except FileNotFoundError: + return [] ALLOWED_TASK_SDK_TEST_PACKAGES = [ diff --git a/dev/breeze/src/airflow_breeze/utils/kubernetes_utils.py b/dev/breeze/src/airflow_breeze/utils/kubernetes_utils.py index 3aca9d51c130c..b9bdc5302bdfc 100644 --- a/dev/breeze/src/airflow_breeze/utils/kubernetes_utils.py +++ b/dev/breeze/src/airflow_breeze/utils/kubernetes_utils.py @@ -391,10 +391,7 @@ def create_virtualenv(force_venv_setup: bool) -> RunCommandResult: "[info]You can uninstall breeze and install it again with earlier Python " "version. For example:[/]\n" ) - get_console().print("pipx reinstall --python PYTHON_PATH apache-airflow-breeze\n") - get_console().print( - f"[info]PYTHON_PATH - path to your Python binary(< {higher_python_version_tuple})[/]\n" - ) + get_console().print("[info]Then recreate your k8s virtualenv with:[/]\n") get_console().print("breeze k8s setup-env --force-venv-setup\n") sys.exit(1) diff --git a/dev/breeze/src/airflow_breeze/utils/path_utils.py b/dev/breeze/src/airflow_breeze/utils/path_utils.py index 0feba56356bac..cf04ecc278715 100644 --- a/dev/breeze/src/airflow_breeze/utils/path_utils.py +++ b/dev/breeze/src/airflow_breeze/utils/path_utils.py @@ -167,8 +167,9 @@ def reinstall_if_setup_changed() -> bool: return False if "apache-airflow-breeze" in e.msg: print( - """Missing Package `apache-airflow-breeze`. - Use `pipx install -e ./dev/breeze` to install the package.""" + """Missing Package `apache-airflow-breeze`. Please install it.\n + Use `uv tool install -e ./dev/breeze or `pipx install -e ./dev/breeze` + to install the package.""" ) return False sources_hash = get_installation_sources_config_metadata_hash() @@ -224,10 +225,10 @@ def get_used_airflow_sources() -> Path: @cache def find_airflow_sources_root_to_operate_on() -> Path: """ - Find the root of airflow sources we operate on. Handle the case when Breeze is installed via `pipx` from - a different source tree, so it searches upwards of the current directory to find the right root of - airflow directory we are actually in. This **might** be different than the sources of Airflow Breeze - was installed from. + Find the root of airflow sources we operate on. Handle the case when Breeze is installed via + `pipx` or `uv tool` from a different source tree, so it searches upwards of the current directory + to find the right root of airflow directory we are actually in. This **might** be different + than the sources of Airflow Breeze was installed from. If not found, we operate on Airflow sources that we were installed it. This handles the case when we run Breeze from a "random" directory. diff --git a/dev/breeze/src/airflow_breeze/utils/python_versions.py b/dev/breeze/src/airflow_breeze/utils/python_versions.py index d144139b06816..4f5a7a00bb588 100644 --- a/dev/breeze/src/airflow_breeze/utils/python_versions.py +++ b/dev/breeze/src/airflow_breeze/utils/python_versions.py @@ -51,8 +51,9 @@ def check_python_version(release_provider_packages: bool = False): get_console().print( "[warning]Please reinstall Breeze using Python 3.9 - 3.11 environment because not all " "provider packages support Python 3.12 yet.[/]\n\n" - "For example:\n\n" - "pipx uninstall apache-airflow-breeze\n" - "pipx install --python $(which python3.9) -e ./dev/breeze --force\n" + "If you are using uv:\n\n" + " uv tool install --force --reinstall --python 3.9 -e ./dev/breeze\n\n" + "If you are using pipx:\n\n" + " pipx install --python $(which python3.9) --force -e ./dev/breeze\n" ) sys.exit(1) diff --git a/dev/breeze/src/airflow_breeze/utils/reinstall.py b/dev/breeze/src/airflow_breeze/utils/reinstall.py index de3da92855430..6165c8a307201 100644 --- a/dev/breeze/src/airflow_breeze/utils/reinstall.py +++ b/dev/breeze/src/airflow_breeze/utils/reinstall.py @@ -27,15 +27,24 @@ def reinstall_breeze(breeze_sources: Path, re_run: bool = True): """ - Reinstalls Breeze from specified sources. + Re-installs Breeze from specified sources. :param breeze_sources: Sources where to install Breeze from. :param re_run: whether to re-run the original command that breeze was run with. """ + # First check if `breeze` is installed with uv and if it is, reinstall it using uv + # If not - we assume pipx is used and we reinstall it using pipx # Note that we cannot use `pipx upgrade` here because we sometimes install # Breeze from different sources than originally installed (i.e. when we reinstall airflow # From the current directory. get_console().print(f"\n[info]Reinstalling Breeze from {breeze_sources}\n") - subprocess.check_call(["pipx", "install", "-e", str(breeze_sources), "--force"]) + result = subprocess.run(["uv", "tool", "list"], text=True, capture_output=True, check=False) + if result.returncode == 0: + if "apache-airflow-breeze" in result.stdout: + subprocess.check_call( + ["uv", "tool", "install", "--force", "--reinstall", "-e", breeze_sources.as_posix()] + ) + else: + subprocess.check_call(["pipx", "install", "-e", breeze_sources.as_posix(), "--force"]) if re_run: # Make sure we don't loop forever if the metadata hash hasn't been updated yet (else it is tricky to # run pre-commit checks via breeze!) diff --git a/dev/breeze/src/airflow_breeze/utils/run_utils.py b/dev/breeze/src/airflow_breeze/utils/run_utils.py index 2e828936aa64e..8396c0016dea3 100644 --- a/dev/breeze/src/airflow_breeze/utils/run_utils.py +++ b/dev/breeze/src/airflow_breeze/utils/run_utils.py @@ -219,14 +219,14 @@ def assert_pre_commit_installed(): python_executable = sys.executable get_console().print(f"[info]Checking pre-commit installed for {python_executable}[/]") command_result = run_command( - [python_executable, "-m", "pre_commit", "--version"], + ["pre-commit", "--version"], capture_output=True, text=True, check=False, ) if command_result.returncode == 0: if command_result.stdout: - pre_commit_version = command_result.stdout.split(" ")[-1].strip() + pre_commit_version = command_result.stdout.split(" ")[1].strip() if Version(pre_commit_version) >= Version(min_pre_commit_version): get_console().print( f"\n[success]Package pre_commit is installed. " @@ -238,6 +238,20 @@ def assert_pre_commit_installed(): f"aat least {min_pre_commit_version} and is {pre_commit_version}.[/]\n\n" ) sys.exit(1) + if "pre-commit-uv" not in command_result.stdout: + get_console().print( + "\n[warning]You can significantly improve speed of installing your pre-commit envs " + "by installing `pre-commit-uv` with it.[/]\n" + ) + get_console().print( + "\n[warning]With uv you can install it with:[/]\n\n" + " uv tool install pre-commit --with pre-commit-uv --force-reinstall\n" + ) + get_console().print( + "\n[warning]With pipx you can install it with:[/]\n\n" + " pipx inject\n" + " pipx inject pre-commit pre-commit-uv\n" + ) else: get_console().print( "\n[warning]Could not determine version of pre-commit. You might need to update it![/]\n" @@ -459,9 +473,7 @@ def run_compile_www_assets( "[info]However, it requires you to have local yarn installation.\n" ) command_to_execute = [ - sys.executable, - "-m", - "pre_commit", + "pre-commit", "run", "--hook-stage", "manual", @@ -512,9 +524,7 @@ def run_compile_ui_assets( "[info]However, it requires you to have local pnpm installation.\n" ) command_to_execute = [ - sys.executable, - "-m", - "pre_commit", + "pre-commit", "run", "--hook-stage", "manual", diff --git a/scripts/ci/pre_commit/common_precommit_utils.py b/scripts/ci/pre_commit/common_precommit_utils.py index c3cb40ffeb51c..b8d29410db4aa 100644 --- a/scripts/ci/pre_commit/common_precommit_utils.py +++ b/scripts/ci/pre_commit/common_precommit_utils.py @@ -118,8 +118,11 @@ def initialize_breeze_precommit(name: str, file: str): if shutil.which("breeze") is None: console.print( "[red]The `breeze` command is not on path.[/]\n\n" - "[yellow]Please install breeze with `pipx install -e ./dev/breeze` from Airflow sources " - "and make sure you run `pipx ensurepath`[/]\n\n" + "[yellow]Please install breeze.\n" + "You can use uv with `uv tool install -e ./dev/breeze or " + "`pipx install -e ./dev/breeze`.\n" + "It will install breeze from Airflow sources " + "(make sure you run `pipx ensurepath` if you use pipx)[/]\n\n" "[bright_blue]You can also set SKIP_BREEZE_PRE_COMMITS env variable to non-empty " "value to skip all breeze tests." )