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

CLASS DESCRIPTION
NavaWorker

Some (hopefully) small tweaks of upstream functionality.

FUNCTION DESCRIPTION
render_template_file

Hackily render an individual file with the template settings.

run_copy

Copy a template to a destination, from zero.

run_update

Update a subproject, from its template.

NavaWorker(src_exclude: Sequence[str] = ())

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.

METHOD DESCRIPTION
__enter__

Allow using worker as a context manager.

__exit__

Clean up garbage files after worker usage ends.

render_template_file

Render an individual file with the template settings.

run_copy

Generate a subproject from zero, ignoring what was in the folder.

run_recopy

Update a subproject, keeping answers but discarding evolution.

run_update

Update a subproject that was already generated.

ATTRIBUTE DESCRIPTION
all_exclusions

Combine default, template and user-chosen exclusions.

TYPE: Sequence[str]

all_src_exclusions

Combine template and user-chosen exclusions.

TYPE: Sequence[str]

answers_relpath

Obtain the proper relative path for the answers file.

TYPE: Path

jinja_env

Return a pre-configured Jinja environment.

TYPE: SandboxedEnvironment

match_exclude

Get a callable to match paths against all exclusions.

TYPE: Callable[[Path], bool]

match_skip

Get a callable to match paths against all skip-if-exists patterns.

TYPE: Callable[[Path], bool]

match_src_exclude

Get a callable to match paths against src file exclusions.

TYPE: Callable[[Path], bool]

subproject

Get related subproject.

TYPE: Subproject

template

Get related template.

TYPE: Template

template_copy_root

Absolute path from where to start copying.

TYPE: Path

all_exclusions: Sequence[str]

Combine default, template and user-chosen exclusions.

all_src_exclusions: Sequence[str]

Combine template and user-chosen exclusions.

answers_relpath: Path

Obtain the proper relative path for the answers file.

It comes from:

  1. User choice.
  2. Template default.
  3. Copier default.
jinja_env: SandboxedEnvironment

Return a pre-configured Jinja environment.

Respects template settings.

match_exclude: Callable[[Path], bool]

Get a callable to match paths against all exclusions.

match_skip: Callable[[Path], bool]

Get a callable to match paths against all skip-if-exists patterns.

match_src_exclude: Callable[[Path], bool]

Get a callable to match paths against src file exclusions.

subproject: Subproject

Get related subproject.

template: Template

Get related template.

template_copy_root: Path

Absolute path from where to start copying.

It points to the cloned template local abspath + the rendered subdir, if any.

__enter__() -> Self

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
__exit__(type: None, value: None, traceback: None) -> None
__exit__(
    type: type[BaseException], value: BaseException, traceback: TracebackType
) -> None
__exit__(
    type: type[BaseException] | None,
    value: BaseException | None,
    traceback: TracebackType | None,
) -> None

Clean up garbage files after worker usage ends.

Source code in copier/main.py
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
def __exit__(
    self,
    type: type[BaseException] | None,
    value: BaseException | None,
    traceback: TracebackType | None,
) -> None:
    """Clean up garbage files after worker usage ends."""
    if value is not None:
        # exception was raised from code inside context manager:
        # try to clean up, ignoring any exception, then re-raise
        with suppress(Exception):
            self._cleanup()
        raise value
    # otherwise clean up and let any exception bubble up
    self._cleanup()
render_template_file(
    src_file_path: Path,
    data: AnyByStrDict | None = None,
    render_path: Path | None = None,
) -> None

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)
run_copy() -> None

Generate a subproject from zero, ignoring what was in the folder.

If dst_path was missing, it will be created. Otherwise, src_path be rendered directly into it, without worrying about evolving what was there already.

See [generating a project][generating-a-project].

Source code in copier/main.py
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
def run_copy(self) -> None:
    """Generate a subproject from zero, ignoring what was in the folder.

    If `dst_path` was missing, it will be
    created. Otherwise, `src_path` be rendered
    directly into it, without worrying about evolving what was there
    already.

    See [generating a project][generating-a-project].
    """
    self._check_unsafe("copy")
    self._print_message(self.template.message_before_copy)
    self._ask()
    was_existing = self.subproject.local_abspath.exists()
    try:
        if not self.quiet:
            # TODO Unify printing tools
            print(
                f"\nCopying from template version {self.template.version}",
                file=sys.stderr,
            )
        self._render_template()
        if not self.quiet:
            # TODO Unify printing tools
            print("")  # padding space
        if not self.skip_tasks:
            self._execute_tasks(self.template.tasks)
    except Exception:
        if not was_existing and self.cleanup_on_error:
            rmtree(self.subproject.local_abspath)
        raise
    self._print_message(self.template.message_after_copy)
    if not self.quiet:
        # TODO Unify printing tools
        print("")  # padding space
run_recopy() -> None

Update a subproject, keeping answers but discarding evolution.

Source code in copier/main.py
858
859
860
861
862
863
864
865
866
def run_recopy(self) -> None:
    """Update a subproject, keeping answers but discarding evolution."""
    if self.subproject.template is None:
        raise UserMessageError(
            "Cannot recopy because cannot obtain old template references "
            f"from `{self.subproject.answers_relpath}`."
        )
    with replace(self, src_path=self.subproject.template.url) as new_worker:
        new_worker.run_copy()
run_update() -> None

Update a subproject that was already generated.

See updating a project.

Source code in copier/main.py
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
def run_update(self) -> None:
    """Update a subproject that was already generated.

    See [updating a project][updating-a-project].
    """
    self._check_unsafe("update")
    # Check all you need is there
    if self.subproject.vcs != "git":
        raise UserMessageError(
            "Updating is only supported in git-tracked subprojects."
        )
    if self.subproject.is_dirty():
        raise UserMessageError(
            "Destination repository is dirty; cannot continue. "
            "Please commit or stash your local changes and retry."
        )
    if self.subproject.template is None or self.subproject.template.ref is None:
        raise UserMessageError(
            "Cannot update because cannot obtain old template references "
            f"from `{self.subproject.answers_relpath}`."
        )
    if self.template.commit is None:
        raise UserMessageError(
            "Updating is only supported in git-tracked templates."
        )
    if not self.subproject.template.version:
        raise UserMessageError(
            "Cannot update: version from last update not detected."
        )
    if not self.template.version:
        raise UserMessageError("Cannot update: version from template not detected.")
    if self.subproject.template.version > self.template.version:
        raise UserMessageError(
            f"You are downgrading from {self.subproject.template.version} to {self.template.version}. "
            "Downgrades are not supported."
        )
    if not self.overwrite:
        # Only git-tracked subprojects can be updated, so the user can
        # review the diff before committing; so we can safely avoid
        # asking for confirmation
        raise UserMessageError("Enable overwrite to update a subproject.")
    self._print_message(self.template.message_before_update)
    if not self.quiet:
        # TODO Unify printing tools
        print(
            f"Updating to template version {self.template.version}", file=sys.stderr
        )
    self._apply_update()
    self._print_message(self.template.message_after_update)
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.

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
run_copy(
    src_path: str,
    dst_path: StrOrPath = ".",
    data: AnyByStrDict | None = None,
    **kwargs: Any,
) -> 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
run_update(
    dst_path: StrOrPath = ".", data: AnyByStrDict | None = None, **kwargs: Any
) -> 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