From 21ac516cf99c201ab3fec72b626d91533155822e Mon Sep 17 00:00:00 2001 From: Lasse Benninga Date: Thu, 4 Jun 2026 10:18:32 +0200 Subject: [PATCH] feat(grader): venv hygiene checks + Python .gitignore MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds three grader_lib checks to surface venv hygiene problems on every PR, and fixes the template's .gitignore so cloned student repos don't accidentally track .venv/ or __pycache__/. - .gitignore: add Python virtualenv folders (.venv/, venv/, env/, ENV/), bytecode/caches (__pycache__/, *.pyc, .pytest_cache/, .ruff_cache/, .mypy_cache/), and build artefacts. The template previously shipped the GitHub Node template only — Python students cloning it had no protection against committing a venv folder. - grader_lib.sh: - Extend check_gitignore_python: also require .venv/ (or venv/). - Add check_no_venv_committed: FAILs when git ls-files shows a tracked .venv/, venv/, env/, or ENV/ entry. Curriculum: Week 1 Ch1 'Don't commit the venv folder'. - Add check_lockfile_present: WARNs when no requirements.txt, uv.lock, or pyproject.toml is at the repo root, with a nudge to 'pip freeze > requirements.txt' inside an activated venv. - test.sh: wire the two new checks into the Code Hygiene block, matching this PR series' '0-pt warnings, scoring ladder unchanged' rule. Companion PR on the curriculum side: HackYourFuture/hyf-datatrack #245. Co-Authored-By: Claude Opus 4.7 (1M context) --- .gitignore | 20 ++++++++++++++++++ .hyf/grader_lib.sh | 51 ++++++++++++++++++++++++++++++++++++++++++++-- .hyf/test.sh | 10 ++++++++- 3 files changed, 78 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 2b76d7c..b97fd73 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,26 @@ Thumbs.db # hyf .hyf/score.json +# Python virtual environments +.venv/ +venv/ +env/ +ENV/ + +# Python bytecode and caches +__pycache__/ +*.pyc +*.pyo +*.pyd +.pytest_cache/ +.ruff_cache/ +.mypy_cache/ + +# Python build / dist artifacts +*.egg-info/ +build/ +dist/ + # Editor and IDE settings .vscode/ .idea/ diff --git a/.hyf/grader_lib.sh b/.hyf/grader_lib.sh index 1871010..bed5074 100755 --- a/.hyf/grader_lib.sh +++ b/.hyf/grader_lib.sh @@ -101,7 +101,7 @@ check_gitignore_python() { # Warns when Python cache patterns are absent from .gitignore. local gi="${1:-.gitignore}" if [[ ! -f "$gi" ]]; then - warn ".gitignore is missing — add one so __pycache__/ and *.pyc are not committed" + warn ".gitignore is missing — add one so __pycache__/, *.pyc, and .venv/ are not committed" return 0 fi local ok=true @@ -117,7 +117,54 @@ check_gitignore_python() { warn ".gitignore missing .env — secret files should not be committed" ok=false fi - if [[ "$ok" = true ]]; then pass ".gitignore correctly excludes __pycache__/, *.pyc, and .env"; fi + # Either .venv/ or venv/ must be excluded (the two common conventions). + if ! grep -qE "^\.?venv/?$" "$gi"; then + warn ".gitignore missing .venv/ (or venv/) — virtual environment folder must not be committed" + ok=false + fi + if [[ "$ok" = true ]]; then pass ".gitignore correctly excludes __pycache__/, *.pyc, .env, and .venv/"; fi + return 0 +} + +check_no_venv_committed() { + # Usage: check_no_venv_committed + # Fails when a virtual-environment folder is tracked by git. The venv is a + # per-machine artefact: committing it bloats the repo, leaks local paths, + # and breaks the moment a teammate on another OS clones the repo. + # Detection prefers `git ls-files` (only flags tracked entries); falls back + # to a filesystem check when git is unavailable. + local found="" + if command -v git &>/dev/null && git rev-parse --is-inside-work-tree &>/dev/null; then + found=$(git ls-files | grep -E "^(\.venv|venv|env|ENV)/" | head -3 || true) + else + for d in .venv venv env ENV; do + [[ -d "$d" ]] && found="$d/" + done + fi + if [[ -n "$found" ]]; then + fail "virtual environment folder is committed — add .venv/ to .gitignore and 'git rm -r --cached .venv' to remove it from history. Tracked entries: + ${found}" + else + pass "no virtual environment folder committed" + fi + return 0 +} + +check_lockfile_present() { + # Usage: check_lockfile_present + # Warns when neither requirements.txt nor uv.lock nor pyproject.toml exists + # at the repo root. The lockfile is the *recipe* a fresh venv needs to + # reproduce the student's environment — without it CI cannot install + # whatever the student's code imports. + if [[ -f requirements.txt ]] || [[ -f uv.lock ]] || [[ -f pyproject.toml ]]; then + local seen=() + [[ -f requirements.txt ]] && seen+=("requirements.txt") + [[ -f uv.lock ]] && seen+=("uv.lock") + [[ -f pyproject.toml ]] && seen+=("pyproject.toml") + pass "lockfile present: ${seen[*]}" + else + warn "no requirements.txt / uv.lock / pyproject.toml at repo root — run 'pip freeze > requirements.txt' inside your activated venv and commit it (see Week 1 Ch1)" + fi return 0 } diff --git a/.hyf/test.sh b/.hyf/test.sh index fcedee8..f6a9eff 100755 --- a/.hyf/test.sh +++ b/.hyf/test.sh @@ -167,9 +167,17 @@ if [ -s task-2/AI_DEBUG.md ]; then fi fi -# .gitignore should exclude Python cache files. +# .gitignore should exclude Python cache files + the venv folder. check_gitignore_python ".gitignore" +# Virtual environment folders (.venv/, venv/) must not be committed. +# Curriculum: Week 1 Ch1 "Don't commit the venv folder". +check_no_venv_committed + +# A lockfile (requirements.txt / uv.lock / pyproject.toml) must exist at the +# repo root so a fresh venv can be reproduced from it. +check_lockfile_present + print_results "Week 1 Autograder" # ── Final score ──────────────────────────────────────────────────────────────