Replies: 6 comments 8 replies
-
From what I've seen, it seems the best advice is to simply separate templates such that there is no overlap of files that they would affect in a rendered project. That seems mostly feasible, with the exception of |
Beta Was this translation helpful? Give feedback.
-
Another place where this seems to be a problem is with pre-commit hooks in the |
Beta Was this translation helpful? Give feedback.
-
I think the solution to your particular problem is to use tasks
tasks:
- poetry add mkdocs=1.0.0 Technically this works for any "configuration" that you can generate through A possible solution to this is if copier supported "snippet injection". The way I see it, you could be able to insert in a rendered file a "keyword" like: ExampleIn a
In snippets:
- id: DOCKER_LAYER
snippet_path: snippets/node-layer.jinja
file_path: Dockerfile
- id: DOCKER_LAYER
snippet_path: snippets/rust-layer.jinja
file_path: Dockerfile
- id: DOCKER_RUN
snippet_path: snippets/python-build.jinja
file_path: Dockerfile |
Beta Was this translation helpful? Give feedback.
-
I've been hitting exactly this issue of "how does a layered template MODIFY files", and have come up with a hacky but creative solution that may help you. The idea is to store See the below patch file example for
diff --git a/pyproject.toml b/pyproject.toml
index 7ae5802..ea08686 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -17,6 +17,8 @@ my-testing-project = "my_testing_project.cli:cli"
[tool.poetry.dependencies]
python = "^3.12"
+# Library for making patches
+mass-driver = "*"
[tool.poetry.group.test.dependencies]
@@ -81,3 +83,6 @@ pydocstyle = {convention = "google"}
[tool.mypy]
python_version = "3.12"
check_untyped_defs = true
+
+[tool.poetry.plugins.'massdriver.drivers']
+my-testing-project = 'my_testing_project.patchdriver:SimplePatcher' ComplicationsYou may notice that some of the patch context includes legitimate base-template answer material like the python version and python package name. So we turn the BUT the patch won't apply properly if the template "above" doesn't know the base template's answers. So off we go making the "overlay" template get the answers from the base template! Note this solution breaks the "templates are horizontally independent" assumption that permeates in copier's documentation, replacing it with a notion of vertical "stack", or "overlay" of templates where the above "overlay" knows everything about the below template (answers-file filename, template questions/answers and their meaning...), and is assumed to have to target such a base specifically. Adding base template answers to jinja contextUsing a copier Context Hook (arbitrary Python func that can change the jinja context = extra metadata) I can read what the destination path looks like on filesystem, read the
import yaml
from copier_templates_extensions import ContextHook
# Expand the context to add all the old data from the sub-project we're loading from
class ContextUpdater(ContextHook):
def hook(self, context):
copier_conf = context["_copier_conf"]
answers_path = copier_conf["dst_path"] / ".copier-answers.yml" # Hardcoded!
new_context = yaml.load(answers_path.read_text(), Loader=yaml.Loader)
return {"baseconf": new_context}
diff --git a/pyproject.toml b/pyproject.toml
index 7ae5802..ea08686 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -17,6 +17,8 @@ {{ baseconf.project_slug }} = "{{ baseconf.package_name }}.cli:cli"
[tool.poetry.dependencies]
python = "^{{ baseconf.python_version }}"
+# Library for making patches
+mass-driver = "*"
[tool.poetry.group.test.dependencies]
@@ -81,3 +83,6 @@ pydocstyle = {convention = "google"}
[tool.mypy]
python_version = "{{ baseconf.python_version }}"
check_untyped_defs = true
+
+[tool.poetry.plugins.'massdriver.drivers']
+{{ baseconf.project_slug }} = '{{ baseconf.package_name }}.patchdriver:SimplePatcher' And my copier's apply-patches #!/usr/bin/env bash
set -euo pipefail
# Print, apply, and remove patches
# PWD is dst_path in templates
PATCHDIR=$PWD/patches/
echo "Checking for patches:"
find $PATCHDIR -type f -iname "*.patch"
echo "Applying patches..."
find $PATCHDIR -type f -iname "*.patch" -exec git apply {} \;
echo "Patch applied!"
find $PATCHDIR -type f -iname "*.patch" -delete
echo "Patches removed!" and finally all this tied in my _subdirectory: template
_templates_suffix: .j2
_tasks:
- {{_copier_conf.src_path}}/hooks/patches
_jinja_extensions:
- copier_templates_extensions.TemplateExtensionLoader
- context.py:ContextUpdater
# [...] template questions overall folder structure:
I'd love for copier to make it easier to do this "base template information" for me, but I don't see how this can be done in a way that can recurse (I fully expect 2 or more layers of templates that each know both layers below them, in the future, and cannot imagine how copier can do this in a generic way that is not brittle). Hopefully this helps, sparks ideas on how to do template layering, or at least we all learn something from it =) |
Beta Was this translation helpful? Give feedback.
-
I arrived at this thread as a result of similar requirements, i.e. how to patch an existing file since it's a tricky challenge to avoid, especially in an upgrade scenario. Reading the above as other tickets I wonder if there are some simpler use-cases that could be catered for that may hit the 80/20 rule. For example looking at what has been mentioned could the tasks (and migrations?) be enhanced? E.g., if a method == method == methods == method == I think the above would be possible without introducing any new dependencies too. |
Beta Was this translation helpful? Give feedback.
-
I think we're overcomplicating it a bit. Copier handles updates. Merges histories from your subproject and from the template. When you apply 2 templates to 1 subproject and you update from one of those templates, from that template's point of view, anything you updated from the other template is just subproject history that it has to apply. I guess it's easier to see it in action, so I recorded a video: https://youtu.be/EwzJA-v7DCQ |
Beta Was this translation helpful? Give feedback.
-
Hello! First let me say, I'm loving Copier! Our team adopted it about a year ago and now all of our projects share the same base template that we update regularly.
Over the year, our template has now grown, and it's becoming clear that soon we'll need to break it apart into smaller templates. I apologize in advance if this question has been asked (and answered) before, but I've had trouble tracking down a concrete answer for this.
Instead of writing in the abstract, let me be clear in our specific use case. Our template currently can, among other things, can create placeholder tests and automatically render Jupyter notebooks for use on ReadTheDocs. To support that we create a pyproject.toml file, that has a list of dependencies including
pytest
andnbsphinx
(among several others).If we separate the portion of the template that renders the Jupyter notebooks into a separate "documentation template", what is the best practice for defining the requirements in the pyproject.toml file? Experimenting locally leads me to believe that if we apply multiple templates that all affect the same file, that file will either be overwritten or unmodified by each new template applied.
I've seen a few discussions that seem close to addressing this: https://github.com/orgs/copier-org/discussions/1465, #934, but they feel a bit abstract for me. I'm hoping to find some advice for this particular issue that might unblock the path forward.
Beta Was this translation helpful? Give feedback.
All reactions