From e2e08d5d6361a9088650d4b4b89ec6559d504a3f Mon Sep 17 00:00:00 2001 From: Muhwezi Karen Emily Date: Tue, 16 Jun 2026 22:50:20 -0700 Subject: [PATCH 1/7] Add Phase II contribution README --- CONTRIBUTION.md | 217 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 217 insertions(+) create mode 100644 CONTRIBUTION.md diff --git a/CONTRIBUTION.md b/CONTRIBUTION.md new file mode 100644 index 000000000..9e9e8a7af --- /dev/null +++ b/CONTRIBUTION.md @@ -0,0 +1,217 @@ +# Contribution 1: Live Preview / Hot-Reloading for Copier Templates + +**Contribution Number:** 1 +**Student:** Karen Emily Muhwezi +**Issue:** https://github.com/copier-org/copier/issues/1451 +**Status:** Phase II In Progress + +--- + +## Why I Chose This Issue + +As someone with a Python background who has worked with ML models, I'm drawn +to tooling problems that slow developers down in real, tangible ways. This +issue describes a genuinely frustrating workflow — having to commit, push, and +run updates just to preview a template change — and proposes a solution that +would make the development experience significantly better. That kind of +quality-of-life improvement is something I find meaningful to work on because +the impact is immediately visible. + +This issue also aligns well with my learning goals. Copier is a Python +project, which plays to my existing strengths, but building a live preview +feature means I'll be working with file watching, CLI design, and real-time +rendering pipelines — areas I haven't worked in deeply before. I want to grow +my problem-solving skills by tackling something that has genuine complexity +underneath a simple-sounding description, and see that solution actually ship +in a tool that real developers use every day. + +--- + +## Understanding the Issue + +### Problem Description + +Copier currently has no way to preview how a template renders during +development. To see any changes, a developer must commit their changes, push +to GitHub, switch to a downstream project, run `copier update`, and observe +the output — then repeat the entire cycle for every fix. This makes template +development extremely slow and tedious. + +### Expected Behavior + +A template developer should be able to run a single command like +`copier preview` that renders their template locally into a temporary +directory and automatically re-renders it every time they save a change, +without any manual steps in between — similar to hot-reloading in React +development. + +### Current Behavior + +There is no preview or watch command in Copier. The only way to test template +changes is to go through the full commit, push, and `copier update` cycle on +a downstream project, which requires switching between multiple repositories +and running multiple commands every single time a change is made. + +### Affected Components + +- Copier's CLI layer — a new `preview` or `watch` command needs to be added +- Core template rendering pipeline in `copier/main.py` — the existing render + logic will be reused +- A new file watching component using the `watchfiles` library to detect + changes and trigger re-renders + +--- + +## Reproduction Process + +### Environment Setup + +Cloned fork from https://github.com/karenemily/codepath-copier.git + +Ran `python -m uv sync` and hit a network timeout trying to fetch `hatch_vcs`: +x Failed to build copier @ file:///C:/Users/Administrator/codepath-copier + +|-> Failed to resolve requirements from build-system.requires + +|-> No solution found when resolving: hatchling, hatch-vcs + +|-> Request failed after 3 retries in 84.0s + +|-> error sending request for url (https://files.pythonhosted.org/...) + +`-> operation timed out +This appears to be a network connectivity issue on the current machine rather +than a project configuration problem. Will retry on a stable network +connection. + +### Steps to Reproduce + +1. Create a Copier template locally +2. Make a change to the template +3. Attempt to preview how the change renders without pushing to GitHub +4. Observed result: No preview mechanism exists — must commit, push, and run + `copier update` on a downstream project to see any changes + +### Reproduction Evidence + +- **Commit showing reproduction:** [Link to commit in your fork] +- **Screenshots/logs:** Network timeout error documented above +- **My findings:** [What you discovered during reproduction] + +--- + +## Solution Approach + +### Analysis + +[Your analysis of the root cause - what's causing the issue?] + +### Proposed Solution + +[High-level description of your fix approach] + +### Implementation Plan + +Using UMPIRE framework (adapted): + +**Understand:** Template developers currently have no way to preview renders +locally without a full commit-push-update cycle. We need a live preview +command that watches for changes and re-renders automatically. + +**Match:** The existing `copier copy` and `copier update` commands in +`copier/main.py` show how rendering is triggered. The new command will reuse +the same rendering logic but wrap it in a file watcher loop. + +**Plan:** +1. Add `watchfiles` as a dependency in `pyproject.toml` +2. Create a new `preview` or `watch` function in `copier/main.py` that + accepts a template path and output path +3. On first call, render the template into a temporary directory +4. Start a `watchfiles` watcher on the template directory +5. On every detected change, re-render and show a diff of what changed +6. Expose the new function as a CLI command via the existing CLI layer +7. Add tests for the new command + +**Implement:** https://github.com/karenemily/codepath-copier/tree/fix-issue-live-preview + +**Review:** [Self-review checklist - does it follow the project's contribution +guidelines?] + +**Evaluate:** [What tests will confirm your fix works?] + +--- + +## Testing Strategy + +### Unit Tests + +- [ ] Test case 1: [Description] +- [ ] Test case 2: [Description] +- [ ] Test case 3: [Description] + +### Integration Tests + +- [ ] Integration scenario 1 +- [ ] Integration scenario 2 + +### Manual Testing + +[What you tested manually and results] + +--- + +## Implementation Notes + +### Week 1 Progress + +Environment setup attempted. Hit network timeout during `uv sync`. Branch +`fix-issue-live-preview` created and pushed. Codebase exploration in progress. + +### Week [Y] Progress + +[Continue documenting as you work] + +### Code Changes + +- **Files modified:** [List] +- **Key commits:** [Links to important commits] +- **Approach decisions:** [Why you chose certain approaches] + +--- + +## Pull Request + +**PR Link:** [GitHub PR URL when submitted] + +**PR Description:** [Draft or final PR description] + +**Maintainer Feedback:** +- [Date]: [Summary of feedback received] +- [Date]: [How you addressed it] + +**Status:** [Awaiting review / Iterating / Approved / Merged] + +--- + +## Learnings & Reflections + +### Technical Skills Gained + +[What you learned technically] + +### Challenges Overcome + +[What was hard and how you solved it] + +### What I'd Do Differently Next Time + +[Reflection on your process] + +--- + +## Resources Used + +- https://github.com/copier-org/copier — Main repository +- https://github.com/copier-org/copier/issues/1451 — Issue being addressed +- https://github.com/samuelcolvin/watchfiles — Suggested file watching library +- Copier documentation: https://copier.readthedocs.io \ No newline at end of file From 08c252b7337d3658db50ebb002dba4e07a849604 Mon Sep 17 00:00:00 2001 From: Muhwezi Karen Emily Date: Tue, 23 Jun 2026 21:54:52 -0700 Subject: [PATCH 2/7] feat: add run_preview command with hot-reloading support --- copier/_main.py | 42 ++++++++++++++++++++++++++++++++++++++++++ pyproject.toml | 1 + 2 files changed, 43 insertions(+) diff --git a/copier/_main.py b/copier/_main.py index a584dba48..c876ac19a 100644 --- a/copier/_main.py +++ b/copier/_main.py @@ -1358,6 +1358,29 @@ def run_update(self) -> None: self._apply_update() self._print_message(self.template.message_after_update) + + def run_preview(self, dst_path: Path) -> None: + """Preview a template with hot-reloading as changes are made.""" + from watchfiles import watch + + print(f"\nStarting preview of template at {self.template.local_abspath}") + print("Watching for changes... (Ctrl+C to stop)\n") + + # Do initial render + with Phase.use(Phase.RENDER): + self._render_template() + print("Initial render complete.") + + # Watch for changes and re-render + try: + for changes in watch(self.template.local_abspath): + print(f"\nDetected changes: {changes}") + print("Re-rendering template...") + with Phase.use(Phase.RENDER): + self._render_template() + print("Re-render complete.") + except KeyboardInterrupt: + print("\nPreview stopped.") def _apply_update(self) -> None: # noqa: C901 git = get_git() @@ -1805,6 +1828,24 @@ def run_recopy( worker.run_recopy() return worker +def run_preview( + src_path: Path | str, + dst_path: Path | str = ".", + defaults: bool = True, + overwrite: bool = True, + quiet: bool = False, +) -> Worker: + """Preview a template with hot-reloading as you develop it.""" + with Worker( + src_path=src_path, + dst_path=Path(dst_path), + defaults=defaults, + overwrite=overwrite, + quiet=quiet, + ) as worker: + worker.run_preview(Path(dst_path)) + return worker + def run_update( dst_path: Path | str = ".", @@ -1864,6 +1905,7 @@ def run_update( return worker + def get_update_data( dst_path: Path | str = ".", answers_file: Path | str | None = None, diff --git a/pyproject.toml b/pyproject.toml index a037a8bb7..aa4d4e008 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,6 +32,7 @@ dependencies = [ "questionary>=1.8.1", "platformdirs>=4.3.6", "typing-extensions>=4.0.0,<5.0.0; python_version < '3.11'", + "watchfiles>=0.20", ] [project.urls] From 85cb7928a7794747264d5900e3cb6fea65c1bab4 Mon Sep 17 00:00:00 2001 From: Muhwezi Karen Emily Date: Tue, 23 Jun 2026 22:44:03 -0700 Subject: [PATCH 3/7] feat: add run_preview command with hot-reloading support and tests --- copier/_main.py | 7 ++++++- tests/test_copy.py | 18 ++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/copier/_main.py b/copier/_main.py index c876ac19a..e1bdcfb05 100644 --- a/copier/_main.py +++ b/copier/_main.py @@ -1,7 +1,9 @@ """Main functions and classes, used to generate or update projects.""" + from __future__ import annotations + import os import platform import stat @@ -18,6 +20,7 @@ from itertools import chain from pathlib import Path, PurePath, PurePosixPath, PureWindowsPath from shutil import rmtree +from watchfiles import watch from tempfile import TemporaryDirectory from types import TracebackType from typing import ( @@ -1359,9 +1362,9 @@ def run_update(self) -> None: self._apply_update() self._print_message(self.template.message_after_update) + @as_operation("copy") def run_preview(self, dst_path: Path) -> None: """Preview a template with hot-reloading as changes are made.""" - from watchfiles import watch print(f"\nStarting preview of template at {self.template.local_abspath}") print("Watching for changes... (Ctrl+C to stop)\n") @@ -1831,6 +1834,7 @@ def run_recopy( def run_preview( src_path: Path | str, dst_path: Path | str = ".", + data: dict[str, Any] | None = None, defaults: bool = True, overwrite: bool = True, quiet: bool = False, @@ -1839,6 +1843,7 @@ def run_preview( with Worker( src_path=src_path, dst_path=Path(dst_path), + data=data or {}, defaults=defaults, overwrite=overwrite, quiet=quiet, diff --git a/tests/test_copy.py b/tests/test_copy.py index 24e4c2b6a..5b90db15f 100644 --- a/tests/test_copy.py +++ b/tests/test_copy.py @@ -1522,3 +1522,21 @@ def test_copy_with_relative_path_to_local_git_template_through_multiple_parents( assert (dst / "test.txt").is_file() assert (dst / "test.txt").read_text() == "test" + +def test_preview_renders_template(tmp_path_factory: pytest.TempPathFactory) -> None: + """Test that run_preview renders the template into the destination directory.""" + src, dst = map(tmp_path_factory.mktemp, ("src", "dst")) + build_file_tree( + { + src / "hello.txt": "Hello, world!", + src / "copier.yml": "", + } + ) + # We mock watch to avoid actually watching for file changes in tests + with mock.patch("copier._main.watch") as mock_watch: + mock_watch.return_value = [] + from copier._main import run_preview + run_preview(str(src), dst, defaults=True, overwrite=True) + + assert (dst / "hello.txt").exists() + assert (dst / "hello.txt").read_text() == "Hello, world!" \ No newline at end of file From c722a6ff87be7829658a6273d14a582e96edc2c5 Mon Sep 17 00:00:00 2001 From: Muhwezi Karen Emily Date: Tue, 23 Jun 2026 23:01:57 -0700 Subject: [PATCH 4/7] docs: update Phase III Week 1 progress in contribution README --- CONTRIBUTION.md | 138 ++++++++++++++++++++++++++++++++++-------------- 1 file changed, 97 insertions(+), 41 deletions(-) diff --git a/CONTRIBUTION.md b/CONTRIBUTION.md index 9e9e8a7af..328fb9443 100644 --- a/CONTRIBUTION.md +++ b/CONTRIBUTION.md @@ -3,7 +3,7 @@ **Contribution Number:** 1 **Student:** Karen Emily Muhwezi **Issue:** https://github.com/copier-org/copier/issues/1451 -**Status:** Phase II In Progress +**Status:** Phase III In Progress --- @@ -55,7 +55,7 @@ and running multiple commands every single time a change is made. ### Affected Components - Copier's CLI layer — a new `preview` or `watch` command needs to be added -- Core template rendering pipeline in `copier/main.py` — the existing render +- Core template rendering pipeline in `copier/_main.py` — the existing render logic will be reused - A new file watching component using the `watchfiles` library to detect changes and trigger re-renders @@ -80,9 +80,13 @@ x Failed to build copier @ file:///C:/Users/Administrator/codepath-copier |-> error sending request for url (https://files.pythonhosted.org/...) `-> operation timed out -This appears to be a network connectivity issue on the current machine rather -than a project configuration problem. Will retry on a stable network -connection. + +Resolved by retrying on a stable network connection. Confirmed working +environment by running `python -m uv run copier --version` which returned +`copier 0.1.dev2247+g735b2ed0e`. + +Note: `uv` is not on the system PATH on Windows, so all commands must be +run as `python -m uv` instead of `uv` directly. ### Steps to Reproduce @@ -96,7 +100,9 @@ connection. - **Commit showing reproduction:** [Link to commit in your fork] - **Screenshots/logs:** Network timeout error documented above -- **My findings:** [What you discovered during reproduction] +- **My findings:** The issue is a missing feature — there is no existing + preview or watch command in Copier's CLI. Confirmed by searching the + codebase for any existing preview functionality and finding none. --- @@ -104,11 +110,18 @@ connection. ### Analysis -[Your analysis of the root cause - what's causing the issue?] +This is a missing feature rather than a bug. Copier currently only supports +rendering templates on demand via `copier copy` or `copier update`. There is +no mechanism to watch a template directory for changes and re-render +automatically. The root cause is that the CLI and rendering pipeline were +built for one-shot use, not continuous development workflows. ### Proposed Solution -[High-level description of your fix approach] +Added a new `run_preview` function that renders the template into a +destination directory and then watches the template directory for file +changes using the `watchfiles` library, re-rendering automatically on every +change detected. ### Implementation Plan @@ -118,26 +131,34 @@ Using UMPIRE framework (adapted): locally without a full commit-push-update cycle. We need a live preview command that watches for changes and re-renders automatically. -**Match:** The existing `copier copy` and `copier update` commands in -`copier/main.py` show how rendering is triggered. The new command will reuse -the same rendering logic but wrap it in a file watcher loop. +**Match:** The existing `run_copy` and `run_update` methods in `copier/_main.py` +show how rendering is triggered inside the `Worker` class. The new +`run_preview` method follows the exact same pattern, reusing `_render_template()` +and wrapping it in a `watchfiles` loop. The standalone function pattern was +modelled on `run_recopy`. **Plan:** -1. Add `watchfiles` as a dependency in `pyproject.toml` -2. Create a new `preview` or `watch` function in `copier/main.py` that - accepts a template path and output path -3. On first call, render the template into a temporary directory -4. Start a `watchfiles` watcher on the template directory -5. On every detected change, re-render and show a diff of what changed -6. Expose the new function as a CLI command via the existing CLI layer -7. Add tests for the new command +1. ✅ Add `watchfiles` as a dependency in `pyproject.toml` +2. ✅ Add `from watchfiles import watch` to module-level imports in `_main.py` +3. ✅ Add `run_preview` method to the `Worker` class decorated with + `@as_operation("copy")` +4. ✅ Add standalone `run_preview` function following the same pattern as + `run_recopy` +5. ✅ Write and pass test for the new command +6. ⬜ Expose the new function as a CLI command via the existing CLI layer +7. ⬜ Add additional edge case tests **Implement:** https://github.com/karenemily/codepath-copier/tree/fix-issue-live-preview -**Review:** [Self-review checklist - does it follow the project's contribution -guidelines?] +**Review:** +- [ ] Follows Copier's code style +- [ ] New command is documented +- [ ] Existing tests still pass +- [ ] New tests added and passing +- [ ] CHANGELOG updated if required -**Evaluate:** [What tests will confirm your fix works?] +**Evaluate:** Test `test_preview_renders_template` confirms the initial render +works correctly. Still need to test the hot-reload/watch loop behavior. --- @@ -145,18 +166,25 @@ guidelines?] ### Unit Tests -- [ ] Test case 1: [Description] -- [ ] Test case 2: [Description] -- [ ] Test case 3: [Description] +- [x] Test case 1: `test_preview_renders_template` — confirms that + `run_preview` correctly renders template files into the destination + directory on initial run. Test passes. +- [ ] Test case 2: Re-render triggered correctly after a file change is + detected by the watcher +- [ ] Test case 3: Preview handles template errors gracefully without + crashing the watcher ### Integration Tests -- [ ] Integration scenario 1 -- [ ] Integration scenario 2 +- [ ] Integration scenario 1: Full preview workflow from template edit to + re-render +- [ ] Integration scenario 2: Preview works correctly with Jinja templating + and variable substitution ### Manual Testing -[What you tested manually and results] +Manual testing not yet performed. Will test by running `run_preview` directly +on a sample template and editing files to confirm hot-reload behavior. --- @@ -164,18 +192,32 @@ guidelines?] ### Week 1 Progress -Environment setup attempted. Hit network timeout during `uv sync`. Branch -`fix-issue-live-preview` created and pushed. Codebase exploration in progress. - -### Week [Y] Progress +- Reviewed `CONTRIBUTING.md` and understood code style, testing, and commit + message requirements (Conventional Commits format) +- Explored codebase — identified `_main.py` as the correct file, studied + `run_copy` and `run_update` patterns +- Added `watchfiles>=0.20` to `pyproject.toml` dependencies +- Added `from watchfiles import watch` to module-level imports in `_main.py` +- Added `run_preview` method to the `Worker` class using `@as_operation("copy")` + decorator to satisfy operation context requirements +- Added standalone `run_preview` function with `src_path`, `dst_path`, `data`, + `defaults`, `overwrite`, and `quiet` parameters +- Wrote and passed test `test_preview_renders_template` in `tests/test_copy.py` +- Debugged multiple errors including SyntaxError from misplaced import, + LookupError from missing operation context, and mock patching issues + +### Week 2 Progress [Continue documenting as you work] ### Code Changes -- **Files modified:** [List] -- **Key commits:** [Links to important commits] -- **Approach decisions:** [Why you chose certain approaches] +- **Files modified:** `copier/_main.py`, `pyproject.toml`, `tests/test_copy.py` +- **Key commits:** [Paste your latest commit hash here] +- **Approach decisions:** Used `@as_operation("copy")` decorator on the + `run_preview` method to satisfy the internal operation context requirement. + Mocked `watchfiles.watch` in tests to avoid actual file watching during + test runs. Moved `watchfiles` import to module level to allow proper mocking. --- @@ -183,13 +225,13 @@ Environment setup attempted. Hit network timeout during `uv sync`. Branch **PR Link:** [GitHub PR URL when submitted] -**PR Description:** [Draft or final PR description] +**PR Description:** [TBD — will write in Phase IV] **Maintainer Feedback:** - [Date]: [Summary of feedback received] - [Date]: [How you addressed it] -**Status:** [Awaiting review / Iterating / Approved / Merged] +**Status:** Not yet submitted --- @@ -197,15 +239,28 @@ Environment setup attempted. Hit network timeout during `uv sync`. Branch ### Technical Skills Gained -[What you learned technically] +- Learned how to navigate a large unfamiliar Python codebase +- Understood the `Worker` class pattern and how Copier's operations are + structured +- Learned how to use `unittest.mock` to mock third-party library calls in tests +- Learned about Python `ContextVar` and operation context requirements +- Practised Conventional Commits format for commit messages ### Challenges Overcome -[What was hard and how you solved it] +- `uv` not on PATH on Windows — resolved by using `python -m uv` prefix +- Network timeout during `uv sync` — resolved by retrying on stable network +- SyntaxError from placing `from watchfiles import watch` before + `from __future__ import annotations` — resolved by moving it to the correct + position in the imports section +- `LookupError` for missing operation context — resolved by adding + `@as_operation("copy")` decorator to the method +- Mock patching failing because import was local — resolved by moving import + to module level ### What I'd Do Differently Next Time -[Reflection on your process] +[TBD — will reflect on this at the end of the project] --- @@ -214,4 +269,5 @@ Environment setup attempted. Hit network timeout during `uv sync`. Branch - https://github.com/copier-org/copier — Main repository - https://github.com/copier-org/copier/issues/1451 — Issue being addressed - https://github.com/samuelcolvin/watchfiles — Suggested file watching library -- Copier documentation: https://copier.readthedocs.io \ No newline at end of file +- Copier documentation: https://copier.readthedocs.io +- Copier CONTRIBUTING.md — Code style, testing and commit message guidelines \ No newline at end of file From 65b7fcaad88c0ac22ffe5a245e03d74a056789f8 Mon Sep 17 00:00:00 2001 From: Muhwezi Karen Emily Date: Mon, 29 Jun 2026 13:40:12 -0700 Subject: [PATCH 5/7] feat: add copier preview CLI subcommand --- copier/_cli.py | 45 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/copier/_cli.py b/copier/_cli.py index 0365f9364..0c2ff93c3 100644 --- a/copier/_cli.py +++ b/copier/_cli.py @@ -67,7 +67,7 @@ import yaml from plumbum import LocalPath, cli, colors -from ._main import get_update_data, run_copy, run_recopy, run_update +from ._main import get_update_data, run_copy, run_preview, run_recopy, run_update from ._tools import copier_version, try_enum from ._types import AnyByStrDict, VcsRef from .errors import UnsafeTemplateError, UserMessageError @@ -565,3 +565,46 @@ def inner() -> int: return 0 return _handle_exceptions(inner) + +@CopierApp.subcommand("preview") +class CopierPreviewSubApp(_Subcommand): + """The `copier preview` subcommand. + + Use this subcommand to preview how your template renders as you develop + it, with hot-reloading whenever you save a change. + """ + + DESCRIPTION = "Preview a template with hot-reloading as you develop it." + + defaults = cli.Flag( + ["-l", "--defaults"], + help="Use default answers to questions, which might be null if not specified.", + ) + overwrite = cli.Flag( + ["-w", "--overwrite"], + default=True, + help="Overwrite files that already exist.", + ) + + def main(self, template_src: str, destination_path: str) -> int: + """Call [run_preview][copier.run_preview]. + + Params: + template_src: + Path to the template you are developing locally. + + destination_path: + Where to render the preview output. + """ + + def inner() -> None: + run_preview( + template_src, + destination_path, + data=self.data, + defaults=self.defaults, + overwrite=self.overwrite, + quiet=self.quiet, + ) + + return _handle_exceptions(inner) From 8dc06c87002fe2f0f3bf31374f2252de62e6acb6 Mon Sep 17 00:00:00 2001 From: Muhwezi Karen Emily Date: Mon, 29 Jun 2026 14:25:42 -0700 Subject: [PATCH 6/7] docs: update Phase IV completion in contribution README --- CONTRIBUTION.md | 88 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 57 insertions(+), 31 deletions(-) diff --git a/CONTRIBUTION.md b/CONTRIBUTION.md index 328fb9443..0eb26a7d8 100644 --- a/CONTRIBUTION.md +++ b/CONTRIBUTION.md @@ -3,7 +3,7 @@ **Contribution Number:** 1 **Student:** Karen Emily Muhwezi **Issue:** https://github.com/copier-org/copier/issues/1451 -**Status:** Phase III In Progress +**Status:** Phase IV Complete --- @@ -11,8 +11,8 @@ As someone with a Python background who has worked with ML models, I'm drawn to tooling problems that slow developers down in real, tangible ways. This -issue describes a genuinely frustrating workflow — having to commit, push, and -run updates just to preview a template change — and proposes a solution that +issue describes a genuinely frustrating workflow, having to commit, push, and +run updates just to preview a template change, and proposes a solution that would make the development experience significantly better. That kind of quality-of-life improvement is something I find meaningful to work on because the impact is immediately visible. @@ -20,7 +20,7 @@ the impact is immediately visible. This issue also aligns well with my learning goals. Copier is a Python project, which plays to my existing strengths, but building a live preview feature means I'll be working with file watching, CLI design, and real-time -rendering pipelines — areas I haven't worked in deeply before. I want to grow +rendering pipelines, areas I haven't worked in deeply before. I want to grow my problem-solving skills by tackling something that has genuine complexity underneath a simple-sounding description, and see that solution actually ship in a tool that real developers use every day. @@ -34,7 +34,7 @@ in a tool that real developers use every day. Copier currently has no way to preview how a template renders during development. To see any changes, a developer must commit their changes, push to GitHub, switch to a downstream project, run `copier update`, and observe -the output — then repeat the entire cycle for every fix. This makes template +the output, then repeat the entire cycle for every fix. This makes template development extremely slow and tedious. ### Expected Behavior @@ -42,7 +42,7 @@ development extremely slow and tedious. A template developer should be able to run a single command like `copier preview` that renders their template locally into a temporary directory and automatically re-renders it every time they save a change, -without any manual steps in between — similar to hot-reloading in React +without any manual steps in between, similar to hot-reloading in React development. ### Current Behavior @@ -69,16 +69,12 @@ and running multiple commands every single time a change is made. Cloned fork from https://github.com/karenemily/codepath-copier.git Ran `python -m uv sync` and hit a network timeout trying to fetch `hatch_vcs`: -x Failed to build copier @ file:///C:/Users/Administrator/codepath-copier +x Failed to build copier @ file:///C:/Users/Administrator/codepath-copier |-> Failed to resolve requirements from build-system.requires - |-> No solution found when resolving: hatchling, hatch-vcs - |-> Request failed after 3 retries in 84.0s - |-> error sending request for url (https://files.pythonhosted.org/...) - `-> operation timed out Resolved by retrying on a stable network connection. Confirmed working @@ -98,11 +94,15 @@ run as `python -m uv` instead of `uv` directly. ### Reproduction Evidence -- **Commit showing reproduction:** [Link to commit in your fork] +- **Commit showing reproduction:** https://github.com/karenemily/codepath-copier/commit/65b7fcaad88c0ac22ffe5a245e03d74a056789f8 - **Screenshots/logs:** Network timeout error documented above - **My findings:** The issue is a missing feature — there is no existing preview or watch command in Copier's CLI. Confirmed by searching the - codebase for any existing preview functionality and finding none. + codebase for any `preview` or `watch` functionality and finding none. + Identified `_main.py` as the correct file to add the feature by studying + how `run_copy` and `run_update` are structured. Confirmed the feature + was fully missing by successfully running `copier --help` and seeing no + preview command listed. --- @@ -145,20 +145,21 @@ modelled on `run_recopy`. 4. ✅ Add standalone `run_preview` function following the same pattern as `run_recopy` 5. ✅ Write and pass test for the new command -6. ⬜ Expose the new function as a CLI command via the existing CLI layer +6. ✅ Expose the new function as a CLI command via the existing CLI layer 7. ⬜ Add additional edge case tests **Implement:** https://github.com/karenemily/codepath-copier/tree/fix-issue-live-preview **Review:** -- [ ] Follows Copier's code style -- [ ] New command is documented -- [ ] Existing tests still pass -- [ ] New tests added and passing -- [ ] CHANGELOG updated if required +- [x] Follows Copier's code style +- [x] New command is documented in PR description +- [x] Existing tests still pass +- [x] New tests added and passing +- [ ] CHANGELOG updated if required by maintainer **Evaluate:** Test `test_preview_renders_template` confirms the initial render -works correctly. Still need to test the hot-reload/watch loop behavior. +works correctly. Manual end-to-end testing confirmed hot-reload re-renders +correctly when template files are saved. --- @@ -183,8 +184,10 @@ works correctly. Still need to test the hot-reload/watch loop behavior. ### Manual Testing -Manual testing not yet performed. Will test by running `run_preview` directly -on a sample template and editing files to confirm hot-reload behavior. +Manually tested by running `copier preview` on a local test template. Created +a template with a `hello.txt` file, ran the preview command, edited the file, +and confirmed the output directory updated automatically without any manual +steps. Hot-reload confirmed working end to end. --- @@ -208,30 +211,43 @@ on a sample template and editing files to confirm hot-reload behavior. ### Week 2 Progress -[Continue documenting as you work] +- Added `CopierPreviewSubApp` class to `copier/_cli.py` to expose `run_preview` + as a `copier preview` CLI subcommand +- Imported `run_preview` into the CLI module alongside existing commands +- Rebased branch on `upstream/master` to stay up to date +- Ran full test suite — 246 passed, 2 pre-existing Windows-only failures + unrelated to our changes +- Performed manual end-to-end testing — confirmed hot-reload works correctly +- Submitted PR #2747 to the upstream Copier repository +- Tagged maintainer @sisp for review ### Code Changes -- **Files modified:** `copier/_main.py`, `pyproject.toml`, `tests/test_copy.py` -- **Key commits:** [Paste your latest commit hash here] +- **Files modified:** `copier/_main.py`, `copier/_cli.py`, `pyproject.toml`, + `tests/test_copy.py` +- **Key commits:** https://github.com/karenemily/codepath-copier/tree/fix-issue-live-preview - **Approach decisions:** Used `@as_operation("copy")` decorator on the `run_preview` method to satisfy the internal operation context requirement. Mocked `watchfiles.watch` in tests to avoid actual file watching during test runs. Moved `watchfiles` import to module level to allow proper mocking. + Followed the existing `CopierCopySubApp` pattern exactly when building the + CLI subcommand. --- ## Pull Request -**PR Link:** [GitHub PR URL when submitted] +**PR Link:** https://github.com/copier-org/copier/pull/2747 -**PR Description:** [TBD — will write in Phase IV] +**PR Description:** Added a new `copier preview` CLI command that renders +a template locally and automatically re-renders it whenever a file change +is detected using `watchfiles`, implementing the hot-reloading feature +requested in issue #1451. **Maintainer Feedback:** -- [Date]: [Summary of feedback received] -- [Date]: [How you addressed it] +- Awaiting review from @sisp -**Status:** Not yet submitted +**Status:** Awaiting review --- @@ -245,6 +261,9 @@ on a sample template and editing files to confirm hot-reload behavior. - Learned how to use `unittest.mock` to mock third-party library calls in tests - Learned about Python `ContextVar` and operation context requirements - Practised Conventional Commits format for commit messages +- Learned how CLI frameworks work and how to add new subcommands +- Gained experience with Git rebasing and working with upstream remotes +- Learned how to submit a real open source pull request ### Challenges Overcome @@ -257,10 +276,16 @@ on a sample template and editing files to confirm hot-reload behavior. `@as_operation("copy")` decorator to the method - Mock patching failing because import was local — resolved by moving import to module level +- Git rebase failing because default branch is `master` not `main` — resolved + by using `upstream/master` ### What I'd Do Differently Next Time -[TBD — will reflect on this at the end of the project] +- Set up the development environment on a Linux or Mac machine to avoid + Windows-specific issues with symlinks, PATH, and missing commands like `cat` +- Comment on the issue before starting work to confirm it is still wanted + by maintainers and get implementation guidance early +- Write tests alongside the code rather than after to catch issues sooner --- @@ -268,6 +293,7 @@ on a sample template and editing files to confirm hot-reload behavior. - https://github.com/copier-org/copier — Main repository - https://github.com/copier-org/copier/issues/1451 — Issue being addressed +- https://github.com/copier-org/copier/pull/2747 — Submitted pull request - https://github.com/samuelcolvin/watchfiles — Suggested file watching library - Copier documentation: https://copier.readthedocs.io - Copier CONTRIBUTING.md — Code style, testing and commit message guidelines \ No newline at end of file From e821ee509344440cf26d1e1b73bd9a0fd63dd7ef Mon Sep 17 00:00:00 2001 From: Karen Emily Muhwezi <150694931+karenemily@users.noreply.github.com> Date: Tue, 7 Jul 2026 20:36:29 -0700 Subject: [PATCH 7/7] Change contribution status to 'Phase IV Pending' Updated contribution status and added maintainer feedback. --- CONTRIBUTION.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTION.md b/CONTRIBUTION.md index 0eb26a7d8..2c8e0d381 100644 --- a/CONTRIBUTION.md +++ b/CONTRIBUTION.md @@ -3,7 +3,7 @@ **Contribution Number:** 1 **Student:** Karen Emily Muhwezi **Issue:** https://github.com/copier-org/copier/issues/1451 -**Status:** Phase IV Complete +**Status:** Phase IV Pending --- @@ -245,9 +245,12 @@ is detected using `watchfiles`, implementing the hot-reloading feature requested in issue #1451. **Maintainer Feedback:** -- Awaiting review from @sisp +"I don't think the current implementation is a correct solution to the problem. -**Status:** Awaiting review +It is insufficient to run Worker._render_template on source file changes – this ignores changes in the questionnaire, which likely affects the render context, and doesn't (re)run tasks, which may be essential to the render output. +It doesn't remove files that were previously rendered but are no longer rendered after a template change – files are only added or replaced. I imagine a (more) correct solution might involve making a full copy (using run_copy) in a temporary destination, computing the filetree diff between the temporary copy and the user-specified destination, and syncing the latter to the former. But this is just the first idea that has come to my mind right now. I imagine the devil is in the details. For example, for good UX you'll likely want to ask only questions that were changed in copier.yml, but a template might not render an answers file which is what copier update --skip-answered uses. On [CONTRIBUTION.md](https://github.com/copier-org/copier/pull/2747#discussion_r3529591928): I don't understand this file, it doesn't belong in this repo." + +**Status:** Awaiting review and working on implementing the feedback --- @@ -296,4 +299,4 @@ requested in issue #1451. - https://github.com/copier-org/copier/pull/2747 — Submitted pull request - https://github.com/samuelcolvin/watchfiles — Suggested file watching library - Copier documentation: https://copier.readthedocs.io -- Copier CONTRIBUTING.md — Code style, testing and commit message guidelines \ No newline at end of file +- Copier CONTRIBUTING.md — Code style, testing and commit message guidelines