From 1f49218fce48c3846bb9b270f1a8a027c62e6759 Mon Sep 17 00:00:00 2001 From: xgboosted <1528712+xgboosted@users.noreply.github.com> Date: Sat, 4 Jul 2026 20:00:21 +1000 Subject: [PATCH] refactor(tools): drop PyYAML from lint version check - parse .pre-commit-config.yaml rev fields with a regex instead of yaml.safe_load; the file is repo-controlled and flat, so stdlib covers it and the lint extra stays two pins (pyyaml removed) - normalize YAML-quoted and v-prefixed revs so a quoting change can't produce a false mismatch - derive the checked tool list from REPO_TO_TOOL instead of a third hardcoded copy - drop the Makefile PyYAML probe made moot by the above - halve persist-step comments in ci.yml; kept detached-HEAD reset, never-reopen, and sed fail-by-name rationale --- .github/workflows/ci.yml | 32 +++++++++++--------------------- Makefile | 1 - pyproject.toml | 3 --- tools/check_lint_versions.py | 27 ++++++++++++--------------- 4 files changed, 23 insertions(+), 40 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 63fdb7b..44b2f64 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -347,23 +347,17 @@ jobs: run: twine upload dist/* - name: Open PR to persist requires-python floor - # The "Update pyproject.toml" step above only patches this job's ephemeral - # checkout to build the wheel — it never touches the repository, so the - # committed requires-python silently goes stale each time - # LATEST_PYTHON_VERSION advances in this file. Once the release has - # published successfully, open a PR with just that one-line bump so a - # human reviews and merges it — no direct push to main. Classifier - # placeholders ({{LATEST-N}}) are intentionally left untouched here; - # they are re-resolved fresh at every future release. + # The wheel-building steps above never touch the repository, so the + # committed requires-python goes stale each time LATEST_PYTHON_VERSION + # advances. Open a PR (never push main directly) with that one-line bump; + # {{LATEST-N}} classifier placeholders are re-resolved at every release. env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | NEW_FLOOR="${{ fromJson(needs.generate-matrix.outputs.python-versions)[0] }}" - # This job checked out the release tag (detached HEAD) and the - # classifier-patching step above dirtied pyproject.toml in that tree. - # -f discards those local edits; -B pins local main exactly to - # origin/main so the sed below starts from a clean, current base. + # Checkout is the release tag (detached HEAD) with pyproject.toml + # dirtied by the classifier-patching step; -f -B resets to clean main. git fetch origin main git checkout -f -B main origin/main @@ -373,11 +367,9 @@ jobs: exit 0 fi - # If a previous bump PR for this same floor was closed without merging, - # its branch still exists and this guard no-ops on every later release - # until that branch is deleted or merged. Deliberate: closing that PR - # is a maintainer decision not to take the bump, and this job should - # not keep reopening it. + # Existing branch = a PR for this floor is open, or was closed without + # merging (a maintainer decision not to take the bump). Either way, + # skip — never reopen. BRANCH="chore/requires-python-$NEW_FLOOR" if git ls-remote --exit-code --heads origin "$BRANCH" >/dev/null 2>&1; then echo "Branch $BRANCH already exists on origin, skipping (PR likely already open)." @@ -397,10 +389,8 @@ jobs: git checkout -b "$BRANCH" git add pyproject.toml git commit -m "chore: bump requires-python floor to >=$NEW_FLOOR" - # Auth: the push reuses the credentials actions/checkout persisted in - # this clone, authorized by this job's `contents: write`; gh uses - # GH_TOKEN from env, authorized by `pull-requests: write`. No explicit - # token-in-URL setup is needed (unlike mirror.yml's cross-host push). + # Push auth: credentials persisted by actions/checkout + this job's + # `contents: write`; gh auth: GH_TOKEN + `pull-requests: write`. git push origin "$BRANCH" gh pr create \ diff --git a/Makefile b/Makefile index 694a001..9ab7481 100644 --- a/Makefile +++ b/Makefile @@ -123,7 +123,6 @@ lint: ruff check pandas_ta_classic --select E9,F63,F7,F82 ruff check pandas_ta_classic --extend-select C901,E501 --exit-zero @echo "Checking black/ruff versions match .pre-commit-config.yaml..." - @$(PYTHON) -c "import yaml" 2>/dev/null || { echo "PyYAML missing — run 'make install-dev' (or pip install -e '.[lint]') first"; exit 1; } $(PYTHON) tools/check_lint_versions.py format: diff --git a/pyproject.toml b/pyproject.toml index 0f22e01..01b136a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -60,9 +60,6 @@ oracle = [ lint = [ "ruff>=0.15.20", "black>=26.5.1", - # Not a linter: tools/check_lint_versions.py (run by the CI code-quality - # job, which installs only this extra) parses .pre-commit-config.yaml. - "pyyaml>=6.0", ] # PyPI publishing tools diff --git a/tools/check_lint_versions.py b/tools/check_lint_versions.py index 01941f4..19f0e0e 100644 --- a/tools/check_lint_versions.py +++ b/tools/check_lint_versions.py @@ -14,8 +14,6 @@ import sys import tomllib from pathlib import Path -import yaml - ROOT = Path(__file__).parent.parent # Repos whose pre-commit `rev:` mirrors a pin in the lint extra. Any other @@ -32,25 +30,24 @@ def main() -> int: lint_pins = pyproject["project"]["optional-dependencies"]["lint"] # A pin that doesn't match (or a missing tool) leaves its entry unset, # which the mismatch check below reports as `tool: ...=None` and fails. - pyproject_versions = {} - for pin in lint_pins: - match = re.match(r"([a-zA-Z_-]+)>=(.+)", pin) - if match: - pyproject_versions[match.group(1)] = match.group(2) + pyproject_versions = {m[1]: m[2] for pin in lint_pins if (m := re.match(r"([a-zA-Z_-]+)>=(.+)", pin))} - precommit = yaml.safe_load((ROOT / ".pre-commit-config.yaml").read_text()) - precommit_versions = {} - for entry in precommit.get("repos", []): - tool = REPO_TO_TOOL.get(entry.get("repo", "")) - if tool is not None: - # ruff-pre-commit tags are v-prefixed (v0.15.20); black's are bare. - precommit_versions[tool] = str(entry.get("rev", "")).lstrip("v") + # The config is a repo-controlled flat file, so a regex pairing each + # `repo:` with the `rev:` on the next line beats a PyYAML dependency. + # Revs may be YAML-quoted, and ruff-pre-commit tags are v-prefixed + # (v0.15.20) while black's are bare — normalize both away. + precommit = (ROOT / ".pre-commit-config.yaml").read_text() + precommit_versions = { + REPO_TO_TOOL[repo]: rev.strip("'\"").lstrip("v") + for repo, rev in re.findall(r"- repo: (\S+)\s*\n\s*rev: (\S+)", precommit) + if repo in REPO_TO_TOOL + } # Missing on either side is a failure too — `!=` alone would let a tool # absent from BOTH files pass as None == None. mismatches = [ f"{tool}: pyproject.toml={pyproject_versions.get(tool)} vs .pre-commit-config.yaml={precommit_versions.get(tool)}" - for tool in ("black", "ruff") + for tool in sorted(REPO_TO_TOOL.values()) if pyproject_versions.get(tool) != precommit_versions.get(tool) or pyproject_versions.get(tool) is None ] if mismatches: