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 ──────────────────────────────────────────────────────────────