mirror of
https://github.com/GAM-team/GAM.git
synced 2026-06-03 22:01:39 +00:00
Add daily workflow for dependency pinning
This workflow automates the process of checking and pinning Python dependencies to stable versions that are at least two weeks old, creating a pull request with the updates.
This commit is contained in:
121
.github/workflows/upgrade_deps.yml
vendored
Normal file
121
.github/workflows/upgrade_deps.yml
vendored
Normal file
@@ -0,0 +1,121 @@
|
||||
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
|
||||
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")
|
||||
|
||||
# Regex to find standard dependencies under [project] dependencies = [ ... ]
|
||||
# This handles lines like "urllib3>=1.26", "cryptography", etc. inside the array
|
||||
dep_block_match = re.search(r'dependencies\s*=\s*\[(.*?)\]', content, re.DOTALL)
|
||||
if not dep_block_match:
|
||||
print("No standard project dependencies array found.")
|
||||
exit(0)
|
||||
|
||||
dep_block = dep_block_match.group(1)
|
||||
raw_deps = re.findall(r'"([^"]+)"', dep_block)
|
||||
|
||||
updated_deps = []
|
||||
two_weeks_ago = datetime.now(timezone.utc) - timedelta(days=14)
|
||||
|
||||
print(f"Evaluating dependencies against cutoff date: {two_weeks_ago.isoformat()}")
|
||||
|
||||
for dep in raw_deps:
|
||||
# Isolate base package name (e.g., "urllib3>=1.25" -> "urllib3")
|
||||
pkg_name = re.split(r'[<>=!~;]', dep)[0].strip()
|
||||
|
||||
# Pull environment markers to re-attach later if they exist (e.g., ; sys_platform == 'win32')
|
||||
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
|
||||
|
||||
# Get upload time of the first file in the release
|
||||
upload_time_str = files[0].get("upload_time_iso_8601")
|
||||
if not upload_time_str:
|
||||
continue
|
||||
|
||||
# Normalize trailing Z to +00:00 for Python's datetime parser
|
||||
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 (alpha/beta/rc)
|
||||
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}"
|
||||
updated_deps.append(f' "{pinned_dep}",')
|
||||
print(f" -> Pinned to {target_version}")
|
||||
else:
|
||||
# Fallback to original line if no historical versions found
|
||||
updated_deps.append(f' "{dep}",')
|
||||
|
||||
except Exception as e:
|
||||
print(f" -> Error fetching package {pkg_name}: {e}. Keeping original constraint.")
|
||||
updated_deps.append(f' "{dep}",')
|
||||
|
||||
# 2. Reconstruct the dependencies block in pyproject.toml
|
||||
new_dep_block = "\n" + "\n".join(updated_deps) + "\n"
|
||||
new_content = content.replace(dep_block_match.group(1), new_dep_block)
|
||||
toml_path.write_text(new_content, encoding="utf-8")
|
||||
|
||||
- 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
|
||||
Reference in New Issue
Block a user