|
| 1 | +# CLAUDE.md |
| 2 | + |
| 3 | +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. |
| 4 | + |
| 5 | +Pythonlings is "Rustlings for Python": a terminal TUI (Textual) where learners fix small broken exercises and checks rerun on save. Published on PyPI as `pythonlings` (formerly `python-learnings`; the unrelated `pylings` PyPI name belongs to a different project). |
| 6 | + |
| 7 | +## Commands |
| 8 | + |
| 9 | +```bash |
| 10 | +pip install -e ".[dev]" # local install with pytest deps |
| 11 | + |
| 12 | +python -m pytest -q # full suite |
| 13 | +python -m pytest tests/unit/test_runner.py -q # one file |
| 14 | +python -m pytest tests/unit/test_state.py::test_name -q # one test |
| 15 | + |
| 16 | +pylings --root tests/fixtures/passing_curriculum verify # smoke-test: all solutions pass their checks |
| 17 | + |
| 18 | +python -m build # sdist + wheel |
| 19 | +``` |
| 20 | + |
| 21 | +Manual testing of flows: `pylings init --path ./learn-python` (create a learner workspace), `pylings` (TUI), `pylings run variables1`, `pylings dry-run variables1`, `pylings solution variables1`, `pylings hint`, `pylings list`, `pylings topics`, `pylings reset`. `--root` points any command at an arbitrary workspace (used heavily by tests against `tests/fixtures/`). |
| 22 | + |
| 23 | +## Architecture |
| 24 | + |
| 25 | +Two distinct trees in this repo: |
| 26 | + |
| 27 | +1. **The application** — `pylings/` (installable package) |
| 28 | +2. **The curriculum** — repo-root `exercises/`, `checks/`, `solutions/`, and `info.toml` |
| 29 | + |
| 30 | +At build time, hatch `force-include` maps the curriculum into the wheel as `pylings/curriculum/` (see `pyproject.toml`). `pylings init` copies that bundled curriculum into a self-contained learner workspace; progress lives in `<workspace>/.pylings/state.json` (written atomically, with `.bak` recovery on corruption). |
| 31 | + |
| 32 | +### Curriculum model |
| 33 | + |
| 34 | +Each exercise is a triple that must stay in sync, plus a manifest entry: |
| 35 | + |
| 36 | +- `exercises/<topic>/<name>.py` — the broken code the learner edits, containing the `# I AM NOT DONE` marker (defined in `core/exercise.py`) |
| 37 | +- `checks/<topic>/<name>.py` — hidden bare `assert` statements (not pytest) |
| 38 | +- `solutions/<name>.py` — reference answer |
| 39 | +- `info.toml` — ordered `[[exercises]]` entries with `name`, `path`, `hint`, `docs` URL; this file is the source of truth for exercise order and topics |
| 40 | + |
| 41 | +When changing curriculum, update all four, then run the relevant tests plus `pylings --root tests/fixtures/passing_curriculum verify`. Exercise names are topic + ordinal (`variables1`, `collections10`). |
| 42 | + |
| 43 | +### How checks run (`core/runner.py`) |
| 44 | + |
| 45 | +An exercise passes when a generated runner script `exec()`s the exercise source and then the check source in a shared namespace, in a fresh subprocess with a 5s timeout. The check sees the exercise's variables directly — that's why checks are bare asserts. Passing checks alone aren't enough: the learner must also remove `# I AM NOT DONE` to advance. |
| 46 | + |
| 47 | +### Layering |
| 48 | + |
| 49 | +- `pylings/core/` — all filesystem, manifest, state, reset, solutions, runner, and watcher logic. No UI imports. |
| 50 | +- `pylings/screens/` and `pylings/widgets/` — Textual UI only; `pylings/app.py` wires them up; `pylings.tcss` holds styles. |
| 51 | +- `pylings/cli.py` — argparse subcommands; entry point `pylings = "pylings.cli:main"`. |
| 52 | + |
| 53 | +Keep UI behavior in screens/widgets and behavior logic in core — tests depend on this split (`tests/unit/` for core, `tests/integration/` for CLI/workspace flows, `tests/tui/` for Textual pilot tests, fixtures in `tests/fixtures/`). |
| 54 | + |
| 55 | +## Conventions |
| 56 | + |
| 57 | +- `requires-python = ">=3.9"`: guard newer-stdlib usage (e.g. `tomllib` falls back to `tomli` in `core/manifest.py`); `from __future__ import annotations` at the top of modules. |
| 58 | +- Async tests run under `pytest-asyncio` in auto mode (configured in `pyproject.toml`). |
| 59 | +- Tests are named `test_<behavior>.py` / `test_<expected_behavior>`. |
| 60 | +- Commits use conventional prefixes (`feat:`, `fix:`, `docs:`, `chore:`); work flows through `feature/*` → `dev` → `main`. |
| 61 | +- `AGENTS.md` holds the same contributor guidelines in long form. |
0 commit comments