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 +1 -3
- rhiza/cli.py +3 -3
- rhiza/commands/__init__.py +4 -0
- rhiza/commands/init.py +2 -4
- rhiza/commands/materialize.py +95 -54
- rhiza/commands/validate.py +2 -6
- rhiza/models.py +13 -28
- {rhiza-0.5.0.dist-info → rhiza-0.5.2.dist-info}/METADATA +7 -2
- rhiza-0.5.2.dist-info/RECORD +13 -0
- rhiza-0.5.0.dist-info/RECORD +0 -13
- {rhiza-0.5.0.dist-info → rhiza-0.5.2.dist-info}/WHEEL +0 -0
- {rhiza-0.5.0.dist-info → rhiza-0.5.2.dist-info}/entry_points.txt +0 -0
- {rhiza-0.5.0.dist-info → rhiza-0.5.2.dist-info}/licenses/LICENSE +0 -0
rhiza/__init__.py
CHANGED
rhiza/cli.py
CHANGED
|
@@ -8,9 +8,9 @@ from pathlib import Path
|
|
|
8
8
|
|
|
9
9
|
import typer
|
|
10
10
|
|
|
11
|
-
from rhiza.commands
|
|
12
|
-
from rhiza.commands
|
|
13
|
-
from rhiza.commands
|
|
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",
|
rhiza/commands/__init__.py
CHANGED
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
|
-
|
|
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()
|
rhiza/commands/materialize.py
CHANGED
|
@@ -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
|
|
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
|
|
42
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
101
|
-
|
|
87
|
+
subprocess.run(
|
|
88
|
+
["git", "sparse-checkout", "init", "--cone"],
|
|
89
|
+
cwd=tmp_dir,
|
|
90
|
+
check=True,
|
|
91
|
+
)
|
|
102
92
|
|
|
103
|
-
|
|
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
|
-
|
|
107
|
-
|
|
108
|
-
|
|
104
|
+
excluded_files = {
|
|
105
|
+
f.resolve()
|
|
106
|
+
for f in expand_paths(tmp_dir, excluded_paths)
|
|
107
|
+
}
|
|
109
108
|
|
|
110
|
-
files_to_copy = [
|
|
111
|
-
|
|
109
|
+
files_to_copy = [
|
|
110
|
+
f for f in all_files
|
|
111
|
+
if f.resolve() not in excluded_files
|
|
112
|
+
]
|
|
112
113
|
|
|
113
|
-
#
|
|
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(
|
|
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] {
|
|
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
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
git
|
|
133
|
-
|
|
134
|
-
2. Commit
|
|
135
|
-
git add
|
|
136
|
-
git commit -m "chore: import rhiza templates"
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
+
)
|
rhiza/commands/validate.py
CHANGED
|
@@ -19,14 +19,10 @@ def validate(target: Path) -> bool:
|
|
|
19
19
|
- Validates required fields
|
|
20
20
|
- Validates field values are appropriate
|
|
21
21
|
|
|
22
|
-
|
|
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
|
-
|
|
21
|
-
The
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
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
|
-
|
|
54
|
-
If the file
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
[](https://www.python.org/downloads/)
|
|
37
37
|
[](https://opensource.org/licenses/MIT)
|
|
38
|
+
[](https://pypi.org/project/rhiza/)
|
|
39
|
+
[](https://jebel-quant.github.io/rhiza-cli/tests/html-coverage/index.html)
|
|
40
|
+
[](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,,
|
rhiza-0.5.0.dist-info/RECORD
DELETED
|
@@ -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
|
|
File without changes
|
|
File without changes
|