Allow for .copier-answers.yaml to be default in backwards-compatible way#2196
Allow for .copier-answers.yaml to be default in backwards-compatible way#2196timkpaine wants to merge 4 commits intocopier-org:masterfrom
Conversation
There was a problem hiding this comment.
I'm open to supporting the .yaml suffix in a backwards-compatible way, but I'm afraid the solution is a little more involved.
- See my inline comment about your suggested change.
- The default answers file name/path is hardcoded in a few places: https://github.com/search?q=repo%3Acopier-org%2Fcopier+%22.copier-answers.yml%22+path%3A%2F%5Ecopier%5C%2F%2F&type=code This should be refactored anyway, but with a primary and secondary file name/path, we can't simply use a default value, as there isn't a single one anymore but rather some logic that first tries reading the primary file name/path and falls back to the secondary when the primary doesn't exist. Perhaps the simplest solution is to encode the default answers file name/path as
Noneand only resolveNoneas.copier-answers.ymlor.copier-answers.yamlincopier/_user_data.py::load_answersfile_data.
Whatever the solution looks like in the end, I have three general requests:
- I think the
.ymlsuffix should take precedence over the.yamlsuffix for maximum backwards compatibility. - I think we should check for the existence of both files (
.ymlsuffix and.yamlsuffix) and raise a warning if both exist, just in case, e.g., a project was migrated from.yamlto.ymlbut forgot to remove the.yamlfile. - Please add tests.
WDYT?
copier/_main.py Outdated
| @cached_property | ||
| def subproject(self) -> Subproject: | ||
| """Get related subproject.""" | ||
| default_answers_file = Path(".copier-answers.yaml") if Path(".copier-answers.yaml").exists() else Path(".copier-answers.yml") |
There was a problem hiding this comment.
This won't work because the Path object passed to the answers_relpath argument of Subproject(local_abspath=..., answers_relpath=...) is the path of the answers file relative to local_abspath. Path(".copier-answers.yaml").exists() checks whether .copier-answers.yaml exists in the current working directory, which is not guaranteed to be local_abspath.
Sounds good, unfortunately it will be slightly annoying as we'll have to make sure to look in the "right direction", e.g. in some places you want to check
Makes sense to me.
Will do |
| coming back to this soon, apologies I got distracted with some other copier stuff. |
| Awesome presentation @timkpaine! Always nice to see our work presented in conferences 😍 |
sisp left a comment
There was a problem hiding this comment.
Thanks for reminding me of this PR, @timkpaine! 👍 And sorry for the delay ... 🫣
I've left a couple of inline comments.
tests/test_yaml_answersfile.py Outdated
| @pytest.fixture | ||
| def simple_template(tmp_path_factory: pytest.TempPathFactory) -> Path: | ||
| """Create a simple template for testing.""" | ||
| root = tmp_path_factory.mktemp("template") | ||
| build_file_tree( | ||
| { | ||
| (root / "copier.yml"): dedent( | ||
| """\ | ||
| name: | ||
| type: str | ||
| default: test | ||
| """ | ||
| ), | ||
| (root / "{{ _copier_conf.answers_file }}.jinja"): ( | ||
| "{{ _copier_answers|tojson }}" | ||
| ), | ||
| } | ||
| ) | ||
| git_save(root) | ||
| return root |
There was a problem hiding this comment.
How about moving this fixture to the TestCopyWithYamlAnswersFile class below? This is the only place where it is used.
tests/test_yaml_answersfile.py Outdated
There was a problem hiding this comment.
How about moving the contents of this file to the existing tests/test_answersfile.py file to avoid scattering somewhat related tests across multiple files?
tests/test_yaml_answersfile.py Outdated
There was a problem hiding this comment.
I acknowledge your efforts to test the answers file resolution thoroughly. But I notice quite a few tests involving rather internal classes and functions which are likely subject to change because of internal refactoring once the public API has been cleaned up. What really matters is the intended answers file resolution behavior for a few user-level scenarios which can be tested with run_{recopy,update} calls. How about limiting tests to those scenarios and removing the tests involving non-public API?
copier/_user_data.py Outdated
| if answers_file is None: | ||
| yml_path = dst / DEFAULT_ANSWERS_FILE_YML | ||
| yaml_path = dst / DEFAULT_ANSWERS_FILE_YAML | ||
| | ||
| yml_exists = yml_path.is_file() | ||
| yaml_exists = yaml_path.is_file() | ||
| | ||
| # Warn if both files exist | ||
| if yml_exists and yaml_exists: | ||
| warnings.warn( | ||
| f"Both {DEFAULT_ANSWERS_FILE_YML} and {DEFAULT_ANSWERS_FILE_YAML} " | ||
| f"exist in {dst_path}. Using {DEFAULT_ANSWERS_FILE_YML}. " | ||
| "Please remove the duplicate file.", | ||
| stacklevel=2, | ||
| ) | ||
| | ||
| # .yml takes precedence, fall back to .yaml | ||
| if yml_exists: | ||
| answers_file = DEFAULT_ANSWERS_FILE_YML | ||
| elif yaml_exists: | ||
| answers_file = DEFAULT_ANSWERS_FILE_YAML | ||
| else: | ||
| # Default to .yml when neither exists | ||
| answers_file = DEFAULT_ANSWERS_FILE_YML |
There was a problem hiding this comment.
I think we can call resolve_answersfile_path here to deduplicate this code block.
This is a very small backwards-compatible change.
Copier uses
.copier-answers.ymlas the default, and supports overriding. As discussed in #1077.ymlis the default suffix, not.yaml. It makes sense to not change the default, but perhaps it would be ok to start to check.copier-answers.yaml? This PR does that very small change, if the user has decided to use the default answers file but with.yamlsuffix instead of.ymlsuffix,copier updatewill work without-a.