Skip to content

Copier worker

Tweaked version of the upstream copier functionality.

See the upstream: https://github.com/copier-org/copier/blob/259f351fc3c017c82b235888c119b9010d80494a/copier/main.py

Bases: Worker

Some (hopefully) small tweaks of upstream functionality.

Copier's upstream exclusion logic only runs against paths after they have been rendered. This class supports exclusions based on the paths in the template itself, before they have rendered, via src_exclude which can be specified in the copier.yml file or as arguments in the API call.

Source code in nava/platform/copier_worker.py
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
@dataclass
class NavaWorker(Worker):
    """Some (hopefully) small tweaks of upstream functionality.

    Copier's upstream exclusion logic only runs against paths after they have
    been rendered. This class supports exclusions based on the paths in the
    template itself, _before_ they have rendered, via `src_exclude` which can be
    specified in the `copier.yml` file or as arguments in the API call.
    """

    src_exclude: Sequence[str] = ()

    # just redefining to fix the return type
    def __enter__(self) -> Self:
        """Allow using worker as a context manager."""
        return self

    @cached_property
    def all_src_exclusions(self) -> Sequence[str]:
        """Combine template and user-chosen exclusions."""
        return tuple(self.template.config_data.get("src_exclude", [])) + tuple(self.src_exclude)

    @cached_property
    def match_src_exclude(self) -> Callable[[Path], bool]:
        """Get a callable to match paths against src file exclusions."""
        return self._path_matcher(self.all_src_exclusions)

    def _render_path(self, relpath: Path) -> Path | None:
        # if `_render_path()` returns `None`, `_render_template()` skips the
        # path, so seems like the least invasive place to hook in
        #
        # https://github.com/copier-org/copier/blob/259f351fc3c017c82b235888c119b9010d80494a/copier/main.py#L609-L613
        if self.match_src_exclude(relpath):
            return None

        return super()._render_path(relpath)

    # not a part of the upstream class, but put here for convenience until we
    # find a better approach
    def render_template_file(
        self, src_file_path: Path, data: AnyByStrDict | None = None, render_path: Path | None = None
    ) -> None:
        """Render an individual file with the template settings."""
        src_relpath = src_file_path

        # TODO: upstream is more like:
        #
        #   src_abspath = self.template.local_abspath / src_relpath
        #   self._render_path(Path(src_abspath).relative_to(self.template_copy_root))
        #
        # but that that means the template needs configured correctly/we need to
        # run _ask() first
        dst_relpath = render_path or self._render_path(src_relpath)

        # hack to just pass the data down to Jinja
        self.answers = AnswersMap(user=data or dict())

        if dst_relpath is None or self.match_exclude(dst_relpath):
            return

        self._render_file(src_relpath, dst_relpath)

Combine template and user-chosen exclusions.

Get a callable to match paths against src file exclusions.

Allow using worker as a context manager.

Source code in nava/platform/copier_worker.py
34
35
36
def __enter__(self) -> Self:
    """Allow using worker as a context manager."""
    return self

Render an individual file with the template settings.

Source code in nava/platform/copier_worker.py
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
def render_template_file(
    self, src_file_path: Path, data: AnyByStrDict | None = None, render_path: Path | None = None
) -> None:
    """Render an individual file with the template settings."""
    src_relpath = src_file_path

    # TODO: upstream is more like:
    #
    #   src_abspath = self.template.local_abspath / src_relpath
    #   self._render_path(Path(src_abspath).relative_to(self.template_copy_root))
    #
    # but that that means the template needs configured correctly/we need to
    # run _ask() first
    dst_relpath = render_path or self._render_path(src_relpath)

    # hack to just pass the data down to Jinja
    self.answers = AnswersMap(user=data or dict())

    if dst_relpath is None or self.match_exclude(dst_relpath):
        return

    self._render_file(src_relpath, dst_relpath)

Hackily render an individual file with the template settings.

Source code in nava/platform/copier_worker.py
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
def render_template_file(
    src_path: str,
    src_file_path: StrOrPath,
    dst_path: StrOrPath = ".",
    render_path: StrOrPath | None = None,
    data: AnyByStrDict | None = None,
    **kwargs: Any,
) -> Worker:
    """Hackily render an individual file with the template settings."""
    if data is not None:
        kwargs["data"] = data
    if render_path is not None:
        render_path = Path(render_path)
    with NavaWorker(src_path=src_path, dst_path=Path(dst_path), **kwargs) as worker:
        worker.render_template_file(Path(src_file_path), data, render_path=render_path)
    return worker

Copy a template to a destination, from zero.

Source code in nava/platform/copier_worker.py
84
85
86
87
88
89
90
91
92
93
94
95
def run_copy(
    src_path: str,
    dst_path: StrOrPath = ".",
    data: AnyByStrDict | None = None,
    **kwargs: Any,
) -> Worker:
    """Copy a template to a destination, from zero."""
    if data is not None:
        kwargs["data"] = data
    with NavaWorker(src_path=src_path, dst_path=Path(dst_path), **kwargs) as worker:
        worker.run_copy()
    return worker

Update a subproject, from its template.

Source code in nava/platform/copier_worker.py
 98
 99
100
101
102
103
104
105
106
107
108
def run_update(
    dst_path: StrOrPath = ".",
    data: AnyByStrDict | None = None,
    **kwargs: Any,
) -> Worker:
    """Update a subproject, from its template."""
    if data is not None:
        kwargs["data"] = data
    with NavaWorker(dst_path=Path(dst_path), **kwargs) as worker:
        worker.run_update()
    return worker