From 4aeed64713c175f84cf0acaa0fac07bc7432984f Mon Sep 17 00:00:00 2001 From: Paul Fremantle Date: Sun, 30 Aug 2026 09:48:01 +0100 Subject: [PATCH 1/2] fix: declare templates/thumbnail extras and re-lock on release MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two follow-ups from the 0.6.2 click fix. render_template() and gcode_thumbnail() point users at `pip install bambox[templates]` / `[thumbnail]`, but neither extra existed — jinja2 and pillow were dev-only, so the hint installed nothing. Declare both extras, give gcode_thumbnail() the same actionable ImportError render_template() already had instead of a bare ModuleNotFoundError, and gate it with a metadata test so a hint can never again name an extra that does not exist. Prepare Release bumped pyproject.toml but never re-locked, so uv.lock drifted behind every release (it still said 0.4.7 at 0.6.1). Re-lock after the bump, verify the lock reports the target version, and commit it with the release. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_011aB9rCzbxyCrr7ZcXEN7g5 --- .github/workflows/prepare-release.yml | 7 ++++++- README.md | 13 +++++++++++++ changes/+optional-extras.bugfix | 1 + changes/+release-lock-sync.misc | 1 + pyproject.toml | 6 ++++++ src/bambox/thumbnail.py | 8 +++++++- tests/test_release_readiness.py | 16 ++++++++++++++++ uv.lock | 12 ++++++++++-- 8 files changed, 60 insertions(+), 4 deletions(-) create mode 100644 changes/+optional-extras.bugfix create mode 100644 changes/+release-lock-sync.misc diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml index b6dff81..9d83b0e 100644 --- a/.github/workflows/prepare-release.yml +++ b/.github/workflows/prepare-release.yml @@ -69,6 +69,11 @@ jobs: sed -i "s/^version = \".*\"/version = \"${VERSION}\"/" pyproject.toml grep "^version = \"${VERSION}\"" pyproject.toml + - name: Sync uv.lock to the new version + run: | + uv lock + grep -A1 '^name = "bambox"' uv.lock | grep "^version = \"${{ inputs.version }}\"" + - name: Build changelog with towncrier run: | VERSION="${{ inputs.version }}" @@ -87,7 +92,7 @@ jobs: BRANCH="release/v${VERSION}" git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" - git add pyproject.toml CHANGELOG.md changes/ + git add pyproject.toml uv.lock CHANGELOG.md changes/ git commit -m "Release v${VERSION}" git remote set-url origin "https://x-access-token:${GH_TOKEN}@github.com/${{ github.repository }}.git" git push --force origin "$BRANCH" diff --git a/README.md b/README.md index eb18b70..375f6ed 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,19 @@ Or with [uv](https://docs.astral.sh/uv/): ```bash uv pip install bambox +uv tool install bambox # just the CLI +``` + +The core install covers packing, repacking and validation. Two optional +extras pull in heavier dependencies only if you need them: + +| Extra | Pulls in | Needed for | +|-------|----------|------------| +| `templates` | Jinja2 | `bambox.templates.render_template()` — rendering bundled G-code templates | +| `thumbnail` | Pillow | `bambox.thumbnail.gcode_thumbnail()` — rendering toolpath previews | + +```bash +pip install "bambox[templates,thumbnail]" ``` ### Supported printers and filaments diff --git a/changes/+optional-extras.bugfix b/changes/+optional-extras.bugfix new file mode 100644 index 0000000..7466642 --- /dev/null +++ b/changes/+optional-extras.bugfix @@ -0,0 +1 @@ +Declare the `templates` (Jinja2) and `thumbnail` (Pillow) extras that `render_template()` and `gcode_thumbnail()` already told users to install, and give `gcode_thumbnail()` the same actionable error as `render_template()` when Pillow is missing. diff --git a/changes/+release-lock-sync.misc b/changes/+release-lock-sync.misc new file mode 100644 index 0000000..e3f69a2 --- /dev/null +++ b/changes/+release-lock-sync.misc @@ -0,0 +1 @@ +Prepare Release now re-locks `uv.lock` with the new version, so the lockfile no longer drifts behind `pyproject.toml` after each release. diff --git a/pyproject.toml b/pyproject.toml index b3155f5..5e0fa1c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,6 +29,12 @@ classifiers = [ ] [project.optional-dependencies] +templates = [ + "jinja2>=3.1.0", +] +thumbnail = [ + "pillow>=10.0.0", +] dev = [ "pytest>=8.0.0", "pytest-cov>=6.0.0", diff --git a/src/bambox/thumbnail.py b/src/bambox/thumbnail.py index 5d8876c..3475f7a 100644 --- a/src/bambox/thumbnail.py +++ b/src/bambox/thumbnail.py @@ -16,7 +16,13 @@ def gcode_thumbnail( Parses G0/G1 extrusion moves from the print body (skipping startup G-code) and draws them on a dark background. Returns PNG bytes. """ - from PIL import Image, ImageDraw + try: + from PIL import Image, ImageDraw + except ImportError: + raise ImportError( + "Pillow is required for thumbnail rendering. " + "Install with: pip install bambox[thumbnail]" + ) if isinstance(gcode, bytes): gcode = gcode.decode(errors="replace") diff --git a/tests/test_release_readiness.py b/tests/test_release_readiness.py index 261a0d6..f599702 100644 --- a/tests/test_release_readiness.py +++ b/tests/test_release_readiness.py @@ -171,3 +171,19 @@ def test_notices_force_included_in_wheel(self) -> None: "THIRD-PARTY-NOTICES should be placed under bambox/ in the wheel " "so it's discoverable next to the installed package." ) + + +# --------------------------------------------------------------------------- +# 5. Optional extras named in ImportError hints must exist +# --------------------------------------------------------------------------- + + +class TestOptionalExtras: + """`pip install bambox[]` hints must point at a real extra.""" + + @pytest.mark.parametrize("extra", ["templates", "thumbnail"]) + def test_extra_is_declared(self, extra: str) -> None: + from importlib.metadata import metadata + + declared = metadata("bambox").get_all("Provides-Extra") or [] + assert extra in declared diff --git a/uv.lock b/uv.lock index 8872935..5d0945a 100644 --- a/uv.lock +++ b/uv.lock @@ -13,7 +13,7 @@ wheels = [ [[package]] name = "bambox" -version = "0.6.1" +version = "0.6.2" source = { editable = "." } dependencies = [ { name = "rich" }, @@ -33,13 +33,21 @@ dev = [ docs = [ { name = "pdoc" }, ] +templates = [ + { name = "jinja2" }, +] +thumbnail = [ + { name = "pillow" }, +] [package.metadata] requires-dist = [ { name = "jinja2", marker = "extra == 'dev'", specifier = ">=3.1.0" }, + { name = "jinja2", marker = "extra == 'templates'", specifier = ">=3.1.0" }, { name = "mypy", marker = "extra == 'dev'", specifier = ">=1.10.0" }, { name = "pdoc", marker = "extra == 'docs'", specifier = ">=14.0.0" }, { name = "pillow", marker = "extra == 'dev'", specifier = ">=10.0.0" }, + { name = "pillow", marker = "extra == 'thumbnail'", specifier = ">=10.0.0" }, { name = "pytest", marker = "extra == 'dev'", specifier = ">=8.0.0" }, { name = "pytest-cov", marker = "extra == 'dev'", specifier = ">=6.0.0" }, { name = "rich", specifier = ">=13.0" }, @@ -47,7 +55,7 @@ requires-dist = [ { name = "towncrier", marker = "extra == 'dev'", specifier = ">=24.7.0" }, { name = "typer", specifier = ">=0.12" }, ] -provides-extras = ["dev", "docs"] +provides-extras = ["templates", "thumbnail", "dev", "docs"] [[package]] name = "click" From 565f1193f00a5f1de2b8d777e74e548e7262ddf3 Mon Sep 17 00:00:00 2001 From: Paul Fremantle Date: Sun, 30 Aug 2026 09:48:23 +0100 Subject: [PATCH 2/2] misc: number changelog fragments for #274 Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_011aB9rCzbxyCrr7ZcXEN7g5 --- changes/{+optional-extras.bugfix => 274.bugfix} | 0 changes/{+release-lock-sync.misc => 274.misc} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename changes/{+optional-extras.bugfix => 274.bugfix} (100%) rename changes/{+release-lock-sync.misc => 274.misc} (100%) diff --git a/changes/+optional-extras.bugfix b/changes/274.bugfix similarity index 100% rename from changes/+optional-extras.bugfix rename to changes/274.bugfix diff --git a/changes/+release-lock-sync.misc b/changes/274.misc similarity index 100% rename from changes/+release-lock-sync.misc rename to changes/274.misc