Coverage for src / rhiza / models / template.py: 100%
46 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-02 07:04 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-02 07:04 +0000
1"""Template model for Rhiza configuration."""
3from dataclasses import dataclass, field
4from enum import StrEnum
5from typing import TYPE_CHECKING, Any
7from rhiza.models._base import YamlSerializable
8from rhiza.models._git_utils import _normalize_to_list
10if TYPE_CHECKING:
11 pass
14class GitHost(StrEnum):
15 """Supported git hosting platforms."""
17 GITHUB = "github"
18 GITLAB = "gitlab"
21@dataclass(kw_only=True, frozen=True)
22class RhizaTemplate(YamlSerializable):
23 """Represents the structure of .rhiza/template.yml.
25 Attributes:
26 template_repository: The GitHub or GitLab repository containing templates (e.g., "jebel-quant/rhiza").
27 Can be None if not specified in the template file.
28 template_branch: The branch to use from the template repository.
29 Can be None if not specified in the template file (defaults to "main" when creating).
30 template_host: The git hosting platform ("github" or "gitlab").
31 Defaults to "github" if not specified in the template file.
32 language: The programming language of the project ("python", "go", etc.).
33 Defaults to "python" if not specified in the template file.
34 include: List of paths to include from the template repository (path-based mode).
35 exclude: List of paths to exclude from the template repository (default: empty list).
36 templates: List of template names to include (template-based mode).
37 Can be used together with include to merge paths.
38 template_bundles_path: Path to the bundle definitions file inside the upstream
39 template repository. Defaults to ``.rhiza/template-bundles.yml``.
40 """
42 template_repository: str = ""
43 template_branch: str = ""
44 template_host: GitHost | str = GitHost.GITHUB
45 language: str = "python"
46 include: list[str] = field(default_factory=list)
47 exclude: list[str] = field(default_factory=list)
48 templates: list[str] = field(default_factory=list)
49 template_bundles_path: str = ".rhiza/template-bundles.yml"
51 @classmethod
52 def from_config(cls, config: dict[str, Any]) -> "RhizaTemplate":
53 """Create a RhizaTemplate instance from a configuration dictionary.
55 Args:
56 config: Dictionary containing template configuration.
58 Returns:
59 A new RhizaTemplate instance.
60 """
61 # Support both 'repository' and 'template-repository' (repository takes precedence)
62 # Empty or None values fall back to the alternative field
63 template_repository = config.get("repository") or config.get("template-repository")
65 # Support both 'ref' and 'template-branch' (ref takes precedence)
66 # Empty or None values fall back to the alternative field
67 template_branch = config.get("ref") or config.get("template-branch")
69 return cls(
70 template_repository=template_repository or "",
71 template_branch=template_branch or "",
72 template_host=config.get("template-host", GitHost.GITHUB),
73 language=config.get("language", "python"),
74 include=_normalize_to_list(config.get("include")),
75 exclude=_normalize_to_list(config.get("exclude")),
76 templates=_normalize_to_list(config.get("templates")),
77 template_bundles_path=config.get("template-bundles-path", ".rhiza/template-bundles.yml"),
78 )
80 @property
81 def config(self) -> dict[str, Any]:
82 """Read template configuration from the template.yml file."""
83 # Convert to dictionary with YAML-compatible keys
84 config: dict[str, Any] = {}
85 config["repository"] = self.template_repository
86 config["ref"] = self.template_branch
87 config["template-host"] = str(self.template_host)
88 config["language"] = self.language
89 config["templates"] = self.templates
90 config["include"] = self.include
91 config["exclude"] = self.exclude
92 if self.template_bundles_path != ".rhiza/template-bundles.yml":
93 config["template-bundles-path"] = self.template_bundles_path
94 return config
96 @property
97 def git_url(self) -> str:
98 """Construct the HTTPS clone URL for this template repository.
100 Returns:
101 HTTPS clone URL derived from ``template_repository`` and
102 ``template_host``.
104 Raises:
105 ValueError: If ``template_repository`` is not set or
106 ``template_host`` is not ``"github"`` or ``"gitlab"``.
107 """
108 if not self.template_repository:
109 raise ValueError("template_repository is not configured in template.yml") # noqa: TRY003
110 host = self.template_host or GitHost.GITHUB
111 if host == GitHost.GITHUB:
112 return f"https://github.com/{self.template_repository}.git"
113 if host == GitHost.GITLAB:
114 return f"https://gitlab.com/{self.template_repository}.git"
115 raise ValueError(f"Unsupported template-host: {host}. Must be 'github' or 'gitlab'.") # noqa: TRY003