From 2eb915261c7b7f94017ab48c9f3ef3b913e92063 Mon Sep 17 00:00:00 2001 From: meqdaddev Date: Sat, 12 Sep 2026 21:30:33 +0300 Subject: [PATCH] Update docs for today's merges; add semver and release-process rules Documentation: - CHANGELOG.md (and its mirror, docs/changelog.md) now covers the nested-Sequential .h5 loading fix and the Windows/Pillow annotation fixes merged today (PRs #13, #14, #16), not just the DepthwiseConv2D compat fix. - README.md / docs/index.md "Compatibility" sections updated to mention both new fixes. - docs/index.md's table of contents was missing links to the Changelog and Contributing pages despite both being in the mkdocs nav; added, plus a new Releasing page. Versioning/release process: - Added docs/releasing.md: semantic versioning rules (what bumps MAJOR vs MINOR vs PATCH for this package specifically), changelog discipline (add an entry in the same PR as the change), and a step-by-step release checklist (bump version, tag, GitHub Release, which triggers the existing publish.yml to PyPI). Linked from mkdocs.yml's nav and from docs/contribution.md. - Fixed a real version-tracking bug found while writing that doc: src/__init__.py's __version__ said "1.2" while setup.py separately hardcoded "1.3.0" -- neither of which had actually been released (the latest tag is v1.2). setup.py now reads the version from src/__init__.py instead of duplicating it, so there's exactly one place to bump per release. Verified: `python setup.py --version` resolves to 1.3.0 via the new single source of truth, `mkdocs build --strict` succeeds with no warnings, and classify_image.py in the tm-example test project still runs correctly end to end. --- CHANGELOG.md | 2 ++ README.md | 4 ++- docs/changelog.md | 2 ++ docs/contribution.md | 5 +++ docs/index.md | 12 ++++--- docs/releasing.md | 84 ++++++++++++++++++++++++++++++++++++++++++++ mkdocs.yml | 1 + setup.py | 19 +++++++++- src/__init__.py | 2 +- 9 files changed, 124 insertions(+), 7 deletions(-) create mode 100644 docs/releasing.md diff --git a/CHANGELOG.md b/CHANGELOG.md index c687269..fde78f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ All notable changes to this project are documented in this file. ### Fixed - **TensorFlow/Keras 3 compatibility**: Teachable Machine's exported `.h5` models embed a legacy `DepthwiseConv2D` layer config (`'groups': 1`) that current Keras (Keras 3, bundled by default since TensorFlow 2.16) rejects during deserialization, causing model loading to fail with `TypeError`/`ValueError: Unrecognized keyword arguments passed to DepthwiseConv2D: {'groups': 1}`. `TeachableMachine` now loads models with a compatibility shim that discards the unused key, so exported models work on up-to-date TensorFlow/Keras installs without pinning an old TensorFlow version. Fixes [#2](https://github.com/MeqdadDev/teachable-machine/issues/2). +- **Nested-Sequential `.h5` exports fail to load under Keras 3**: some Teachable Machine `.h5` exports save the model as a `Sequential` wrapping nested `Sequential`/`Functional` submodels. Keras 3's legacy H5 loader mis-rebuilds that shape (a `Functional` submodel's single output comes back as a length-1 list, which the next layer misreads as two inputs), previously surfacing as a misleading `FileNotFoundError: Model file not found`. `TeachableMachine` now falls back to a manual reconstruction that flattens the nested wrappers, rebuilds the graph via the Functional API, and restores weights by matching each layer's name against the saved weight paths. +- **Prediction annotation crashes on Windows / recent Pillow**: `show_prediction_on_image()` used `ImageFont.truetype("DejaVuSans-Bold.ttf", ...)`, not resolvable by bare name on Windows (`OSError`), and `ImageDraw.textsize()`, removed in Pillow 10+. Both are now handled: a `ImageFont.load_default()` fallback when the named font can't be found, and `textbbox()` in place of `textsize()`. Also fixed the highlight box behind the prediction label clipping text descenders (e.g. "g", "%") by measuring the box at the actual draw position instead of reusing an origin-measured extent. ### Changed diff --git a/README.md b/README.md index ff0b36b..e9bd333 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,9 @@ Teachable Machine's exported `.h5` models embed a legacy `DepthwiseConv2D` layer TypeError: Unrecognized keyword arguments passed to DepthwiseConv2D: {'groups': 1} ``` -Since `v1.3.0`, this package patches the model loader so exported models load correctly on up-to-date TensorFlow/Keras installs, with no need to pin an old TensorFlow version. See [issue #2](https://github.com/MeqdadDev/teachable-machine/issues/2) for background. +Some exports also save the model as a `Sequential` wrapping nested `Sequential`/`Functional` submodels, a shape Keras 3's legacy H5 loader mis-rebuilds, which previously surfaced as a misleading `FileNotFoundError: Model file not found`. + +Since `v1.3.0`, this package patches the model loader to handle both cases, so exported models load correctly on up-to-date TensorFlow/Keras installs, with no need to pin an old TensorFlow version. It also fixes prediction-annotation crashes on Windows / recent Pillow versions (`show_prediction_on_image`). See [issue #2](https://github.com/MeqdadDev/teachable-machine/issues/2) and the [changelog](https://meqdaddev.github.io/teachable-machine/changelog/) for background. ## Requirements diff --git a/docs/changelog.md b/docs/changelog.md index c687269..fde78f1 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -7,6 +7,8 @@ All notable changes to this project are documented in this file. ### Fixed - **TensorFlow/Keras 3 compatibility**: Teachable Machine's exported `.h5` models embed a legacy `DepthwiseConv2D` layer config (`'groups': 1`) that current Keras (Keras 3, bundled by default since TensorFlow 2.16) rejects during deserialization, causing model loading to fail with `TypeError`/`ValueError: Unrecognized keyword arguments passed to DepthwiseConv2D: {'groups': 1}`. `TeachableMachine` now loads models with a compatibility shim that discards the unused key, so exported models work on up-to-date TensorFlow/Keras installs without pinning an old TensorFlow version. Fixes [#2](https://github.com/MeqdadDev/teachable-machine/issues/2). +- **Nested-Sequential `.h5` exports fail to load under Keras 3**: some Teachable Machine `.h5` exports save the model as a `Sequential` wrapping nested `Sequential`/`Functional` submodels. Keras 3's legacy H5 loader mis-rebuilds that shape (a `Functional` submodel's single output comes back as a length-1 list, which the next layer misreads as two inputs), previously surfacing as a misleading `FileNotFoundError: Model file not found`. `TeachableMachine` now falls back to a manual reconstruction that flattens the nested wrappers, rebuilds the graph via the Functional API, and restores weights by matching each layer's name against the saved weight paths. +- **Prediction annotation crashes on Windows / recent Pillow**: `show_prediction_on_image()` used `ImageFont.truetype("DejaVuSans-Bold.ttf", ...)`, not resolvable by bare name on Windows (`OSError`), and `ImageDraw.textsize()`, removed in Pillow 10+. Both are now handled: a `ImageFont.load_default()` fallback when the named font can't be found, and `textbbox()` in place of `textsize()`. Also fixed the highlight box behind the prediction label clipping text descenders (e.g. "g", "%") by measuring the box at the actual draw position instead of reusing an origin-measured extent. ### Changed diff --git a/docs/contribution.md b/docs/contribution.md index 504d6cb..80fabd7 100644 --- a/docs/contribution.md +++ b/docs/contribution.md @@ -6,6 +6,11 @@ Pull requests are welcome. For major changes, please open an issue first to disc For new features, please make sure you've tested your code in real environment. And to avoid duplications, please take a sneak peek on the project structure and practices before making your PR. +If your PR includes a user-facing fix or feature, add an entry to +[`CHANGELOG.md`](https://github.com/MeqdadDev/teachable-machine/blob/main/CHANGELOG.md) +as part of it — see [Releasing](releasing.md) for the versioning and +changelog conventions this project follows. + For more details and questions, contact me via links below. ### 🔗 Contact me diff --git a/docs/index.md b/docs/index.md index 7376c8a..74c6e59 100644 --- a/docs/index.md +++ b/docs/index.md @@ -21,9 +21,11 @@ Source Code is published on [GitHub](https://github.com/MeqdadDev/teachable-mach 1. [How-To Guide](how-to-guide.md) 2. [Requirements](requirements.md) -2. [Code Examples](codeExamples.md) -3. [Explanation](explanation.md) -4. [Contributing](contribution.md) +3. [Code Examples](codeExamples.md) +4. [Explanation](explanation.md) +5. [Changelog](changelog.md) +6. [Contributing](contribution.md) +7. [Releasing](releasing.md) ## Supported Classifiers @@ -37,7 +39,9 @@ Teachable Machine's exported `.h5` models embed a legacy `DepthwiseConv2D` layer TypeError: Unrecognized keyword arguments passed to DepthwiseConv2D: {'groups': 1} ``` -Since `v1.3.0`, this package patches the model loader so exported models load correctly on up-to-date TensorFlow/Keras installs, with no need to pin an old TensorFlow version. See [issue #2](https://github.com/MeqdadDev/teachable-machine/issues/2) for background. +Some exports also save the model as a `Sequential` wrapping nested `Sequential`/`Functional` submodels, a shape Keras 3's legacy H5 loader mis-rebuilds, which previously surfaced as a misleading `FileNotFoundError: Model file not found`. + +Since `v1.3.0`, this package patches the model loader to handle both cases, so exported models load correctly on up-to-date TensorFlow/Keras installs, with no need to pin an old TensorFlow version. It also fixes prediction-annotation crashes on Windows / recent Pillow versions (`show_prediction_on_image`). See [issue #2](https://github.com/MeqdadDev/teachable-machine/issues/2) and the [changelog](changelog.md) for background. ## Links diff --git a/docs/releasing.md b/docs/releasing.md new file mode 100644 index 0000000..9ba3137 --- /dev/null +++ b/docs/releasing.md @@ -0,0 +1,84 @@ +## Versioning + +This project follows [Semantic Versioning](https://semver.org/) (`MAJOR.MINOR.PATCH`): + +- **MAJOR** — a breaking change: a public method/parameter is removed or its + behavior changes incompatibly, the minimum supported Python version is + raised, or a dependency's required version range changes in a way that + drops support for environments that previously worked. +- **MINOR** — a backwards-compatible addition: a new method, parameter, or + supported model/classifier type. +- **PATCH** — a backwards-compatible bug fix: a crash, incorrect result, or + compatibility break (e.g. with a newer TensorFlow/Keras/Pillow release) is + fixed without changing the public API. + +When a change could plausibly be read as more than one of these (e.g. a bug +fix that also happens to add a new optional parameter), pick the highest +level that applies. + +### Where the version lives + +The version is defined in exactly one place: `__version__` in +[`src/__init__.py`](https://github.com/MeqdadDev/teachable-machine/blob/main/src/__init__.py). +`setup.py` reads it from there at build time — don't hardcode a version +in `setup.py` or anywhere else, and don't bump it in more than one file. +(This package previously had the version duplicated between `setup.py` +and `src/__init__.py`, and the two drifted out of sync — `setup.py` said +`1.3.0` while `src/__init__.py` still said `1.2`, a version that was never +actually shipped as `v1.3.0` in a release. Keeping a single source of +truth is what this rule exists to prevent.) + +## Changelog + +Every user-facing change (fix, feature, or breaking change) is recorded in +[`CHANGELOG.md`](https://github.com/MeqdadDev/teachable-machine/blob/main/CHANGELOG.md) +at the repo root, mirrored verbatim to `docs/changelog.md` (the two files +must stay identical — that's what the docs site's Changelog page renders). + +- Add the entry **in the same PR** as the change, under the version header + for the *next* release (create that header, e.g. `## [1.4.0]`, if the PR + is the first change since the last release). +- Group entries under `### Added`, `### Changed`, `### Fixed`, or + `### Removed`, following [Keep a Changelog](https://keepachangelog.com/). +- Link the relevant GitHub issue/PR where useful. +- Once that version is actually released (see below), add the release date + next to its header, e.g. `## [1.3.0] - 2026-09-12`. + +## Release process + +1. **Confirm the changelog is accurate.** The unreleased version's section + at the top of `CHANGELOG.md` (and `docs/changelog.md`) should already + describe everything merged since the last release, since each PR added + its own entry — read through it and fix anything missing before + continuing. +2. **Confirm the version bump matches semver** (see above) given everything + in that section. Adjust the header if the accumulated changes turned out + to need a higher bump than originally expected (e.g. a breaking change + landed alongside what were otherwise just fixes). +3. **Bump `__version__`** in `src/__init__.py` to that version. +4. **Add the release date** to the changelog header, e.g. + `## [1.4.0] - 2026-09-12`. +5. Commit both (e.g. `Release v1.4.0`) directly to `main` (or via a small + PR, same as any other change) once everything above is in place. +6. **Tag and push:** + ```bash + git tag v1.4.0 + git push origin v1.4.0 + ``` +7. **Create a GitHub Release from that tag** (Releases -> Draft a new + release -> pick the tag). Use the changelog section for that version as + the release notes. Publishing the release triggers + [`publish.yml`](https://github.com/MeqdadDev/teachable-machine/blob/main/.github/workflows/publish.yml), + which builds the package and publishes it to PyPI via + [Trusted Publishing](https://docs.pypi.org/trusted-publishers/) (no + token needed) — this only works once a trusted publisher is registered + for this repo/workflow on the [PyPI project's publishing settings](https://pypi.org/manage/project/teachable_machine/settings/publishing/). +8. **Verify:** + - The [PyPI project page](https://pypi.org/project/teachable-machine/) + shows the new version. + - `pip install --upgrade teachable-machine` picks it up. + - The [docs site](https://meqdaddev.github.io/teachable-machine/) — which + [redeploys automatically](https://github.com/MeqdadDev/teachable-machine/blob/main/.github/workflows/docs.yml) + on every push to `main` that touches `docs/`, `src/`, or `mkdocs.yml` — + reflects the release (Changelog page, and any docstring changes via + `mkdocstrings` on the Explanation page). diff --git a/mkdocs.yml b/mkdocs.yml index fd63dfd..18c76ec 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -14,3 +14,4 @@ nav: - Explanation: explanation.md - Changelog: changelog.md - Contributing: contribution.md + - Releasing: releasing.md diff --git a/setup.py b/setup.py index 509d80a..1645f2c 100644 --- a/setup.py +++ b/setup.py @@ -1,11 +1,28 @@ +import re +from pathlib import Path + from setuptools import setup with open("README.md", "r") as fh: long_description = fh.read() + +def _read_version(): + """ + Read `__version__` from src/__init__.py so the package version has a + single source of truth instead of being duplicated (and drifting out + of sync, as it previously did) between here and there. + """ + init_contents = (Path(__file__).parent / "src" / "__init__.py").read_text() + match = re.search(r'^__version__\s*=\s*["\']([^"\']+)["\']', init_contents, re.M) + if not match: + raise RuntimeError("Unable to find __version__ in src/__init__.py") + return match.group(1) + + setup( name="teachable_machine", - version="1.3.0", + version=_read_version(), description="A Python package designed to simplify the integration of exported models from Google's Teachable Machine platform into various environments. \ This tool was specifically crafted to work seamlessly with Teachable Machine, making it easier to implement and use your trained models.", py_modules=["teachable_machine"], diff --git a/src/__init__.py b/src/__init__.py index bde846c..40b6f1f 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -2,7 +2,7 @@ __author__ = "MeqdadDev" -__version__ = "1.2" +__version__ = "1.3.0" __doc__ = """ Teachable Machine