diff --git a/CHANGELOG.md b/CHANGELOG.md index 24f30cb..7351947 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,20 @@ All notable changes to this project are documented here. Pythonlings follows Semantic Versioning. +## [0.3.1] - 2026-06-20 + +### Fixed + +- `pythonlings --version` now reports the installed package version instead of + a separate hardcoded string in `cli.py` (which had drifted to `0.3.0`). The + version is read from package metadata, so it can no longer fall out of sync + with `pyproject.toml`. +- Corrected stale `pylings` references in the contributor guide (`CLAUDE.md`): + the smoke-test command, manual-testing flows, package paths, stylesheet, and + entry point now consistently use the `pythonlings` name the project adopted + in 0.3.0. (The unrelated `pylings` PyPI package belongs to a different + project; `pythonlings` is the only command this project ships.) + ## [0.3.0] - 2026-06-10 ### Added diff --git a/CLAUDE.md b/CLAUDE.md index dbf8214..e881eeb 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -13,21 +13,21 @@ python -m pytest -q # full suite python -m pytest tests/unit/test_runner.py -q # one file python -m pytest tests/unit/test_state.py::test_name -q # one test -pylings --root tests/fixtures/passing_curriculum verify # smoke-test: all solutions pass their checks +pythonlings --root tests/fixtures/passing_curriculum verify # smoke-test: all solutions pass their checks python -m build # sdist + wheel ``` -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/`). +Manual testing of flows: `pythonlings init --path ./learn-python` (create a learner workspace), `pythonlings` (TUI), `pythonlings run variables1`, `pythonlings dry-run variables1`, `pythonlings solution variables1`, `pythonlings hint`, `pythonlings list`, `pythonlings topics`, `pythonlings reset`. `--root` points any command at an arbitrary workspace (used heavily by tests against `tests/fixtures/`). ## Architecture Two distinct trees in this repo: -1. **The application** — `pylings/` (installable package) +1. **The application** — `pythonlings/` (installable package) 2. **The curriculum** — repo-root `exercises/`, `checks/`, `solutions/`, and `info.toml` -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 `/.pylings/state.json` (written atomically, with `.bak` recovery on corruption). +At build time, hatch `force-include` maps the curriculum into the wheel as `pythonlings/curriculum/` (see `pyproject.toml`). `pythonlings init` copies that bundled curriculum into a self-contained learner workspace; progress lives in `/.pythonlings/state.json` (written atomically, with `.bak` recovery on corruption). ### Curriculum model @@ -38,7 +38,7 @@ Each exercise is a triple that must stay in sync, plus a manifest entry: - `solutions/.py` — reference answer - `info.toml` — ordered `[[exercises]]` entries with `name`, `path`, `hint`, `docs` URL; this file is the source of truth for exercise order and topics -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`). +When changing curriculum, update all four, then run the relevant tests plus `pythonlings --root tests/fixtures/passing_curriculum verify`. Exercise names are topic + ordinal (`variables1`, `collections10`). ### How checks run (`core/runner.py`) @@ -47,8 +47,8 @@ An exercise passes when a generated runner script `exec()`s the exercise source ### Layering - `pythonlings/core/` — all filesystem, manifest, state, reset, solutions, and runner logic. No UI imports. (Checks rerun on a debounce in the TUI editor, not a filesystem watcher.) -- `pylings/screens/` and `pylings/widgets/` — Textual UI only; `pylings/app.py` wires them up; `pylings.tcss` holds styles. -- `pylings/cli.py` — argparse subcommands; entry point `pylings = "pylings.cli:main"`. +- `pythonlings/screens/` and `pythonlings/widgets/` — Textual UI only; `pythonlings/app.py` wires them up; `pythonlings.tcss` holds styles. +- `pythonlings/cli.py` — argparse subcommands; entry point `pythonlings = "pythonlings.cli:main"`. 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/`). diff --git a/Readme.md b/Readme.md index cdbcfca..b750e61 100644 --- a/Readme.md +++ b/Readme.md @@ -27,7 +27,7 @@ cd learn-python && uvx pythonlings How it works: **edit** the broken exercise in the built-in editor → checks rerun as you type and advance you to the next one. That's the whole loop. -Status: `v0.3.0`, alpha — published on PyPI as `pythonlings`. +Status: `v0.3.1`, alpha — published on PyPI as `pythonlings`. ![Coding screen](docs/assets/screenshots/coding-screen.png) diff --git a/pyproject.toml b/pyproject.toml index 1ff9083..924dc03 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "pythonlings" -version = "0.3.0" +version = "0.3.1" description = "Python learnings, Rustlings-style, in a terminal TUI." readme = "Readme.md" requires-python = ">=3.9" diff --git a/pythonlings/cli.py b/pythonlings/cli.py index 0001a69..9ffccb1 100644 --- a/pythonlings/cli.py +++ b/pythonlings/cli.py @@ -3,9 +3,13 @@ import argparse import sys +from importlib.metadata import PackageNotFoundError, version as _package_version from pathlib import Path -__version__ = "0.3.0" +try: + __version__ = _package_version("pythonlings") +except PackageNotFoundError: # running from a source checkout without an install + __version__ = "0.0.0+unknown" def _build_parser() -> argparse.ArgumentParser: