`copier update` reformats YAML from answers file
Describe the problem
One of my templates has a type: yaml question for configuration + defaults of the generated project. Since this input is used to render documentation, its order is somewhat significant (more important keys first).
I wrote a custom yaml filter to keep the input order (+ some other tweaks) when dumping .copier-answers.yml, which works perfectly for recopy and update with --skip-answered. However, when omitting --skip-answered during updates, the dumped YAML for the question prompt is sorted automatically, destroying the order in the answers file.
Template
set -exo pipefail
mkdir repro
cd repro
<< 'EOF' > copier.yml
settings:
type: yaml
multiline: true
_jinja_extensions:
- copier_templates_extensions.TemplateExtensionLoader
- dumper.py:YamlDumper
EOF
<< 'EOF' > dumper.py
import yaml
from jinja2.ext import Extension
def represent_str(dumper, data):
"""
Represent multiline strings using "|"
"""
if len(data.splitlines()) > 1:
return dumper.represent_scalar("tag:yaml.org,2002:str", data, style="|")
return dumper.represent_scalar("tag:yaml.org,2002:str", data)
class OpinionatedYamlDumper(yaml.SafeDumper):
"""
Indent lists by two spaces
"""
def increase_indent(self, flow=False, indentless=False):
return super().increase_indent(flow=flow, indentless=False)
OpinionatedYamlDumper.add_representer(str, represent_str)
class YamlDumper(Extension):
def __init__(self, environment):
super().__init__(environment)
environment.filters["yaml"] = dump_yaml
def dump_yaml(data, flow_style=False, indent=0, sort_keys=False):
ret = yaml.dump(
data,
Dumper=OpinionatedYamlDumper,
indent=indent,
default_flow_style=flow_style,
canonical=False,
sort_keys=sort_keys,
)
if ret.endswith("...\n"):
ret = ret[:-4]
return ret.strip()
EOF
<< 'EOF' > doc.md.jinja
{%- for key, val in settings.items() %}
* {{ key }}: {{ val }}
{%- endfor %}
EOF
<< 'EOF' > "{{ _copier_conf.answers_file }}.jinja"
# Autogenerated. Do not edit this by hand, use 'copier update'.
---
{{ _copier_answers | yaml }}
EOF
git init
git add .
git commit -m init
git tag 1.0.0
To Reproduce
From where we left off above:
cd ..
copier copy --trust --data settings='foo: foo
bar: bar' $(pwd)/repro foo
cd foo
git init
git add .
git commit -m init
cat doc.md
# * foo: foo
# * bar: bar
copier update --trust
# just accept the default via alt+enter / esc,enter
git diff -p
Even though I accepted the previous value verbatim, this prints:
diff --git a/.copier-answers.yml b/.copier-answers.yml
index a8a6d42..7434558 100644
--- a/.copier-answers.yml
+++ b/.copier-answers.yml
@@ -3,5 +3,5 @@
_commit: 1.0.0
_src_path: /Users/jeanluc/tmp/reprotemp/repro
settings:
- foo: foo
bar: bar
+ foo: foo
diff --git a/doc.md b/doc.md
index 98bf876..c64a7c6 100644
--- a/doc.md
+++ b/doc.md
@@ -1,3 +1,3 @@
-* foo: foo
* bar: bar
+* foo: foo
Logs
No response
Expected behavior
The default value in the question should not modify the order that is found in the answers file.
I would prefer if Copier kept the formatting exactly as dumped into the answers file, but this would be a bit more involved than just setting sort_keys=False in yaml.dumps and can be recovered.
Screenshots/screencasts/logs
No response
Operating system
macOS
Operating system distribution and version
Sequoia 15.1.1
Copier version
copier 9.4.1
Python version
CPython 3.13
Installation method
pipx+pypi (actually uv+pypi)
Additional context
As mentioned, this could be solved by just setting sort_keys=False here:
https://github.com/copier-org/copier/blob/2dc1687af389505a708f25b0bc4e37af56179e99/copier/user_data.py#L283-L285
I think this should be the default behavior since the current one encodes information in a lossy way.