Files
GoogleDriveManagement/.github/workflows/upgrade_deps.yml
Jay Lee ee3bb42d19 [no ci] Enhance dependency management in upgrade_deps workflow
Added support for parsing TOML files using tomllib and updated dependency handling logic to include optional and development dependencies.
2026-05-15 19:49:32 -04:00

145 lines
6.0 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@de0fac2e4500dabe0009e67214ff5f5447ce83dd
- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405
with:
python-version: '3.14'
- name: Calculate and pin two-week old stable versions
shell: python
run: |
import json
import urllib.request
import re
import tomllib
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()
# Standard [project.dependencies]
target_deps.update(parsed_toml.get("project", {}).get("dependencies", []))
# Optional [project.optional-dependencies]
for deps in parsed_toml.get("project", {}).get("optional-dependencies", {}).values():
target_deps.update(deps)
# Dev [dependency-groups] (uv / PEP 735 standard)
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)
updates = {}
two_weeks_ago = datetime.now(timezone.utc) - timedelta(days=14)
print(f"Evaluating dependencies against cutoff date: {two_weeks_ago.isoformat()}")
for dep in target_deps:
# Isolate base package name (e.g., "yubikey-manager>=5.6.1" -> "yubikey-manager")
pkg_name = re.split(r'[<>=!~;\s]', dep)[0].strip()
# Preserve environment markers if they exist
marker = ""
if ";" in dep:
marker = " ; " + dep.split(";", 1)[1].strip()
print(f"Fetching PyPI data for: {pkg_name}")
try:
url = f"https://pypi.org/pypi/{pkg_name}/json"
req = urllib.request.Request(url, headers={'User-Agent': 'GAM-CI-Script'})
with urllib.request.urlopen(req) as response:
data = json.loads(response.read().decode())
valid_versions = []
for ver, files in data.get("releases", {}).items():
if not files:
continue
upload_time_str = files[0].get("upload_time_iso_8601")
if not upload_time_str:
continue
if upload_time_str.endswith('Z'):
upload_time_str = upload_time_str[:-1] + '+00:00'
upload_time = datetime.fromisoformat(upload_time_str)
# Filter: Must be older than 2 weeks and not a pre-release
if upload_time <= two_weeks_ago and not any(x in ver.lower() for x in ['a', 'b', 'rc', 'dev']):
valid_versions.append((upload_time, ver))
if valid_versions:
# Sort by upload time descending to get the newest valid option
valid_versions.sort(key=lambda x: x[0], reverse=True)
target_version = valid_versions[0][1]
pinned_dep = f"{pkg_name}=={target_version}{marker}"
if pinned_dep != dep:
updates[dep] = pinned_dep
print(f" -> Pinning: '{dep}' => '{pinned_dep}'")
else:
print(f" -> Already pinned correctly to {target_version}")
else:
print(f" -> No valid historical versions found.")
except urllib.error.HTTPError as e:
print(f" -> Package not found on PyPI or HTTP error: {e}")
except Exception as e:
print(f" -> Error processing {pkg_name}: {e}")
# 3. Replace the strings safely in the original file content
new_content = content
for old_dep, new_dep in updates.items():
# Regex targets the exact string inside either single or double quotes
# Using a lambda replacement ensures we don't trip over escape sequences in the new string
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)
# Write changes back to pyproject.toml
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 package versions at least 2 weeks old."
branch: sys-deps-upgrade
force: false # Standard push, plays nice with rulesets