Skip to content

subproject.py

Objects to interact with subprojects.

A subproject is a project that gets rendered and/or updated with Copier.

Subproject

Object that represents the subproject and its current state.

Attributes:

Name Type Description
local_abspath AbsolutePath

Absolute path on local disk pointing to the subproject root folder.

answers_relpath Path

Relative path to the answers file.

Source code in copier/subproject.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
82
83
84
85
86
87
88
@dataclass
class Subproject:
    """Object that represents the subproject and its current state.

    Attributes:
        local_abspath:
            Absolute path on local disk pointing to the subproject root folder.

        answers_relpath:
            Relative path to [the answers file][the-copier-answersyml-file].
    """

    local_abspath: AbsolutePath
    answers_relpath: Path = Path(".copier-answers.yml")

    _cleanup_hooks: list[Callable[[], None]] = field(default_factory=list, init=False)

    def is_dirty(self) -> bool:
        """Indicate if the local template root is dirty.

        Only applicable for VCS-tracked templates.
        """
        if self.vcs == "git":
            with local.cwd(self.local_abspath):
                return bool(get_git()("status", "--porcelain").strip())
        return False

    def _cleanup(self) -> None:
        """Remove temporary files and folders created by the subproject."""
        for method in self._cleanup_hooks:
            method()

    @property
    def _raw_answers(self) -> AnyByStrDict:
        """Get last answers, loaded raw as yaml."""
        try:
            return yaml.safe_load(
                (self.local_abspath / self.answers_relpath).read_text()
            )
        except OSError:
            return {}

    @cached_property
    def last_answers(self) -> AnyByStrDict:
        """Last answers, excluding private ones (except _src_path and _commit)."""
        return {
            key: value
            for key, value in self._raw_answers.items()
            if key in {"_src_path", "_commit"} or not key.startswith("_")
        }

    @cached_property
    def template(self) -> Template | None:
        """Template, as it was used the last time."""
        last_url = self.last_answers.get("_src_path")
        last_ref = self.last_answers.get("_commit")
        if last_url:
            result = Template(url=last_url, ref=last_ref)
            self._cleanup_hooks.append(result._cleanup)
            return result
        return None

    @cached_property
    def vcs(self) -> VCSTypes | None:
        """VCS type of the subproject."""
        if is_in_git_repo(self.local_abspath):
            return "git"
        return None

last_answers: AnyByStrDict cached property

Last answers, excluding private ones (except _src_path and _commit).

template: Template | None cached property

Template, as it was used the last time.

vcs: VCSTypes | None cached property

VCS type of the subproject.

is_dirty()

Indicate if the local template root is dirty.

Only applicable for VCS-tracked templates.

Source code in copier/subproject.py
38
39
40
41
42
43
44
45
46
def is_dirty(self) -> bool:
    """Indicate if the local template root is dirty.

    Only applicable for VCS-tracked templates.
    """
    if self.vcs == "git":
        with local.cwd(self.local_abspath):
            return bool(get_git()("status", "--porcelain").strip())
    return False