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 e6ff18ca629 Add selected packages and explain why to the package
scripts (#52433)
e6ff18ca629 is described below
commit e6ff18ca629d6033a7f15d93e4e8e31e297d4167
Author: Jarek Potiuk <[email protected]>
AuthorDate: Sun Jun 29 00:10:05 2025 +0200
Add selected packages and explain why to the package scripts (#52433)
The script has now two more parameters:
* --selected-packages with comma separated list of packages
* --explain-why - explaining whhy the latest version of packages
is not installed.
---
dev/constraints-updated-version-check.py | 103 ++++++++++++++++++++++++++++---
1 file changed, 96 insertions(+), 7 deletions(-)
diff --git a/dev/constraints-updated-version-check.py
b/dev/constraints-updated-version-check.py
index 7b0b23c5770..767df85743a 100644
--- a/dev/constraints-updated-version-check.py
+++ b/dev/constraints-updated-version-check.py
@@ -25,6 +25,7 @@
from __future__ import annotations
import json
+import os
import re
import urllib.request
from datetime import datetime
@@ -156,7 +157,7 @@ def get_first_newer_release_date_str(releases,
current_version):
return datetime.fromisoformat(upload_time_str.replace("Z",
"+00:00")).strftime("%Y-%m-%d")
-def main(python_version, mode):
+def main(python_version, mode, selected_packages=None, explain_why=False):
lines = get_constraints_file(python_version)
constraints_date = parse_constraints_generation_date(lines)
@@ -172,7 +173,9 @@ def main(python_version, mode):
if line and not line.startswith("#") and "@" not in line:
match = re.match(r"^([a-zA-Z0-9_.\-]+)==([\w.\-]+)$", line)
if match:
- packages.append((match.group(1), match.group(2)))
+ pkg_name = match.group(1)
+ if not selected_packages or (pkg_name in selected_packages):
+ packages.append((pkg_name, match.group(2)))
max_pkg_length = get_max_package_length(packages)
@@ -209,13 +212,84 @@ def main(python_version, mode):
"PyPI Link",
]
- console.print(f"[bold magenta]{format_str.format(*headers)}[/]")
- total_width = sum(col_widths.values()) + (len(col_widths) - 1) * 3
- console.print(f"[magenta]{'=' * total_width}[/]")
+ if not explain_why:
+ console.print(f"[bold magenta]{format_str.format(*headers)}[/]")
+ total_width = sum(col_widths.values()) + (len(col_widths) - 1) * 3
+ console.print(f"[magenta]{'=' * total_width}[/]")
+ else:
+ total_width = sum(col_widths.values()) + (len(col_widths) - 1) * 3
outdated_count = 0
skipped_count = 0
+ if explain_why and packages:
+ import shutil
+ import subprocess
+ from contextlib import contextmanager
+ from pathlib import Path
+
+ @contextmanager
+ def pyproject_backup_restore(pyproject_path):
+ backup_path = pyproject_path.with_suffix(pyproject_path.suffix +
".bak")
+ shutil.copyfile(pyproject_path, backup_path)
+ try:
+ yield
+ finally:
+ if backup_path.exists():
+ shutil.copyfile(backup_path, pyproject_path)
+ backup_path.unlink()
+
+ def run_uv_sync(cmd, cwd):
+ env = os.environ.copy()
+ env.pop("VIRTUAL_ENV", None)
+ result = subprocess.run(cmd, cwd=cwd, env=env,
capture_output=True, text=True)
+ return result.stdout + result.stderr
+
+ airflow_pyproject = Path(__file__).parent.parent / "pyproject.toml"
+ airflow_pyproject = airflow_pyproject.resolve()
+ repo_root = airflow_pyproject.parent
+ with pyproject_backup_restore(airflow_pyproject):
+ for pkg, _ in packages:
+ console.print(f"[bold blue]\n--- Explaining for {pkg} ---[/]")
+ # 1. Run uv sync --all-packages
+ before_output = run_uv_sync(
+ ["uv", "sync", "--all-packages", "--resolution",
"highest"], cwd=repo_root
+ )
+ with open("/tmp/uv_sync_before.txt", "w") as f:
+ f.write(before_output)
+ # 2. Get latest version from PyPI
+ pypi_url = f"https://pypi.org/pypi/{pkg}/json"
+ with urllib.request.urlopen(pypi_url) as resp:
+ data = json.loads(resp.read().decode("utf-8"))
+ latest_version = data["info"]["version"]
+ # 3. Update pyproject.toml (append dependency line)
+ lines = airflow_pyproject.read_text().splitlines()
+ new_lines = []
+ in_deps = False
+ dep_added = False
+ for line in lines:
+ new_lines.append(line)
+ if line.strip() == "dependencies = [":
+ in_deps = True
+ elif in_deps and line.strip().startswith("]") and not
dep_added:
+ # Add the new dependency just before closing bracket
+ new_lines.insert(-1, f' "{pkg}=={latest_version}",')
+ dep_added = True
+ in_deps = False
+ if not dep_added:
+ # fallback: just append at the end
+ new_lines.append(f' "{pkg}=={latest_version}",')
+ airflow_pyproject.write_text("\n".join(new_lines) + "\n")
+ # 4. Run uv sync --all-packages and capture output
+ after_output = run_uv_sync(
+ ["uv", "sync", "--all-packages", "--resolution",
"highest"], cwd=repo_root
+ )
+ with open("/tmp/uv_sync_after.txt", "w") as f:
+ f.write(after_output)
+ console.print(f"[yellow]uv sync output for
{pkg}=={latest_version}:[/]")
+ console.print(after_output)
+ return
+
for pkg, pinned_version in packages:
try:
pypi_url = f"https://pypi.org/pypi/{pkg}/json"
@@ -314,8 +388,23 @@ def main(python_version, mode):
show_default=True,
help="Operation mode: full, diff-constraints, or diff-all.",
)
-def cli(python_version, mode):
- main(python_version, mode)
[email protected](
+ "--selected-packages",
+ required=False,
+ default=None,
+ help="Comma-separated list of package names to check (e.g.,
'requests,flask'). If not set, all packages are checked.",
+)
[email protected](
+ "--explain-why",
+ is_flag=True,
+ default=False,
+ help="For each selected package, attempts to upgrade to the latest version
and explains why it cannot be upgraded.",
+)
+def cli(python_version, mode, selected_packages, explain_why):
+ selected_packages_set = None
+ if selected_packages:
+ selected_packages_set = set(pkg.strip() for pkg in
selected_packages.split(",") if pkg.strip())
+ main(python_version, mode, selected_packages_set, explain_why)
if __name__ == "__main__":