Skip to content

feat: add sync-pyproject target to non-destructively patch pyproject.toml via template.yml#838

Draft
Copilot wants to merge 5 commits intomainfrom
copilot/add-pyproject-section-support
Draft

feat: add sync-pyproject target to non-destructively patch pyproject.toml via template.yml#838
Copilot wants to merge 5 commits intomainfrom
copilot/add-pyproject-section-support

Conversation

Copy link
Contributor

Copilot AI commented Mar 13, 2026

Summary

Adds a make sync-pyproject target and .rhiza/utils/sync_pyproject.py utility that non-destructively patches selected pyproject.toml fields declared in a new optional pyproject: section of .rhiza/template.yml. Project-owned fields (name, version, dependencies, etc.) are never touched.

Changes

  • .rhiza/utils/sync_pyproject.py — New stdlib+tomlkit utility that reads pyproject: from .rhiza/template.yml and patches pyproject.toml in-place with comment/formatting preservation. Supports five controlled keys:
    • requires-python → sets [project].requires-python
    • classifiers → replaces [project].classifiers wholesale
    • license → sets [project].license; accepts a plain string ("MIT", PEP 639) or a mapping ({text: "MIT"} / {file: "LICENSE"}, PEP 517 inline table)
    • readme → sets [project].readme (string file path, e.g. "README.md")
    • tool-sections → syncs named [tool.*] subtrees from rhiza's own pyproject.toml
  • .rhiza/make.d/releasing.mk — New sync-pyproject target (alongside bump); supports DRY_RUN=1 and CHECK=1 (CI-friendly exit-nonzero-on-drift)
  • .rhiza/template-bundles.yml — Added sync_pyproject.py to core bundle so it flows to all downstream projects
  • pyproject.toml / .rhiza/requirements/tools.txt — Added tomlkit>=0.13,<1.0 as dev dependency; added to tool.deptry.package_module_name_map
  • .rhiza/tests/deps/test_sync_pyproject.py — 27 tests covering: no-op, patching, field preservation, license (string and table forms), readme, --dry-run, --check
  • .rhiza/docs/WORKFLOWS.md — New pyproject.toml Field Synchronization section
  • .rhiza/docs/TEMPLATE_YML_EXAMPLE.md — Annotated example of the pyproject: section

Example downstream .rhiza/template.yml:

pyproject:
  requires-python: ">=3.11"
  license: "MIT"
  readme: "README.md"
  classifiers:
    - "Programming Language :: Python :: 3.11"
    - "Programming Language :: Python :: 3.12"
    - "License :: OSI Approved :: MIT License"
  tool-sections:
    - tool.deptry.package_module_name_map

Testing

  • make test passes locally
  • make fmt has been run
  • New tests added (or explain why not needed)

Checklist

  • Commit messages follow the Conventional Commits format
  • Documentation updated if behaviour changed
  • make deptry passes (no unused or missing dependencies)
Original prompt

Summary

Add the ability for rhiza to control selected fields in a downstream project's pyproject.toml via a new pyproject: section in .rhiza/template.yml. This is implemented entirely within rhiza (no rhiza-cli changes needed) using a Python utility script and a new make sync-pyproject target.


Background

Currently, pyproject.toml is completely excluded from rhiza's sync system — it is project-owned. However, several fields are naturally "rhiza-owned" and drift across downstream projects:

  • requires-python — should match rhiza's supported Python version matrix
  • classifiers — Python version classifiers should mirror .python-version / requires-python
  • [tool.*] sections like [tool.deptry.package_module_name_map] — currently duplicated manually

The idea: extend .rhiza/template.yml with an optional pyproject: section. A new make sync-pyproject target reads it and non-destructively patches pyproject.toml, preserving all project-specific fields (name, version, description, authors, dependencies, etc.).


Changes Required

1. New utility script: .rhiza/utils/sync_pyproject.py

A Python script (stdlib only — no tomlkit dep needed for the basic patch, uses tomllib to read and string-based TOML writing to patch) that:

  • Reads .rhiza/template.yml (using pyyaml, already a dev dep) and extracts the pyproject: section
  • Reads the existing pyproject.toml
  • Applies a non-destructive patch: only sets/replaces keys declared in the pyproject: section; never touches anything else
  • Writes back the updated pyproject.toml preserving structure as much as possible
  • Supports --dry-run flag to preview changes without writing
  • Supports --check flag (exits non-zero if changes would be made, for CI)
  • Prints a human-readable diff of what changed

The script should use tomlkit (which preserves comments and formatting) as the TOML write backend. Add tomlkit>=0.13,<1.0 to pyproject.toml's [dependency-groups].dev and to .rhiza/requirements/tools.txt.

Supported pyproject: keys in template.yml:

  • requires-python — sets [project].requires-python
  • classifiers — replaces [project].classifiers entirely (it's a list rhiza owns)
  • tool-sections — a list of dotted TOML paths (e.g. tool.deptry.package_module_name_map) that are synced wholesale from rhiza's own pyproject.toml into the downstream project's pyproject.toml

The script must:

  • Never touch name, version, description, authors, keywords, dependencies, [dependency-groups], [project.urls] unless explicitly listed
  • Print [INFO] No pyproject: section in template.yml, nothing to do. and exit 0 if the section is absent (graceful no-op)
  • Print [INFO] pyproject.toml is already up to date. if no changes are needed
  • Be callable standalone: uv run python .rhiza/utils/sync_pyproject.py [--dry-run] [--check]

2. New Makefile target in .rhiza/make.d/releasing.mk

Add a sync-pyproject target:

sync-pyproject: ## sync pyproject.toml fields from .rhiza/template.yml (supports DRY_RUN=1, CHECK=1)
	@if [ -f "pyproject.toml" ] && [ -f ".rhiza/template.yml" ]; then \
		$(MAKE) install; \
		_FLAGS=""; \
		if [ -n "$(DRY_RUN)" ]; then _FLAGS="$$_FLAGS --dry-run"; fi; \
		if [ -n "$(CHECK)" ]; then _FLAGS="$$_FLAGS --check"; fi; \
		${UV_BIN} run python .rhiza/utils/sync_pyproject.py $$_FLAGS; \
	else \
		printf "${YELLOW}[WARN] pyproject.toml or .rhiza/template.yml not found, skipping sync-pyproject${RESET}\n"; \
	fi

Add sync-pyproject to the .PHONY list in releasing.mk.

3. Update rhiza's own .rhiza/template.yml (create it — it doesn't exist in the repo itself)

Wait — rhiza skips sync on itself (the make sync target checks for jebel-quant/rhiza remote and skips). The .rhiza/template.yml file doesn't exist in the repo itself by design. Instead, add a well-documented example as .rhiza/docs/TEMPLATE_YML_EXAMPLE.md showing the new pyproject: section.

4. Update .rhiza/template-bundles.yml

Add .rhiza/utils/sync_pyproject.py to the core bundle's files list (so it syncs to all downstream projects):

      - .rhiza/utils/sync_pyproject.py

5. Update pyproject.toml in rhiza itself

Add tomlkit to [dependency-groups].dev:

"tomlkit>=0.13,<1.0",  # TOML read/write with comment preservation for sync_pyproject.py

Also add it to [tool.deptry.package_module_name_map]:

tomlkit = "tomlkit"

6. Update .rhiza/requirements/tools.txt

Add:

tomlkit>=0.13,<1.0

7. New test file: .rhiza/tests/deps/test_sync_pyproject.py

Add tests that verify:

  • sync_pyproject.py exists and is importable / syntax-valid
  • It is a no-op when pyproject: section is absent from template.yml
  • It correctly patches requires-python when specified
  • It correctly replaces classifiers when specified
  • It leaves name, version, description, `dependenc...

This pull request was created from Copilot chat.


📱 Kick off Copilot coding agent tasks wherever you are with GitHub Mobile, available on iOS and Android.

…matting)

Co-authored-by: tschm <2046079+tschm@users.noreply.github.com>
Copilot AI changed the title [WIP] Add ability for rhiza to control fields in pyproject.toml feat: add sync-pyproject target to non-destructively patch pyproject.toml via template.yml Mar 13, 2026
Copilot AI requested a review from tschm March 13, 2026 09:11
@tschm
Copy link
Member

tschm commented Mar 13, 2026

@copilot: can I have license and readme variables for pyproject section?

Copy link
Contributor Author

Copilot AI commented Mar 13, 2026

@copilot: can I have license and readme variables for pyproject section?

Done in commit d49c540. Both license and readme are now supported in the pyproject: section:

pyproject:
  license: "MIT"           # plain string (PEP 639)
  # or table form:
  # license:
  #   text: "MIT"
  readme: "README.md"

license handles both a plain string and an inline table ({text: "MIT"} / {file: "LICENSE"}). 7 new tests added covering both forms and no-op behaviour.

…toml via template.yml

Co-authored-by: tschm <2046079+tschm@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants