feat: add copier inspect subcommand#2575
Conversation
There was a problem hiding this comment.
I have some reservations about the current change:
- Why add this flag to the
copy/recopy/updatecommands when it performs the exact same task? Conceptually, it isn't even related to any of those because it is an inspect action and not a (re)copy/update action. - The UX of using this flag is bad. For example,
copier copyrequires a destination path, but this path is irrelevant when using the flag, and it can't be omitted because the path is mandatory for normal use of the command.
Perhaps a new subcommand like copier inspect (inspired by, e.g., docker inspect <URL> is more suitable for this feature. In any case, we'll eventually need tests for this feature.
Do you think that printing the question specs is useful even when they are possibly heavily templated and when there are (possibly non-trivial chains of) conditional questions? Note also that when: false questions are computed/derived values for which no answer shall be provided by the user.
Thanks for disclosing the use of AI in the PR description.
f279980 to
566552c
Compare
|
Thanks for the thorough review — all very valid points! Backstory on the What changed:
|
Add a new `copier inspect <template>` subcommand that prints template questions with their metadata (type, default, choices, conditions, help text) without copying or modifying any files. Key behaviors: - Output formats: plain (default), JSON, YAML via --output-format - Questions with `when: false` (computed/derived values) are hidden in plain output but included in JSON/YAML with `computed: true` - Conditional questions show raw `when` Jinja2 expressions - No --trust required (no files created, no tasks executed) Closes copier-org#2574 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
566552c to
31ef758
Compare
sisp
left a comment
There was a problem hiding this comment.
I wonder whether it would be better to simply print a YAML dump of the question specs instead of trying to make it human-readable which may have all kinds of edge cases (e.g., complex choices, conditional questions, conditionally required questions). WDYT?
A comment regarding use of AI: The code changes are clearly AI-generated, as you disclosed using Claude Code in the PR description – there is nothing wrong with it per se. But it is my strong impression that you didn't invest time into reviewing and validating the changes properly prior to pushing commits which means you're offloading this work to us which creates additional effort on our side, disrespecting our time we invest into this project. Also, please write PR comments in your own words. Thanks for your understanding.
| str, | ||
| help="Git reference to checkout in `template_src`.", | ||
| ) | ||
| quiet = cli.Flag(["-q", "--quiet"], help="Suppress status output") |
There was a problem hiding this comment.
What is the point of this subcommand when its output is suppressed? I think this flag makes no sense here and should be removed.
| secret_token: | ||
| type: str | ||
| secret: true | ||
| help: API token for deployment |
There was a problem hiding this comment.
This is an incorrect question spec because Copier currently requires a default answer for secret questions:
- secret: When
true, it hides the prompt displaying asterisks (*****) and doesn't save the answer in the answers file. Whentrue, a default value is required.
| print(f" {label}: {choices}") | ||
| elif isinstance(choices, dict): | ||
| print( | ||
| f" {label}: {', '.join(str(c) for c in choices.keys())}" |
There was a problem hiding this comment.
Listing the choice keys when choices is a dict means that the "labels" are listed but not the values/answers. When passing answers via -d, --data or --data-file flag, the dict value must be passed.
This leads to a more fundamental question: How do you intend to print choice values for JSON/YAML questions when they are complex (multiline) objects?
| if default is MISSING: | ||
| parts.append("REQUIRED") |
There was a problem hiding this comment.
Question can be conditionally required by rendering the special UNSET variable. See the example of the default field at https://copier.readthedocs.io/en/stable/configuring/#advanced-prompt-formatting:
database_engine:
type: str
help: Database engine
choices:
- postgres
- mysql
- other
default: postgres
database_url:
type: str
help: Database URL
default: >-
{%- if database_engine == 'postgres' -%}
postgresql://user:pass@localhost:5432/dbname
{%- elif database_engine == 'mysql' -%}
mysql://user:pass@localhost:3306/dbname
{%- else -%}
{{ UNSET }}
{%- endif -%}
# Simplified for illustration purposes
validator: "{% if '://' not in database_url %}Invalid{% endif %}"This means, printing REQUIRED according to this logic isn't guaranteed to be accurate.
| if isinstance(when, str) and "{{" in when: | ||
| return False |
There was a problem hiding this comment.
The Jinja variable start marker {{ can be configured in the template via
_envops:
variable_start_string: "[["so it is incorrect to hardcode it here.
There was a problem hiding this comment.
I think it would be better and clearer to assert the full printed output for a given question spec in copier.yml rather than asserting substring matches. Instead of having one test template with many questions, a parametrized test with one question spec and one expected print output per test case would be preferable IMHO.
There was a problem hiding this comment.
I'd prefer keeping this function internal until there is real demand for performing this via Python API to minimize the public API surface.
Why
When using Copier non-interactively (CI pipelines, agents, scripts), there is no way to discover which
--dataparameters a template expects without readingcopier.yamldirectly. This makes templates opaque to tooling that wants to callcopier copywith the right--dataflags.What
Adds a new
copier inspect <template>subcommand that prints template questions with their metadata without copying or modifying any files.Key behaviors:
plain(default),json,yamlvia--output-formatwhen: false(computed/derived values) are hidden in plain output but included in JSON/YAML with acomputed: truemarkerwhenJinja2 expressions--trustrequired (exits before any unsafe checks)--vcs-refto inspect a specific template versionDesign note
The original version of this PR added a
--show-questionsflag tocopy/recopy/update. As pointed out in review, this was a poor fit: the flag performed an inspect action on mutation commands, requiring irrelevant arguments (like a destination path). A dedicatedcopier inspectsubcommand — modeled aftercopier check-update— is a much better UX.Test plan
copier inspect <template>prints questions to stdoutcopier inspect --output-format jsonproduces valid JSON withcomputedmarkerscopier inspect --output-format yamlproduces valid YAML withcomputedmarkerscopier inspect --quietsuppresses outputwhen: false,when: 0,when: "false") hidden in plain output🤖 Generated with Claude Code