rhiza 0.5.0__py3-none-any.whl → 0.5.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
rhiza/__init__.py CHANGED
@@ -4,6 +4,4 @@ This package groups small, user-facing utilities that can be invoked from
4
4
  the command line or other automation scripts.
5
5
  """
6
6
 
7
- from rhiza.models import RhizaTemplate
8
-
9
- __all__ = ["RhizaTemplate"]
7
+ __all__ = ["commands", "models"]
rhiza/cli.py CHANGED
@@ -8,9 +8,9 @@ from pathlib import Path
8
8
 
9
9
  import typer
10
10
 
11
- from rhiza.commands.init import init as init_cmd
12
- from rhiza.commands.materialize import materialize as materialize_cmd
13
- from rhiza.commands.validate import validate as validate_cmd
11
+ from rhiza.commands import init as init_cmd
12
+ from rhiza.commands import materialize as materialize_cmd
13
+ from rhiza.commands import validate as validate_cmd
14
14
 
15
15
  app = typer.Typer(
16
16
  help="Rhiza - Manage reusable configuration templates for Python projects",
@@ -3,3 +3,7 @@
3
3
  This package contains the functions that back Typer commands exposed by
4
4
  `rhiza.cli`, such as `hello` and `inject`.
5
5
  """
6
+
7
+ from .init import init # noqa: F401
8
+ from .materialize import materialize # noqa: F401
9
+ from .validate import validate # noqa: F401
rhiza/commands/init.py CHANGED
@@ -19,10 +19,8 @@ def init(target: Path):
19
19
  Creates a default .github/template.yml file if it doesn't exist,
20
20
  or validates an existing one.
21
21
 
22
- Parameters
23
- ----------
24
- target:
25
- Path to the target directory. Defaults to the current working directory.
22
+ Args:
23
+ target: Path to the target directory. Defaults to the current working directory.
26
24
  """
27
25
  # Convert to absolute path to avoid surprises
28
26
  target = target.resolve()
@@ -1,20 +1,11 @@
1
- """Command-line helpers for working with Rhiza templates.
2
-
3
- This module currently exposes a thin wrapper that shells out to the
4
- `tools/inject_rhiza.sh` script. It exists so the functionality can be
5
- invoked via a Python entry point while delegating the heavy lifting to
6
- the maintained shell script.
7
- """
8
-
9
1
  import shutil
10
2
  import subprocess
11
- import sys
12
3
  import tempfile
13
4
  from pathlib import Path
14
5
 
15
6
  from loguru import logger
16
7
 
17
- from rhiza.commands.init import init
8
+ from rhiza.commands import init
18
9
  from rhiza.models import RhizaTemplate
19
10
 
20
11
 
@@ -37,46 +28,44 @@ def expand_paths(base_dir: Path, paths: list[str]) -> list[Path]:
37
28
  return all_files
38
29
 
39
30
 
40
- def materialize(target: Path, branch: str, force: bool):
41
- """Materialize rhiza templates into TARGET repository."""
42
- # Convert to absolute path to avoid surprises
31
+ def materialize(target: Path, branch: str, force: bool) -> None:
32
+ """Materialize Rhiza templates into the target repository.
33
+
34
+ This performs a sparse checkout of the template repository and copies
35
+ the selected files into the target repository, recording all files
36
+ under template control in `.rhiza.history`.
37
+ """
43
38
  target = target.resolve()
44
39
 
45
40
  logger.info(f"Target repository: {target}")
46
41
  logger.info(f"Rhiza branch: {branch}")
47
42
 
48
43
  # -----------------------
49
- # Ensure template.yml
44
+ # Ensure Rhiza is initialized
50
45
  # -----------------------
51
- template_file = target / ".github" / "template.yml"
52
- # template_file.parent.mkdir(parents=True, exist_ok=True)
53
-
54
- # Initialize rhiza if not already initialized, e.g. construct a template.yml file
55
46
  init(target)
56
47
 
57
- # -----------------------
58
- # Load template.yml
59
- # -----------------------
48
+ template_file = target / ".github" / "template.yml"
60
49
  template = RhizaTemplate.from_yaml(template_file)
61
50
 
62
51
  rhiza_repo = template.template_repository
63
- # Use template branch if specified, otherwise fall back to CLI parameter
64
- rhiza_branch = template.template_branch if template.template_branch else branch
52
+ rhiza_branch = template.template_branch or branch
65
53
  include_paths = template.include
66
54
  excluded_paths = template.exclude
67
55
 
68
56
  if not include_paths:
69
- logger.error("No include paths found in template.yml")
70
- raise sys.exit(1)
57
+ raise RuntimeError("No include paths found in template.yml")
71
58
 
72
59
  logger.info("Include paths:")
73
60
  for p in include_paths:
74
61
  logger.info(f" - {p}")
75
62
 
76
63
  # -----------------------
77
- # Sparse clone rhiza
64
+ # Sparse clone template repo
78
65
  # -----------------------
79
66
  tmp_dir = Path(tempfile.mkdtemp())
67
+ materialized_files: list[Path] = []
68
+
80
69
  logger.info(f"Cloning {rhiza_repo}@{rhiza_branch} into temporary directory")
81
70
 
82
71
  try:
@@ -84,12 +73,10 @@ def materialize(target: Path, branch: str, force: bool):
84
73
  [
85
74
  "git",
86
75
  "clone",
87
- "--depth",
88
- "1",
76
+ "--depth", "1",
89
77
  "--filter=blob:none",
90
78
  "--sparse",
91
- "--branch",
92
- rhiza_branch,
79
+ "--branch", rhiza_branch,
93
80
  f"https://github.com/{rhiza_repo}.git",
94
81
  str(tmp_dir),
95
82
  ],
@@ -97,44 +84,98 @@ def materialize(target: Path, branch: str, force: bool):
97
84
  stdout=subprocess.DEVNULL,
98
85
  )
99
86
 
100
- subprocess.run(["git", "sparse-checkout", "init"], cwd=tmp_dir, check=True)
101
- subprocess.run(["git", "sparse-checkout", "set", "--skip-checks", *include_paths], cwd=tmp_dir, check=True)
87
+ subprocess.run(
88
+ ["git", "sparse-checkout", "init", "--cone"],
89
+ cwd=tmp_dir,
90
+ check=True,
91
+ )
102
92
 
103
- # After sparse-checkout
93
+ subprocess.run(
94
+ ["git", "sparse-checkout", "set", "--skip-checks", *include_paths],
95
+ cwd=tmp_dir,
96
+ check=True,
97
+ )
98
+
99
+ # -----------------------
100
+ # Expand include/exclude paths
101
+ # -----------------------
104
102
  all_files = expand_paths(tmp_dir, include_paths)
105
103
 
106
- # Filter out excluded files
107
- # excluded_set = {tmp_dir / e for e in excluded_paths}
108
- excluded_files = expand_paths(tmp_dir, excluded_paths)
104
+ excluded_files = {
105
+ f.resolve()
106
+ for f in expand_paths(tmp_dir, excluded_paths)
107
+ }
109
108
 
110
- files_to_copy = [f for f in all_files if f not in excluded_files]
111
- # print(files_to_copy)
109
+ files_to_copy = [
110
+ f for f in all_files
111
+ if f.resolve() not in excluded_files
112
+ ]
112
113
 
113
- # Copy loop
114
+ # -----------------------
115
+ # Copy files into target repo
116
+ # -----------------------
114
117
  for src_file in files_to_copy:
115
118
  dst_file = target / src_file.relative_to(tmp_dir)
119
+ relative_path = dst_file.relative_to(target)
120
+
121
+ materialized_files.append(relative_path)
122
+
116
123
  if dst_file.exists() and not force:
117
- logger.warning(f"{dst_file.relative_to(target)} already exists — use force=True to overwrite")
124
+ logger.warning(
125
+ f"{relative_path} already exists — use --force to overwrite"
126
+ )
118
127
  continue
119
128
 
120
129
  dst_file.parent.mkdir(parents=True, exist_ok=True)
121
130
  shutil.copy2(src_file, dst_file)
122
- logger.success(f"[ADD] {dst_file.relative_to(target)}")
131
+ logger.success(f"[ADD] {relative_path}")
123
132
 
124
133
  finally:
125
134
  shutil.rmtree(tmp_dir)
126
135
 
136
+ # -----------------------
137
+ # Warn about workflow files
138
+ # -----------------------
139
+ workflow_files = [
140
+ p for p in materialized_files
141
+ if p.parts[:2] == (".github", "workflows")
142
+ ]
143
+
144
+ if workflow_files:
145
+ logger.warning(
146
+ "Workflow files were materialized. Updating these files requires "
147
+ "a token with the 'workflow' permission in GitHub Actions."
148
+ )
149
+
150
+ # -----------------------
151
+ # Write .rhiza.history
152
+ # -----------------------
153
+ history_file = target / ".rhiza.history"
154
+ with history_file.open("w", encoding="utf-8") as f:
155
+ f.write("# Rhiza Template History\n")
156
+ f.write("# This file lists all files managed by the Rhiza template.\n")
157
+ f.write(f"# Template repository: {rhiza_repo}\n")
158
+ f.write(f"# Template branch: {rhiza_branch}\n")
159
+ f.write("#\n")
160
+ f.write("# Files under template control:\n")
161
+ for file_path in sorted(materialized_files):
162
+ f.write(f"{file_path}\n")
163
+
164
+ logger.info(
165
+ f"Created {history_file.relative_to(target)} "
166
+ f"with {len(materialized_files)} files"
167
+ )
168
+
127
169
  logger.success("Rhiza templates materialized successfully")
128
- logger.info("""
129
- Next steps:
130
- 1. Review changes:
131
- git status
132
- git diff
133
-
134
- 2. Commit:
135
- git add .
136
- git commit -m "chore: import rhiza templates"
137
-
138
- This is a one-shot snapshot.
139
- Re-run this script to update templates explicitly.
140
- """)
170
+
171
+ logger.info(
172
+ "Next steps:\n"
173
+ " 1. Review changes:\n"
174
+ " git status\n"
175
+ " git diff\n\n"
176
+ " 2. Commit:\n"
177
+ " git add .\n"
178
+ ' git commit -m "chore: import rhiza templates"\n\n'
179
+ "This is a one-shot snapshot.\n"
180
+ "Re-run this command to update templates explicitly."
181
+ )
@@ -19,14 +19,10 @@ def validate(target: Path) -> bool:
19
19
  - Validates required fields
20
20
  - Validates field values are appropriate
21
21
 
22
- Parameters
23
- ----------
24
- target:
25
- Path to the target Git repository directory.
22
+ Args:
23
+ target: Path to the target Git repository directory.
26
24
 
27
25
  Returns:
28
- -------
29
- bool
30
26
  True if validation passes, False otherwise.
31
27
  """
32
28
  # Convert to absolute path
rhiza/models.py CHANGED
@@ -16,17 +16,12 @@ class RhizaTemplate:
16
16
  """Represents the structure of .github/template.yml.
17
17
 
18
18
  Attributes:
19
- ----------
20
- template_repository : str | None
21
- The GitHub repository containing templates (e.g., "jebel-quant/rhiza").
22
- Can be None if not specified in the template file.
23
- template_branch : str | None
24
- The branch to use from the template repository.
25
- Can be None if not specified in the template file (defaults to "main" when creating).
26
- include : list[str]
27
- List of paths to include from the template repository.
28
- exclude : list[str]
29
- List of paths to exclude from the template repository (default: empty list).
19
+ template_repository: The GitHub repository containing templates (e.g., "jebel-quant/rhiza").
20
+ Can be None if not specified in the template file.
21
+ template_branch: The branch to use from the template repository.
22
+ Can be None if not specified in the template file (defaults to "main" when creating).
23
+ include: List of paths to include from the template repository.
24
+ exclude: List of paths to exclude from the template repository (default: empty list).
30
25
  """
31
26
 
32
27
  template_repository: str | None = None
@@ -38,24 +33,16 @@ class RhizaTemplate:
38
33
  def from_yaml(cls, file_path: Path) -> "RhizaTemplate":
39
34
  """Load RhizaTemplate from a YAML file.
40
35
 
41
- Parameters
42
- ----------
43
- file_path : Path
44
- Path to the template.yml file.
36
+ Args:
37
+ file_path: Path to the template.yml file.
45
38
 
46
39
  Returns:
47
- -------
48
- RhizaTemplate
49
40
  The loaded template configuration.
50
41
 
51
42
  Raises:
52
- ------
53
- FileNotFoundError
54
- If the file does not exist.
55
- yaml.YAMLError
56
- If the YAML is malformed.
57
- ValueError
58
- If the file is empty.
43
+ FileNotFoundError: If the file does not exist.
44
+ yaml.YAMLError: If the YAML is malformed.
45
+ ValueError: If the file is empty.
59
46
  """
60
47
  with open(file_path) as f:
61
48
  config = yaml.safe_load(f)
@@ -73,10 +60,8 @@ class RhizaTemplate:
73
60
  def to_yaml(self, file_path: Path) -> None:
74
61
  """Save RhizaTemplate to a YAML file.
75
62
 
76
- Parameters
77
- ----------
78
- file_path : Path
79
- Path where the template.yml file should be saved.
63
+ Args:
64
+ file_path: Path where the template.yml file should be saved.
80
65
  """
81
66
  # Ensure parent directory exists
82
67
  file_path.parent.mkdir(parents=True, exist_ok=True)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rhiza
3
- Version: 0.5.0
3
+ Version: 0.5.2
4
4
  Summary: Reusable configuration templates for modern Python projects
5
5
  Project-URL: Homepage, https://github.com/jebel-quant/rhiza-cli
6
6
  Project-URL: Repository, https://github.com/jebel-quant/rhiza-cli
@@ -25,7 +25,7 @@ Requires-Dist: typer>=0.20.0
25
25
  Provides-Extra: dev
26
26
  Requires-Dist: marimo==0.18.4; extra == 'dev'
27
27
  Requires-Dist: pdoc>=16.0.0; extra == 'dev'
28
- Requires-Dist: pre-commit==4.5.0; extra == 'dev'
28
+ Requires-Dist: pre-commit==4.5.1; extra == 'dev'
29
29
  Requires-Dist: pytest-cov>=7.0.0; extra == 'dev'
30
30
  Requires-Dist: pytest-html>=4.1.1; extra == 'dev'
31
31
  Requires-Dist: pytest==9.0.2; extra == 'dev'
@@ -35,6 +35,9 @@ Description-Content-Type: text/markdown
35
35
 
36
36
  [![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/downloads/)
37
37
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
38
+ [![PyPI version](https://img.shields.io/pypi/v/rhiza.svg)](https://pypi.org/project/rhiza/)
39
+ [![Coverage](https://img.shields.io/badge/coverage-report-brightgreen.svg)](https://jebel-quant.github.io/rhiza-cli/tests/html-coverage/index.html)
40
+ [![Downloads](https://static.pepy.tech/personalized-badge/rhiza?period=month&units=international_system&left_color=black&right_color=orange&left_text=PyPI%20downloads%20per%20month)](https://pepy.tech/project/rhiza)
38
41
 
39
42
  Command-line interface for managing reusable configuration templates for modern Python projects.
40
43
 
@@ -675,9 +678,11 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
675
678
 
676
679
  ## Links
677
680
 
681
+ - **PyPI:** https://pypi.org/project/rhiza/
678
682
  - **Repository:** https://github.com/jebel-quant/rhiza-cli
679
683
  - **Issues:** https://github.com/jebel-quant/rhiza-cli/issues
680
684
  - **Documentation:** Generated with `make docs`
685
+ - **Companion Book:** https://jebel-quant.github.io/rhiza-cli/ (includes coverage report, API docs, and notebooks)
681
686
 
682
687
  ## Architecture
683
688
 
@@ -0,0 +1,13 @@
1
+ rhiza/__init__.py,sha256=1AECbLiERqRhdcFkVxHk3vEK4KPWll-D05CTAHZDI2A,224
2
+ rhiza/__main__.py,sha256=Lx0GqVZo6ymm0f18_uYB6E7_SOWwJNYjb73Vr31oLoM,236
3
+ rhiza/cli.py,sha256=v7VaGUEnfuGRMOGh8I7Luh-QiUHj0to7tsSXVFuUyts,3458
4
+ rhiza/models.py,sha256=-n5eyPcU35IVsbvy7F-kyKR343TkOrx25kooLCF9whg,3001
5
+ rhiza/commands/__init__.py,sha256=KcoFX52xQ1NFdgVeGsAkIaqmJrRso1GOq0vFL0WEB44,300
6
+ rhiza/commands/init.py,sha256=K8NN9x_gMWeoD1gx9PCDkGUCN3luxZgp7tVD2jCqT94,1929
7
+ rhiza/commands/materialize.py,sha256=6dE5uG41HkSERFqupS29PaHh32lveHRvbxEttaCfBpk,5525
8
+ rhiza/commands/validate.py,sha256=itcLg44GiuZ7hZ67Bscj1RavdERlWgq40rJDaYOp2zM,4829
9
+ rhiza-0.5.2.dist-info/METADATA,sha256=3X4vrQOv-eb1Znj_VAq8J5OmHAPI3V6vgM4dNTSasaw,19938
10
+ rhiza-0.5.2.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
11
+ rhiza-0.5.2.dist-info/entry_points.txt,sha256=NAwZUpbXvfKv50a_Qq-PxMHl3lcjAyZO63IBeuUNgfY,45
12
+ rhiza-0.5.2.dist-info/licenses/LICENSE,sha256=4m5X7LhqX-6D0Ks79Ys8CLpmza8cxDG34g4S9XSNAGY,1077
13
+ rhiza-0.5.2.dist-info/RECORD,,
@@ -1,13 +0,0 @@
1
- rhiza/__init__.py,sha256=fxaeT_K8bQAX5qt1DtRKWmyKpk7ABLomxhzZwL6Rml8,259
2
- rhiza/__main__.py,sha256=Lx0GqVZo6ymm0f18_uYB6E7_SOWwJNYjb73Vr31oLoM,236
3
- rhiza/cli.py,sha256=P130eveGtBUTby3A5LNDvgtJ2ekWcAiv84GqZ5kLcw4,3484
4
- rhiza/models.py,sha256=HbWgHPS-sWur4ax7a8tu2B6apr6YEdbGoxOpWxyeP9s,3220
5
- rhiza/commands/__init__.py,sha256=X5ZRDDl37X8mEbiMWoqjTGlLhebkYhZ2SaLJd4KcHdw,166
6
- rhiza/commands/init.py,sha256=wAVlcTdCiU8bN98Gsx8MGryo8fraFLQCvDl5ZlR1ySg,1953
7
- rhiza/commands/materialize.py,sha256=G1pDraC2gqIFuqC9nmIeXPnAHMna2KBIPfPAuLDllec,4412
8
- rhiza/commands/validate.py,sha256=rY04vz71C4ILAcLDaf4y4AtP3Bs5KcaIPCLoA074SA8,4874
9
- rhiza-0.5.0.dist-info/METADATA,sha256=OG-KEbNrDEd802tVMSUacQKuUH8SPOFV8pVxUbrAM3U,19323
10
- rhiza-0.5.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
11
- rhiza-0.5.0.dist-info/entry_points.txt,sha256=NAwZUpbXvfKv50a_Qq-PxMHl3lcjAyZO63IBeuUNgfY,45
12
- rhiza-0.5.0.dist-info/licenses/LICENSE,sha256=4m5X7LhqX-6D0Ks79Ys8CLpmza8cxDG34g4S9XSNAGY,1077
13
- rhiza-0.5.0.dist-info/RECORD,,
File without changes