doc: add inheritance and composition section in user docs
Contributes to https://github.com/copier-org/copier/issues/934
Codecov Report
:white_check_mark: All modified and coverable lines are covered by tests.
:white_check_mark: Project coverage is 97.90%. Comparing base (aad1f7d) to head (2a2116d).
:warning: Report is 70 commits behind head on master.
Additional details and impacted files
@@ Coverage Diff @@
## master #2165 +/- ##
==========================================
- Coverage 98.03% 97.90% -0.14%
==========================================
Files 55 55
Lines 6005 6108 +103
==========================================
+ Hits 5887 5980 +93
- Misses 118 128 +10
| Flag | Coverage Ξ | |
|---|---|---|
| unittests | 97.90% <ΓΈ> (-0.14%) |
:arrow_down: |
Flags with carried forward coverage won't be shown. Click here to find out more.
:umbrella: View full report in Codecov by Sentry.
:loudspeaker: Have feedback on the report? Share it here.
:rocket: New features to boost your workflow:
- :snowflake: Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
Regarding solutions: Meta-templates is another one, although I don't know how relevant it actually is in practice.
Right! I think it is not really a solution for inheritance and composition tbh, but I added it to the list, and we will discuss it at due time
@sisp I am back from vacation and worked on the documentation last week and I just finished a demo of the approach I was suggesting using tasks and migrations.
You can find it here: https://github.com/francesco086/copier-inheritance-via-task-and-migration-demo-child
I will reference it in the documentation and keep moving with other approaches π
Thanks, @francesco086! I'll take a look next week, out of office myself at the moment. π
Thanks, @francesco086! I'll take a look next week, out of office myself at the moment. π
Enjoy your time off!!!
Hi @sisp , I am coming to this after quite some long time.
I was trying to look at the alternative ideas, but to me they all look way more complicated and with relevant drawbacks compared to the one I already documented. I know I am biased, but to me it looks like the approach I proposed (and now refined) is the way to go.
Could you perhaps have a look at what I have done and let me know if I should still try to document different approaches?
Thanks for the reminder, @francesco086! :+1:
I definitely see some advantages of the task/migrations-based approach, especially the encapsulation of the parent template. But I struggle with the implementation of a simple scenario like this: Imagine you have a generic Python project template (parent) and a Python FastAPI template (child). Consider, e.g., the pyproject.toml.jinja file with PEP 621 project metadata where the parent template declares dependencies = [] (because it makes no assumptions about runtime dependencies) and the child template declares dependencies = ["fastapi[standard]"] (because it generates starter code for a FastAPI app). IIUC, the task/migrations-based approach can only exclude the pyproject.toml.jinja file from the parent template and redefine it in the child template although (for the sake of this example) the only difference is the one line about runtime dependencies. Am I right?
Hey @sisp !
Yes, you are absolutely right.
One would make a copy-paste (or git sparse clone) from the parent, and modify it. The real problem would come with the parents upgrade (notice that since the parent version is pinned, this is under manual control). You would have to do the same procedure mentioned before. You could use diff tools to help yourself, but eventually it would be a manual process. Frankly I don't think one can do much better. In these scenarios usually git gives you conflicts to resolve manually. But I may be mistaken.
In general, I think one should try to avoid the overwriting of a file, so it should be limited to as few files as possible. For example, in the use case you mentioned, one could use dynamic dependencies and only overwrite a requirements file (of course this requires being able to influence the parent template, which is not always granted)
[project]
dynamic = ["dependencies"]
[tool.setuptools.dynamic]
dependencies = { file = ["requirements.txt"] }
This to say that I think that this drawback has not such strong impact.
Thanks for confirming. This means, the limitations of task/migrations-based template inheritance impact design decisions of the generated project. :thinking: The dependencies example is only one among many. For example, a generic Python project template may have a basic pytest configuration, and a FastAPI template may extend the starter to include some E2E tests setup which requires additions in pyproject.toml.jinja, e.g.:
[tool.pytest.ini_options]
...
-addopts = "-n auto"
+addopts = "-n auto --strict-markers -m 'not e2e'"
+markers = ["e2e: mark tests as E2E tests"]
...
It feels like these kinds of surgical edits are unpredictable.
Of course, I'm biased towards the "fork"-based approach, but it does support these scenarios β at the cost of a fully materialized parent template and a need to sync the "fork" with its upstream template. We're exploring this approach internally, so far it's looking fine, but we'll need to gain more experience over time.
That said, the task/migrations-based approach may work just fine with less complexity for some template hierarchies that don't need surgical edits.
Do you have a reference of this fork-like approach for me to document it? It could be my next method to document.
I will also add this limitation you pointed out to the cons.