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
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
@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")

    def is_dirty(self) -> bool:
        """Indicates 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(git("status", "--porcelain").strip())
        return False

    @property
    def _raw_answers(self) -> AnyByStrDict:
        """The 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) -> Optional[Template]:
        """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:
            return Template(url=last_url, ref=last_ref)

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

is_dirty()

Indicates if the local template root is dirty.

Only applicable for VCS-tracked templates.

Source code in copier/subproject.py
41
42
43
44
45
46
47
48
49
def is_dirty(self) -> bool:
    """Indicates 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(git("status", "--porcelain").strip())
    return False

last_answers()

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

Source code in copier/subproject.py
61
62
63
64
65
66
67
68
@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("_")
    }

template()

Template, as it was used the last time.

Source code in copier/subproject.py
70
71
72
73
74
75
76
@cached_property
def template(self) -> Optional[Template]:
    """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:
        return Template(url=last_url, ref=last_ref)

vcs()

VCS type of the subproject.

Source code in copier/subproject.py
78
79
80
81
82
@cached_property
def vcs(self) -> Optional[VCSTypes]:
    """VCS type of the subproject."""
    if is_in_git_repo(self.local_abspath):
        return "git"