Skip to content

cli.py

Command line entrypoint. This module declares the Copier CLI applications.

Basically, there are 3 different commands you can run:

  • copier, the main app, which is a shortcut for the copy and update subapps.

    If the destination project is found and has an answers file with enough information, it will become a shortcut for copier update.

    Otherwise it will be a shortcut for copier copy.

    Example

    # Copy a new project
    copier gh:copier-org/autopretty my-project
    # Update it
    cd my-project
    copier
    
  • copier copy, used to bootstrap a new project from a template.

    Example

    copier copy gh:copier-org/autopretty my-project
    
  • copier update to update a preexisting project to a newer version of its template.

    Example

    copier update
    

Below are the docs of each one of those.

CopierApp

Bases: cli.Application

The Copier CLI application.

Attributes:

Name Type Description
answers_file cli.SwitchAttr

Set answers_file option.

exclude cli.SwitchAttr

Set exclude option.

vcs_ref cli.SwitchAttr

Set vcs_ref option.

pretend cli.Flag

Set pretend option.

force cli.Flag

Set force option.

defaults cli.Flag

Set defaults option.

overwrite cli.Flag

Set overwrite option.

skip cli.Flag

Set skip_if_exists option.

prereleases cli.Flag

Set use_prereleases option.

quiet cli.Flag

Set quiet option.

Source code in copier/cli.py
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
class CopierApp(cli.Application):
    """The Copier CLI application.

    Attributes:
        answers_file: Set [answers_file][] option.
        exclude: Set [exclude][] option.
        vcs_ref: Set [vcs_ref][] option.
        pretend: Set [pretend][] option.
        force: Set [force][] option.
        defaults: Set [defaults][] option.
        overwrite: Set [overwrite][] option.
        skip: Set [skip_if_exists][] option.
        prereleases: Set [use_prereleases][] option.
        quiet: Set [quiet][] option.
    """

    DESCRIPTION = "Create a new project from a template."
    DESCRIPTION_MORE = (
        dedent(
            """\
            Docs in https://copier.readthedocs.io/

            """
        )
        + (
            colors.yellow
            | dedent(
                """\
                WARNING! Use only trusted project templates, as they might
                execute code with the same level of access as your user.\n
                """
            )
        )
    )
    USAGE = dedent(
        """\
        copier [MAIN_SWITCHES] [copy] [SUB_SWITCHES] template_src destination_path
        copier [MAIN_SWITCHES] [update] [SUB_SWITCHES] [destination_path]
        """
    )
    VERSION = copier_version()
    CALL_MAIN_IF_NESTED_COMMAND = False
    data: AnyByStrDict = {}

    answers_file: cli.SwitchAttr = cli.SwitchAttr(
        ["-a", "--answers-file"],
        default=None,
        help=(
            "Update using this path (relative to `destination_path`) "
            "to find the answers file"
        ),
    )
    exclude: cli.SwitchAttr = cli.SwitchAttr(
        ["-x", "--exclude"],
        str,
        list=True,
        help=(
            "A name or shell-style pattern matching files or folders "
            "that must not be copied"
        ),
    )
    vcs_ref: cli.SwitchAttr = cli.SwitchAttr(
        ["-r", "--vcs-ref"],
        str,
        help=(
            "Git reference to checkout in `template_src`. "
            "If you do not specify it, it will try to checkout the latest git tag, "
            "as sorted using the PEP 440 algorithm. If you want to checkout always "
            "the latest version, use `--vcs-ref=HEAD`."
        ),
    )
    pretend: cli.Flag = cli.Flag(
        ["-n", "--pretend"], help="Run but do not make any changes"
    )
    force: cli.Flag = cli.Flag(
        ["-f", "--force"],
        help="Same as `--defaults --overwrite`.",
    )
    defaults: cli.Flag = cli.Flag(
        ["-l", "--defaults"],
        help="Use default answers to questions, which might be null if not specified.",
    )
    overwrite: cli.Flag = cli.Flag(
        ["-w", "--overwrite"],
        help="Overwrite files that already exist, without asking.",
    )
    skip: cli.Flag = cli.SwitchAttr(
        ["-s", "--skip"],
        str,
        list=True,
        help="Skip specified files if they exist already",
    )
    quiet: cli.Flag = cli.Flag(["-q", "--quiet"], help="Suppress status output")
    prereleases: cli.Flag = cli.Flag(
        ["-g", "--prereleases"],
        help="Use prereleases to compare template VCS tags.",
    )

    @cli.switch(
        ["-d", "--data"],
        str,
        "VARIABLE=VALUE",
        list=True,
        help="Make VARIABLE available as VALUE when rendering the template",
    )
    def data_switch(self, values: List[str]) -> None:
        """Update [data][] with provided values.

        Arguments:
            values: The list of values to apply.
                Each value in the list is of the following form: `NAME=VALUE`.
        """
        for arg in values:
            key, value = arg.split("=", 1)
            value = yaml.safe_load(value)
            self.data[key] = value

    def _worker(self, src_path: OptStr = None, dst_path: str = ".", **kwargs) -> Worker:
        """
        Run Copier's internal API using CLI switches.

        Arguments:
            src_path: The source path of the template to generate the project from.
            dst_path: The path to generate the project to.
            **kwargs: Arguments passed to [Worker][copier.main.Worker].
        """
        return Worker(
            data=self.data,
            dst_path=Path(dst_path),
            answers_file=self.answers_file,
            exclude=self.exclude,
            defaults=self.force or self.defaults,
            overwrite=self.force or self.overwrite,
            pretend=self.pretend,
            quiet=self.quiet,
            src_path=src_path,
            vcs_ref=self.vcs_ref,
            use_prereleases=self.prereleases,
            **kwargs,
        )

    @handle_exceptions
    def main(self, *args: str) -> int:
        """Copier CLI app shortcuts.

        This method redirects abstract CLI calls
        (those that don't specify `copy` or `update`)
        to [CopierCopySubApp.main][copier.cli.CopierCopySubApp.main]
        or [CopierUpdateSubApp.main][copier.cli.CopierUpdateSubApp.main]
        automatically.

        Examples:
            - `copier from to` → `copier copy from to`
            - `copier from` → `copier update from`
            - `copier` → `copier update .`
        """
        # Redirect to subcommand if supplied
        if args and args[0] in self._subcommands:
            self.nested_command = (
                self._subcommands[args[0]].subapplication,
                ["copier %s" % args[0]] + list(args[1:]),
            )
        # If using 0 or 1 args, you want to update
        elif len(args) in {0, 1}:
            self.nested_command = (
                self._subcommands["update"].subapplication,
                ["copier update"] + list(args),
            )
        # If using 2 args, you want to copy
        elif len(args) == 2:
            self.nested_command = (
                self._subcommands["copy"].subapplication,
                ["copier copy"] + list(args),
            )
        # If using more args, you're wrong
        else:
            self.help()
            raise UserMessageError("Unsupported arguments")
        return 0

data_switch(values)

Update data with provided values.

Parameters:

Name Type Description Default
values List[str]

The list of values to apply. Each value in the list is of the following form: NAME=VALUE.

required
Source code in copier/cli.py
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
@cli.switch(
    ["-d", "--data"],
    str,
    "VARIABLE=VALUE",
    list=True,
    help="Make VARIABLE available as VALUE when rendering the template",
)
def data_switch(self, values: List[str]) -> None:
    """Update [data][] with provided values.

    Arguments:
        values: The list of values to apply.
            Each value in the list is of the following form: `NAME=VALUE`.
    """
    for arg in values:
        key, value = arg.split("=", 1)
        value = yaml.safe_load(value)
        self.data[key] = value

main(*args)

Copier CLI app shortcuts.

This method redirects abstract CLI calls (those that don't specify copy or update) to CopierCopySubApp.main or CopierUpdateSubApp.main automatically.

Examples:

  • copier from tocopier copy from to
  • copier fromcopier update from
  • copiercopier update .
Source code in copier/cli.py
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
@handle_exceptions
def main(self, *args: str) -> int:
    """Copier CLI app shortcuts.

    This method redirects abstract CLI calls
    (those that don't specify `copy` or `update`)
    to [CopierCopySubApp.main][copier.cli.CopierCopySubApp.main]
    or [CopierUpdateSubApp.main][copier.cli.CopierUpdateSubApp.main]
    automatically.

    Examples:
        - `copier from to` → `copier copy from to`
        - `copier from` → `copier update from`
        - `copier` → `copier update .`
    """
    # Redirect to subcommand if supplied
    if args and args[0] in self._subcommands:
        self.nested_command = (
            self._subcommands[args[0]].subapplication,
            ["copier %s" % args[0]] + list(args[1:]),
        )
    # If using 0 or 1 args, you want to update
    elif len(args) in {0, 1}:
        self.nested_command = (
            self._subcommands["update"].subapplication,
            ["copier update"] + list(args),
        )
    # If using 2 args, you want to copy
    elif len(args) == 2:
        self.nested_command = (
            self._subcommands["copy"].subapplication,
            ["copier copy"] + list(args),
        )
    # If using more args, you're wrong
    else:
        self.help()
        raise UserMessageError("Unsupported arguments")
    return 0

CopierCopySubApp

Bases: cli.Application

The copier copy subcommand.

Use this subcommand to bootstrap a new subproject from a template, or to override a preexisting subproject ignoring its history diff.

Attributes:

Name Type Description
cleanup_on_error cli.Flag

Set cleanup_on_error option.

Source code in copier/cli.py
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
@CopierApp.subcommand("copy")
class CopierCopySubApp(cli.Application):
    """The `copier copy` subcommand.

    Use this subcommand to bootstrap a new subproject from a template, or to override
    a preexisting subproject ignoring its history diff.

    Attributes:
        cleanup_on_error: Set [cleanup_on_error][] option.
    """

    DESCRIPTION = "Copy from a template source to a destination."

    cleanup_on_error: cli.Flag = cli.Flag(
        ["-C", "--no-cleanup"],
        default=True,
        help="On error, do not delete destination if it was created by Copier.",
    )

    @handle_exceptions
    def main(self, template_src: str, destination_path: str) -> int:
        """Call [run_copy][copier.main.Worker.run_copy].

        Params:
            template_src:
                Indicate where to get the template from.

                This can be a git URL or a local path.

            destination_path:
                Where to generate the new subproject. It must not exist or be empty.
        """
        self.parent._worker(
            template_src,
            destination_path,
            cleanup_on_error=self.cleanup_on_error,
        ).run_copy()
        return 0

main(template_src, destination_path)

Call run_copy.

Parameters:

Name Type Description Default
template_src str

Indicate where to get the template from.

This can be a git URL or a local path.

required
destination_path str

Where to generate the new subproject. It must not exist or be empty.

required
Source code in copier/cli.py
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
@handle_exceptions
def main(self, template_src: str, destination_path: str) -> int:
    """Call [run_copy][copier.main.Worker.run_copy].

    Params:
        template_src:
            Indicate where to get the template from.

            This can be a git URL or a local path.

        destination_path:
            Where to generate the new subproject. It must not exist or be empty.
    """
    self.parent._worker(
        template_src,
        destination_path,
        cleanup_on_error=self.cleanup_on_error,
    ).run_copy()
    return 0

CopierUpdateSubApp

Bases: cli.Application

The copier update subcommand.

Use this subcommand to update an existing subproject from a template that supports updates.

Source code in copier/cli.py
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
@CopierApp.subcommand("update")
class CopierUpdateSubApp(cli.Application):
    """The `copier update` subcommand.

    Use this subcommand to update an existing subproject from a template
    that supports updates.
    """

    DESCRIPTION = "Update a copy from its original template"
    DESCRIPTION_MORE = dedent(
        """\
        The copy must have a valid answers file which contains info
        from the last Copier execution, including the source template
        (it must be a key called `_src_path`).

        If that file contains also `_commit` and `destination_path` is a git
        repository, this command will do its best to respect the diff that you have
        generated since the last `copier` execution. To avoid that, use `copier copy`
        instead.
        """
    )

    @handle_exceptions
    def main(self, destination_path: cli.ExistingDirectory = ".") -> int:
        """Call [run_update][copier.main.Worker.run_update].

        Parameters:
            destination_path:
                Only the destination path is needed to update, because the
                `src_path` comes from [the answers file][the-copier-answersyml-file].

                The subproject must exist. If not specified, the currently
                working directory is used.
        """
        self.parent._worker(
            dst_path=destination_path,
        ).run_update()
        return 0

main(destination_path='.')

Call run_update.

Parameters:

Name Type Description Default
destination_path cli.ExistingDirectory

Only the destination path is needed to update, because the src_path comes from the answers file.

The subproject must exist. If not specified, the currently working directory is used.

'.'
Source code in copier/cli.py
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
@handle_exceptions
def main(self, destination_path: cli.ExistingDirectory = ".") -> int:
    """Call [run_update][copier.main.Worker.run_update].

    Parameters:
        destination_path:
            Only the destination path is needed to update, because the
            `src_path` comes from [the answers file][the-copier-answersyml-file].

            The subproject must exist. If not specified, the currently
            working directory is used.
    """
    self.parent._worker(
        dst_path=destination_path,
    ).run_update()
    return 0

handle_exceptions(method)

Handle keyboard interruption while running a method.

Source code in copier/cli.py
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
def handle_exceptions(method):
    """Handle keyboard interruption while running a method."""

    @wraps(method)
    def _wrapper(*args, **kwargs):
        try:
            try:
                return method(*args, **kwargs)
            except KeyboardInterrupt:
                raise UserMessageError("Execution stopped by user")
        except UserMessageError as error:
            print(colors.red | "\n".join(error.args), file=sys.stderr)
            return 1

    return _wrapper