Skip to content

Commit 61cbe79

Browse files
authored
feat: allow overriding data file with CLI arguments (#1332)
Users should be able to pass CLI data arguments and use a `--data-file` at the same time. Let's just take inspiration from the [helm values file](https://helm.sh/docs/chart_template_guide/values_files/) and give highest priority to data values passed at the command line.
1 parent 74769bc commit 61cbe79

File tree

3 files changed

+40
-23
lines changed

3 files changed

+40
-23
lines changed

copier/cli.py

+9-4
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,9 @@ class CopierApp(cli.Application):
104104
class _Subcommand(cli.Application):
105105
"""Base class for Copier subcommands."""
106106

107-
data: AnyByStrDict = {}
107+
def __init__(self, executable):
108+
self.data: AnyByStrDict = {}
109+
super().__init__(executable)
108110

109111
answers_file = cli.SwitchAttr(
110112
["-a", "--answers-file"],
@@ -158,7 +160,6 @@ class _Subcommand(cli.Application):
158160
"VARIABLE=VALUE",
159161
list=True,
160162
help="Make VARIABLE available as VALUE when rendering the template",
161-
excludes=["--data-file"],
162163
)
163164
def data_switch(self, values: StrSeq) -> None:
164165
"""Update [data][] with provided values.
@@ -175,7 +176,6 @@ def data_switch(self, values: StrSeq) -> None:
175176
["--data-file"],
176177
cli.ExistingFile,
177178
help="Load data from a YAML file",
178-
excludes=["--data"],
179179
)
180180
def data_file_switch(self, path: cli.ExistingFile) -> None:
181181
"""Update [data][] with provided values.
@@ -184,7 +184,12 @@ def data_file_switch(self, path: cli.ExistingFile) -> None:
184184
path: The path to the YAML file to load.
185185
"""
186186
with open(path) as f:
187-
self.data.update(yaml.safe_load(f))
187+
file_updates: AnyByStrDict = yaml.safe_load(f)
188+
189+
updates_without_cli_overrides = {
190+
k: v for k, v in file_updates.items() if k not in self.data
191+
}
192+
self.data.update(updates_without_cli_overrides)
188193

189194
def _worker(self, src_path: OptStr = None, dst_path: str = ".", **kwargs) -> Worker:
190195
"""

docs/configuring.md

+8-5
Original file line numberDiff line numberDiff line change
@@ -758,10 +758,6 @@ questions with default answers.
758758
copier copy -fd 'user_name=Manuel Calavera' template destination
759759
```
760760

761-
!!! info
762-
763-
The `-d, --data` flags are mutually exclusive with the [`--data-file`][data_file] flag.
764-
765761
### `data_file`
766762

767763
- Format: `str`
@@ -797,9 +793,16 @@ contains your data.
797793
copier copy -d 'user_name=Manuel Calavera' -d 'age=7' -d 'height=1.83' template destination
798794
```
799795

796+
if you'd like to override some of the answers in the file, `--data` flags always take
797+
precedence
798+
799+
```shell
800+
copier copy -d 'user_name=Bilbo Baggins' --data-file input.yml template destination
801+
```
802+
800803
!!! info
801804

802-
The `--data-file` flag is mutually exclusive with the [`-d, --data`][data] flags.
805+
command line arguments passed via `--data` will always take precedence over the answers file
803806

804807
### `envops`
805808

tests/test_cli.py

+23-14
Original file line numberDiff line numberDiff line change
@@ -170,18 +170,20 @@ def test_data_file_parsed_by_question_type(
170170
assert answers["question"] == "answer"
171171

172172

173-
def test_data_and_data_file_mutually_exclusive(
173+
def test_data_cli_takes_precedence_over_data_file(
174174
tmp_path_factory: pytest.TempPathFactory,
175-
capsys: pytest.CaptureFixture[str],
176175
):
177176
src, dst, local = map(tmp_path_factory.mktemp, ("src", "dst", "local"))
178-
179177
build_file_tree(
180178
{
181179
(src / "copier.yml"): (
182180
"""\
183181
question:
184182
type: str
183+
another_question:
184+
type: str
185+
yet_another_question:
186+
type: str
185187
"""
186188
),
187189
(src / "{{ _copier_conf.answers_file }}.jinja"): (
@@ -190,31 +192,38 @@ def test_data_and_data_file_mutually_exclusive(
190192
{{ _copier_answers|to_nice_yaml }}
191193
"""
192194
),
193-
(local / "data.yml"): yaml.dump({"question": "does not matter"}),
195+
(local / "data.yml"): yaml.dump(
196+
{
197+
"question": "does not matter",
198+
"another_question": "another answer!",
199+
"yet_another_question": "sure, one more for you!",
200+
}
201+
),
194202
}
195203
)
196204

205+
answer_override = "fourscore and seven years ago"
197206
run_result = CopierApp.run(
198207
[
199208
"copier",
200209
"copy",
201210
f"--data-file={local / 'data.yml'}",
202-
'--data=question="also does not matter"',
211+
f"--data=question={answer_override}",
203212
str(src),
204213
str(dst),
205214
"--quiet",
206215
],
207216
exit=False,
208217
)
209-
assert run_result[1] == 2 # cli failure
210-
out, _ = capsys.readouterr()
211-
assert any(
212-
[
213-
"Error: Given --data-file, the following are invalid ['--data']" in out,
214-
"Error: Given --data, the following are invalid ['--data-file']" in out,
215-
]
216-
)
217-
assert not (dst / ".copier-answers.yml").exists()
218+
assert run_result[1] == 0
219+
actual_answers = yaml.safe_load((dst / ".copier-answers.yml").read_text())
220+
expected_answers = {
221+
"_src_path": str(src),
222+
"question": answer_override,
223+
"another_question": "another answer!",
224+
"yet_another_question": "sure, one more for you!",
225+
}
226+
assert actual_answers == expected_answers
218227

219228

220229
@pytest.mark.parametrize(

0 commit comments

Comments
 (0)