From f0a1a58aa1895513b3ca08b8136eafab5f67a560 Mon Sep 17 00:00:00 2001 From: Thor Whalen <1906276+thorwhalen@users.noreply.github.com> Date: Mon, 3 Aug 2026 23:57:38 +0100 Subject: [PATCH 1/5] fix: make TemplateWrapper doctest deterministic `get_dependencies()` returns a set, and the docstring compared it against a set literal `{'user', 'years'}`. Set repr order for strings depends on PYTHONHASHSEED, so the example passed or failed at random (verified: fails on seeds 0/2/3/4, passes on 1/5). Compare `sorted(...)` against a list instead. This was invisible until now because `testpaths` did not include the package directory, so CI collected zero package doctests. Claude-Session: https://claude.ai/code/session_01VipiLaG4xy7WctqY9w2475 --- embody/base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/embody/base.py b/embody/base.py index 9f71670..00b53d1 100644 --- a/embody/base.py +++ b/embody/base.py @@ -167,8 +167,8 @@ class TemplateWrapper: Examples: >>> template = TemplateWrapper({'name': '${user}', 'age': '${years}'}) - >>> template.get_dependencies() - {'user', 'years'} + >>> sorted(template.get_dependencies()) + ['user', 'years'] """ def __init__( From fc012abcd0af9eb43ded16d119061fe21b4bcaa1 Mon Sep 17 00:00:00 2001 From: Thor Whalen <1906276+thorwhalen@users.noreply.github.com> Date: Mon, 3 Aug 2026 23:58:31 +0100 Subject: [PATCH 2/5] test: widen testpaths so the package's doctests actually run in CI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CI runs `pytest --doctest-modules` with no path argument, so collection is driven entirely by `testpaths`. With `testpaths = ["tests"]` the 58 doctests in the `embody` package were never collected — CI reported green while doctesting nothing. Adding the package dir takes collection from 14 items to 72 (58 package doctests, previously zero). All pass. Also pin `exclude_paths` to `embody/scrap`: wads' default exclude of "scrap" only matches a top-level directory, and `scrap/` is throwaway code that ruff already excludes and that should not gate a release. Claude-Session: https://claude.ai/code/session_01VipiLaG4xy7WctqY9w2475 --- pyproject.toml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e5f417e..808612f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,7 +48,15 @@ convention = "google" "**/examples/*" = ["D"] "**/scrap/*" = ["D"] +[tool.wads.ci.testing] +# `scrap/` is throwaway code (already excluded from ruff); wads' default +# exclude of "scrap" only matches a top-level dir, so name the real path. +exclude_paths = ["examples", "embody/scrap"] + [tool.pytest.ini_options] minversion = "6.0" -testpaths = ["tests"] +# CI runs `pytest --doctest-modules` with no path argument, so collection is +# driven entirely by testpaths. The package dir must be listed here or none of +# its doctests ever run. +testpaths = ["tests", "embody"] doctest_optionflags = ["NORMALIZE_WHITESPACE", "ELLIPSIS"] From d46ac90d7ebdbd87ebb7239881c4996b69533438 Mon Sep 17 00:00:00 2001 From: Thor Whalen <1906276+thorwhalen@users.noreply.github.com> Date: Tue, 4 Aug 2026 00:00:09 +0100 Subject: [PATCH 3/5] chore: modernise PyPI metadata (SPDX license, classifiers, keywords, urls) - `license`: deprecated `[project.license] text = ...` table -> PEP 639 SPDX string `license = "Apache-2.0"` (matches the LICENSE file), plus explicit `license-files = ["LICENSE"]`. Built metadata is now Metadata-Version 2.4 with `License-Expression: Apache-2.0`. - `classifiers`: added an accurate set. Development Status is 3 - Alpha (0.1.x, API still settling) - deliberately not promoted. No `License :: OSI Approved` classifier: under PEP 639 it contradicts the SPDX expression and newer build backends reject the combination. - `keywords`: filled in (was empty) from what the package actually does. - `[project.urls]`: added Documentation (the live GitHub Pages site), Repository and Issues alongside the existing Homepage. - README: the licence section said "MIT License" while LICENSE, setup.cfg and pyproject all say Apache-2.0. Corrected to Apache-2.0. Verified: `uv build` succeeds, `twine check` PASSED on both sdist and wheel, all classifiers validate against the canonical trove-classifiers list. Claude-Session: https://claude.ai/code/session_01VipiLaG4xy7WctqY9w2475 --- README.md | 2 +- pyproject.toml | 31 +++++++++++++++++++++++++++---- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 89b3895..66b62ef 100644 --- a/README.md +++ b/README.md @@ -243,4 +243,4 @@ Contributions are welcome! Please see our [contributing guidelines](CONTRIBUTING ## License -MIT License - see [LICENSE](LICENSE) file for details. +Apache License 2.0 - see [LICENSE](LICENSE) file for details. diff --git a/pyproject.toml b/pyproject.toml index 808612f..c330a85 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,15 +8,38 @@ version = "0.1.9" description = "Generate templated objects" readme = "README.md" requires-python = ">=3.10" -keywords = [] +license = "Apache-2.0" +license-files = ["LICENSE"] +keywords = [ + "templating", + "template", + "object-generation", + "nested-data", + "json-pointer", + "path-addressing", + "mapping", + "configuration", +] +classifiers = [ + "Development Status :: 3 - Alpha", + "Intended Audience :: Developers", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Topic :: Software Development :: Libraries :: Python Modules", + "Topic :: Software Development :: Code Generators", +] authors = [{ name = "Thor Whalen" }] dependencies = ["dol"] -[project.license] -text = "Apache-2.0" - [project.urls] Homepage = "https://github.com/i2mint/embody" +Documentation = "https://i2mint.github.io/embody/" +Repository = "https://github.com/i2mint/embody" +Issues = "https://github.com/i2mint/embody/issues" [project.optional-dependencies] dev = ["pytest>=7.0", "pytest-cov>=4.0", "ruff>=0.1.0"] From 7ba19a8adaed85a73d23ee8380863384264b4529 Mon Sep 17 00:00:00 2001 From: Thor Whalen <1906276+thorwhalen@users.noreply.github.com> Date: Tue, 4 Aug 2026 00:00:24 +0100 Subject: [PATCH 4/5] chore: add standard .editorconfig wads templates ship one; this repo predates that. Copied verbatim from a healthy repo in the fleet so editor behaviour (charset, EOL, final newline, trailing whitespace, indent) is consistent across contributors and matches the rest of the ecosystem. No existing file is reformatted by this commit. Claude-Session: https://claude.ai/code/session_01VipiLaG4xy7WctqY9w2475 --- .editorconfig | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..88bf4d0 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,17 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.{py,toml,yml,yaml}] +indent_style = space +indent_size = 4 + +[*.md] +trim_trailing_whitespace = false + +[Makefile] +indent_style = tab From f2548e92d643da6cc48038f03cfb217edfe338c6 Mon Sep 17 00:00:00 2001 From: Thor Whalen <1906276+thorwhalen@users.noreply.github.com> Date: Fri, 7 Aug 2026 09:25:21 +0100 Subject: [PATCH 5/5] fix: correct the Windows branch of the mk_pattern doctest The POSIX assertion used a plain string literal and the Windows one a raw literal, so the same source text meant two backslashes in one and four in the other. The pattern only ever has two, so the Windows leg asserted against a regex that could never be built. Only visible now that testpaths collects these doctests. Claude-Session: https://claude.ai/code/session_01Uz2YYxAfa1DX8UcCDmeKC5 --- embody/naming.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/embody/naming.py b/embody/naming.py index 3cfb242..944e9eb 100644 --- a/embody/naming.py +++ b/embody/naming.py @@ -311,7 +311,7 @@ def mk_pattern_from_template_and_format_dict(template, format_dict=None, sep=pat ... assert p == re.compile('(?P[^/]+)/and/(?P[^/]+)') >>> p = mk_pattern_from_template_and_format_dict('{here}/and/{there}', {'there': r'\d+'}) >>> if os.name == 'nt': # for windows - ... assert p == re.compile(r'(?P[^\\\\]+)/and/(?P\d+)') + ... assert p == re.compile(r'(?P[^\\]+)/and/(?P\d+)') ... else: ... assert p == re.compile(r'(?P[^/]+)/and/(?P\d+)') >>> type(p)