Files
GoogleDriveManagement/.github/workflows/upgrade_deps.yml
Jay Lee eb59663f6a [no ci] Enhance upgrade_deps.yml with comments and cleanup
Updated comments for clarity and added cleanup for resolved.txt.
2026-06-27 15:10:29 -04:00

153 lines
5.7 KiB
YAML

name: Daily Dependency Pinning (2-Week Buffer)
on:
schedule:
# Runs every day at midnight UTC
- cron: '0 0 * * *'
workflow_dispatch: # Allows you to trigger it manually from the UI
jobs:
pin-deps:
runs-on: ubuntu-slim
permissions:
contents: write
pull-requests: write
steps:
- name: Checkout Repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405
with:
python-version: '3.14'
- name: Install uv
uses: astral-sh/setup-uv@v3
with:
version: "latest"
- name: Calculate and pin two-week old stable versions
shell: python
run: |
import subprocess
import re
import tomllib
import os
from datetime import datetime, timedelta, timezone
from pathlib import Path
toml_path = Path("pyproject.toml")
if not toml_path.exists():
print("pyproject.toml not found!")
exit(1)
content = toml_path.read_text(encoding="utf-8")
parsed_toml = tomllib.loads(content)
target_deps = set()
# Gather all dependencies
target_deps.update(parsed_toml.get("project", {}).get("dependencies", []))
for deps in parsed_toml.get("project", {}).get("optional-dependencies", {}).values():
target_deps.update(deps)
for deps in parsed_toml.get("dependency-groups", {}).values():
if isinstance(deps, list):
for d in deps:
if isinstance(d, str):
target_deps.add(d)
if not target_deps:
print("No dependencies found to process.")
exit(0)
# 1. Create a "clean" requirements list (base names + markers only, no versions)
clean_reqs = []
for dep in target_deps:
pkg_base = re.split(r'[<>=!~;\s]', dep)[0].strip()
if ";" in dep:
marker = dep.split(";", 1)[1].strip()
clean_reqs.append(f"{pkg_base} ; {marker}")
else:
clean_reqs.append(pkg_base)
temp_in = Path("temp_reqs.in")
temp_in.write_text("\n".join(clean_reqs), encoding="utf-8")
# 2. Calculate Cutoff Date
two_weeks_ago = datetime.now(timezone.utc) - timedelta(days=14)
cutoff_str = two_weeks_ago.strftime("%Y-%m-%dT%H:%M:%SZ")
print(f"Resolving dependencies against cutoff date: {cutoff_str}")
# 3. Use uv to resolve the CLEAN list, allowing free upgrades/downgrades
try:
subprocess.run([
"uv", "pip", "compile",
str(temp_in),
"--exclude-newer", cutoff_str,
"--quiet",
"-o", "resolved.txt"
], check=True)
except subprocess.CalledProcessError:
print("\nDependency resolution failed! Upstream constraints are impossible to satisfy.")
if temp_in.exists(): temp_in.unlink()
exit(1)
# 4. Parse the resolved lockfile
resolved_versions = {}
with open("resolved.txt", "r", encoding="utf-8") as f:
for line in f:
line = line.split("#")[0].strip()
if "==" in line:
pkg, ver = line.split("==", 1)
resolved_versions[pkg.strip().lower()] = ver.strip()
# Cleanup temp files so they don't get committed to the PR
if temp_in.exists():
temp_in.unlink()
if Path("resolved.txt").exists():
Path("resolved.txt").unlink()
# 5. Map the newly resolved versions back to your pyproject.toml updates
updates = {}
for dep in target_deps:
pkg_name_raw = re.split(r'[<>=!~;\s]', dep)[0].strip()
pkg_name_lower = pkg_name_raw.lower()
marker = ""
if ";" in dep:
marker = " ; " + dep.split(";", 1)[1].strip()
if pkg_name_lower in resolved_versions:
target_version = resolved_versions[pkg_name_lower]
pinned_dep = f"{pkg_name_raw}=={target_version}{marker}"
if pinned_dep != dep:
updates[dep] = pinned_dep
print(f" -> Changing: '{dep}' => '{pinned_dep}'")
else:
print(f" -> Up to date: {dep}")
# 6. Replace the strings safely in the original file content
new_content = content
for old_dep, new_dep in updates.items():
escaped_old = re.escape(old_dep)
pattern = r'([\'"])' + escaped_old + r'\1'
new_content = re.sub(pattern, lambda m: m.group(1) + new_dep + m.group(1), new_content)
if content != new_content:
toml_path.write_text(new_content, encoding="utf-8")
print("\npyproject.toml updated successfully.")
else:
print("\nNo updates required.")
- name: Create Pull Request
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: "chore: upgrade PyPi deps"
title: "Upgrade PyPi deps"
body: "Automated scan checking PyPI for mutually compatible package versions at least 2 weeks old. Handles both upgrades and conflict-driven downgrades."
branch: sys-deps-upgrade
force: false # Standard push, plays nice with rulesets