rhiza 0.5.1__tar.gz → 0.5.3__tar.gz

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.
Files changed (64) hide show
  1. {rhiza-0.5.1 → rhiza-0.5.3}/.github/workflows/sync.yml +0 -3
  2. rhiza-0.5.3/.rhiza.history +45 -0
  3. {rhiza-0.5.1 → rhiza-0.5.3}/PKG-INFO +1 -1
  4. {rhiza-0.5.1 → rhiza-0.5.3}/pyproject.toml +1 -1
  5. {rhiza-0.5.1 → rhiza-0.5.3}/src/rhiza/__init__.py +8 -0
  6. {rhiza-0.5.1 → rhiza-0.5.3}/src/rhiza/cli.py +36 -0
  7. {rhiza-0.5.1 → rhiza-0.5.3}/src/rhiza/commands/init.py +1 -1
  8. {rhiza-0.5.1 → rhiza-0.5.3}/src/rhiza/commands/materialize.py +72 -49
  9. {rhiza-0.5.1 → rhiza-0.5.3}/src/rhiza/commands/validate.py +1 -0
  10. {rhiza-0.5.1 → rhiza-0.5.3}/tests/test_cli_commands.py +23 -0
  11. {rhiza-0.5.1 → rhiza-0.5.3}/uv.lock +1 -1
  12. {rhiza-0.5.1 → rhiza-0.5.3}/.editorconfig +0 -0
  13. {rhiza-0.5.1 → rhiza-0.5.3}/.github/README.md +0 -0
  14. {rhiza-0.5.1 → rhiza-0.5.3}/.github/TOKEN_SETUP.md +0 -0
  15. {rhiza-0.5.1 → rhiza-0.5.3}/.github/actions/setup-project/action.yml +0 -0
  16. {rhiza-0.5.1 → rhiza-0.5.3}/.github/copilot-instructions.md +0 -0
  17. {rhiza-0.5.1 → rhiza-0.5.3}/.github/renovate.json +0 -0
  18. {rhiza-0.5.1 → rhiza-0.5.3}/.github/scripts/book.sh +0 -0
  19. {rhiza-0.5.1 → rhiza-0.5.3}/.github/scripts/bump.sh +0 -0
  20. {rhiza-0.5.1 → rhiza-0.5.3}/.github/scripts/customisations/build-extras.sh +0 -0
  21. {rhiza-0.5.1 → rhiza-0.5.3}/.github/scripts/customisations/post-release.sh +0 -0
  22. {rhiza-0.5.1 → rhiza-0.5.3}/.github/scripts/marimushka.sh +0 -0
  23. {rhiza-0.5.1 → rhiza-0.5.3}/.github/scripts/release.sh +0 -0
  24. {rhiza-0.5.1 → rhiza-0.5.3}/.github/scripts/update-readme-help.sh +0 -0
  25. {rhiza-0.5.1 → rhiza-0.5.3}/.github/template.yml +0 -0
  26. {rhiza-0.5.1 → rhiza-0.5.3}/.github/workflows/book.yml +0 -0
  27. {rhiza-0.5.1 → rhiza-0.5.3}/.github/workflows/ci.yml +0 -0
  28. {rhiza-0.5.1 → rhiza-0.5.3}/.github/workflows/deptry.yml +0 -0
  29. {rhiza-0.5.1 → rhiza-0.5.3}/.github/workflows/marimo.yml +0 -0
  30. {rhiza-0.5.1 → rhiza-0.5.3}/.github/workflows/pre-commit.yml +0 -0
  31. {rhiza-0.5.1 → rhiza-0.5.3}/.github/workflows/release.yml +0 -0
  32. {rhiza-0.5.1 → rhiza-0.5.3}/.github/workflows/scripts/version_matrix.py +0 -0
  33. {rhiza-0.5.1 → rhiza-0.5.3}/.github/workflows/scripts/version_max.py +0 -0
  34. {rhiza-0.5.1 → rhiza-0.5.3}/.github/workflows/structure.yml +0 -0
  35. {rhiza-0.5.1 → rhiza-0.5.3}/.gitignore +0 -0
  36. {rhiza-0.5.1 → rhiza-0.5.3}/.pre-commit-config.yaml +0 -0
  37. {rhiza-0.5.1 → rhiza-0.5.3}/CLI.md +0 -0
  38. {rhiza-0.5.1 → rhiza-0.5.3}/CODE_OF_CONDUCT.md +0 -0
  39. {rhiza-0.5.1 → rhiza-0.5.3}/CONTRIBUTING.md +0 -0
  40. {rhiza-0.5.1 → rhiza-0.5.3}/LICENSE +0 -0
  41. {rhiza-0.5.1 → rhiza-0.5.3}/Makefile +0 -0
  42. {rhiza-0.5.1 → rhiza-0.5.3}/README.md +0 -0
  43. {rhiza-0.5.1 → rhiza-0.5.3}/USAGE.md +0 -0
  44. {rhiza-0.5.1 → rhiza-0.5.3}/book/marimo/.gitkeep +0 -0
  45. {rhiza-0.5.1 → rhiza-0.5.3}/pytest.ini +0 -0
  46. {rhiza-0.5.1 → rhiza-0.5.3}/ruff.toml +0 -0
  47. {rhiza-0.5.1 → rhiza-0.5.3}/src/rhiza/__main__.py +0 -0
  48. {rhiza-0.5.1 → rhiza-0.5.3}/src/rhiza/commands/__init__.py +0 -0
  49. {rhiza-0.5.1 → rhiza-0.5.3}/src/rhiza/models.py +0 -0
  50. {rhiza-0.5.1 → rhiza-0.5.3}/tests/test_commands/test_init.py +0 -0
  51. {rhiza-0.5.1 → rhiza-0.5.3}/tests/test_commands/test_materialize.py +0 -0
  52. {rhiza-0.5.1 → rhiza-0.5.3}/tests/test_commands/test_validate.py +0 -0
  53. {rhiza-0.5.1 → rhiza-0.5.3}/tests/test_models.py +0 -0
  54. {rhiza-0.5.1 → rhiza-0.5.3}/tests/test_rhiza/README.md +0 -0
  55. {rhiza-0.5.1 → rhiza-0.5.3}/tests/test_rhiza/conftest.py +0 -0
  56. {rhiza-0.5.1 → rhiza-0.5.3}/tests/test_rhiza/test_bump_script.py +0 -0
  57. {rhiza-0.5.1 → rhiza-0.5.3}/tests/test_rhiza/test_docstrings.py +0 -0
  58. {rhiza-0.5.1 → rhiza-0.5.3}/tests/test_rhiza/test_git_repo_fixture.py +0 -0
  59. {rhiza-0.5.1 → rhiza-0.5.3}/tests/test_rhiza/test_makefile.py +0 -0
  60. {rhiza-0.5.1 → rhiza-0.5.3}/tests/test_rhiza/test_marimushka_script.py +0 -0
  61. {rhiza-0.5.1 → rhiza-0.5.3}/tests/test_rhiza/test_readme.py +0 -0
  62. {rhiza-0.5.1 → rhiza-0.5.3}/tests/test_rhiza/test_release_script.py +0 -0
  63. {rhiza-0.5.1 → rhiza-0.5.3}/tests/test_rhiza/test_structure.py +0 -0
  64. {rhiza-0.5.1 → rhiza-0.5.3}/tests/test_rhiza/test_updatereadme_script.py +0 -0
@@ -137,6 +137,3 @@ jobs:
137
137
 
138
138
  Changes were generated automatically using **rhiza**.
139
139
 
140
- - name: Delete branch
141
- if: ${{ steps.sync.outputs.changes_detected == 'false' }}
142
- run: git push origin --delete rhiza_update
@@ -0,0 +1,45 @@
1
+ # Rhiza Template History
2
+ # This file lists all files managed by the Rhiza template.
3
+ # Template repository: jebel-quant/rhiza
4
+ # Template branch: main
5
+ #
6
+ # Files under template control:
7
+ .editorconfig
8
+ .github/README.md
9
+ .github/TOKEN_SETUP.md
10
+ .github/actions/setup-project/action.yml
11
+ .github/renovate.json
12
+ .github/scripts/book.sh
13
+ .github/scripts/bump.sh
14
+ .github/scripts/customisations/build-extras.sh
15
+ .github/scripts/customisations/post-release.sh
16
+ .github/scripts/marimushka.sh
17
+ .github/scripts/release.sh
18
+ .github/scripts/update-readme-help.sh
19
+ .github/workflows/book.yml
20
+ .github/workflows/ci.yml
21
+ .github/workflows/deptry.yml
22
+ .github/workflows/marimo.yml
23
+ .github/workflows/pre-commit.yml
24
+ .github/workflows/release.yml
25
+ .github/workflows/scripts/version_matrix.py
26
+ .github/workflows/scripts/version_max.py
27
+ .github/workflows/sync.yml
28
+ .gitignore
29
+ .pre-commit-config.yaml
30
+ CODE_OF_CONDUCT.md
31
+ CONTRIBUTING.md
32
+ Makefile
33
+ pytest.ini
34
+ ruff.toml
35
+ tests/test_rhiza/README.md
36
+ tests/test_rhiza/conftest.py
37
+ tests/test_rhiza/test_bump_script.py
38
+ tests/test_rhiza/test_docstrings.py
39
+ tests/test_rhiza/test_git_repo_fixture.py
40
+ tests/test_rhiza/test_makefile.py
41
+ tests/test_rhiza/test_marimushka_script.py
42
+ tests/test_rhiza/test_readme.py
43
+ tests/test_rhiza/test_release_script.py
44
+ tests/test_rhiza/test_structure.py
45
+ tests/test_rhiza/test_updatereadme_script.py
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rhiza
3
- Version: 0.5.1
3
+ Version: 0.5.3
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
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "rhiza"
7
- version = "0.5.1"
7
+ version = "0.5.3"
8
8
  description = "Reusable configuration templates for modern Python projects"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -4,4 +4,12 @@ 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 importlib.metadata import PackageNotFoundError, version
8
+
9
+ try:
10
+ __version__ = version("rhiza")
11
+ except PackageNotFoundError:
12
+ # Package is not installed, use a fallback version
13
+ __version__ = "0.0.0+dev"
14
+
7
15
  __all__ = ["commands", "models"]
@@ -8,6 +8,7 @@ from pathlib import Path
8
8
 
9
9
  import typer
10
10
 
11
+ from rhiza import __version__
11
12
  from rhiza.commands import init as init_cmd
12
13
  from rhiza.commands import materialize as materialize_cmd
13
14
  from rhiza.commands import validate as validate_cmd
@@ -18,6 +19,41 @@ app = typer.Typer(
18
19
  )
19
20
 
20
21
 
22
+ def version_callback(value: bool):
23
+ """Print version information and exit.
24
+
25
+ Args:
26
+ value: Whether the --version flag was provided.
27
+
28
+ Raises:
29
+ typer.Exit: Always exits after printing version.
30
+ """
31
+ if value:
32
+ typer.echo(f"rhiza version {__version__}")
33
+ raise typer.Exit()
34
+
35
+
36
+ @app.callback()
37
+ def main(
38
+ version: bool = typer.Option(
39
+ False,
40
+ "--version",
41
+ "-v",
42
+ help="Show version and exit",
43
+ callback=version_callback,
44
+ is_eager=True,
45
+ ),
46
+ ):
47
+ """Rhiza CLI main callback.
48
+
49
+ This callback is executed before any command. It handles global options
50
+ like --version.
51
+
52
+ Args:
53
+ version: Version flag (handled by callback).
54
+ """
55
+
56
+
21
57
  @app.command()
22
58
  def init(
23
59
  target: Path = typer.Argument(
@@ -61,4 +61,4 @@ Next steps:
61
61
  """)
62
62
 
63
63
  # the template file exists, so validate it
64
- validate(target)
64
+ return validate(target)
@@ -1,9 +1,9 @@
1
- """Command-line helpers for working with Rhiza templates.
1
+ """Command for materializing Rhiza template files into a repository.
2
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.
3
+ This module implements the `materialize` command. It performs a sparse
4
+ checkout of the configured template repository, copies the selected files
5
+ into the target Git repository, and records managed files in
6
+ `.rhiza.history`. Use this to take a one-shot snapshot of template files.
7
7
  """
8
8
 
9
9
  import shutil
@@ -14,7 +14,7 @@ from pathlib import Path
14
14
 
15
15
  from loguru import logger
16
16
 
17
- from rhiza.commands.init import init
17
+ from rhiza.commands import init
18
18
  from rhiza.models import RhizaTemplate
19
19
 
20
20
 
@@ -37,46 +37,48 @@ def expand_paths(base_dir: Path, paths: list[str]) -> list[Path]:
37
37
  return all_files
38
38
 
39
39
 
40
- def materialize(target: Path, branch: str, force: bool):
41
- """Materialize rhiza templates into TARGET repository."""
42
- # Convert to absolute path to avoid surprises
40
+ def materialize(target: Path, branch: str, force: bool) -> None:
41
+ """Materialize Rhiza templates into the target repository.
42
+
43
+ This performs a sparse checkout of the template repository and copies
44
+ the selected files into the target repository, recording all files
45
+ under template control in `.rhiza.history`.
46
+ """
43
47
  target = target.resolve()
44
48
 
45
49
  logger.info(f"Target repository: {target}")
46
50
  logger.info(f"Rhiza branch: {branch}")
47
51
 
48
52
  # -----------------------
49
- # Ensure template.yml
53
+ # Ensure Rhiza is initialized
50
54
  # -----------------------
51
- template_file = target / ".github" / "template.yml"
52
- # template_file.parent.mkdir(parents=True, exist_ok=True)
55
+ valid = init(target)
53
56
 
54
- # Initialize rhiza if not already initialized, e.g. construct a template.yml file
55
- init(target)
57
+ if not valid:
58
+ logger.error(f"Rhiza template is invalid. {target}")
59
+ sys.exit(1)
56
60
 
57
- # -----------------------
58
- # Load template.yml
59
- # -----------------------
61
+ template_file = target / ".github" / "template.yml"
60
62
  template = RhizaTemplate.from_yaml(template_file)
61
63
 
62
64
  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
65
+ rhiza_branch = template.template_branch or branch
65
66
  include_paths = template.include
66
67
  excluded_paths = template.exclude
67
68
 
68
69
  if not include_paths:
69
- logger.error("No include paths found in template.yml")
70
- raise sys.exit(1)
70
+ raise RuntimeError("No include paths found in template.yml")
71
71
 
72
72
  logger.info("Include paths:")
73
73
  for p in include_paths:
74
74
  logger.info(f" - {p}")
75
75
 
76
76
  # -----------------------
77
- # Sparse clone rhiza
77
+ # Sparse clone template repo
78
78
  # -----------------------
79
79
  tmp_dir = Path(tempfile.mkdtemp())
80
+ materialized_files: list[Path] = []
81
+
80
82
  logger.info(f"Cloning {rhiza_repo}@{rhiza_branch} into temporary directory")
81
83
 
82
84
  try:
@@ -97,30 +99,38 @@ def materialize(target: Path, branch: str, force: bool):
97
99
  stdout=subprocess.DEVNULL,
98
100
  )
99
101
 
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)
102
+ subprocess.run(
103
+ ["git", "sparse-checkout", "init", "--cone"],
104
+ cwd=tmp_dir,
105
+ check=True,
106
+ )
102
107
 
103
- # After sparse-checkout
108
+ subprocess.run(
109
+ ["git", "sparse-checkout", "set", "--skip-checks", *include_paths],
110
+ cwd=tmp_dir,
111
+ check=True,
112
+ )
113
+
114
+ # -----------------------
115
+ # Expand include/exclude paths
116
+ # -----------------------
104
117
  all_files = expand_paths(tmp_dir, include_paths)
105
118
 
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)
119
+ excluded_files = {f.resolve() for f in expand_paths(tmp_dir, excluded_paths)}
109
120
 
110
- files_to_copy = [f for f in all_files if f not in excluded_files]
111
- # print(files_to_copy)
121
+ files_to_copy = [f for f in all_files if f.resolve() not in excluded_files]
112
122
 
113
- # Copy loop
114
- materialized_files = []
123
+ # -----------------------
124
+ # Copy files into target repo
125
+ # -----------------------
115
126
  for src_file in files_to_copy:
116
127
  dst_file = target / src_file.relative_to(tmp_dir)
117
128
  relative_path = dst_file.relative_to(target)
118
129
 
119
- # Track this file as being under template control
120
130
  materialized_files.append(relative_path)
121
131
 
122
132
  if dst_file.exists() and not force:
123
- logger.warning(f"{relative_path} already exists — use force=True to overwrite")
133
+ logger.warning(f"{relative_path} already exists — use --force to overwrite")
124
134
  continue
125
135
 
126
136
  dst_file.parent.mkdir(parents=True, exist_ok=True)
@@ -130,9 +140,22 @@ def materialize(target: Path, branch: str, force: bool):
130
140
  finally:
131
141
  shutil.rmtree(tmp_dir)
132
142
 
133
- # Write .rhiza.history file listing all files under template control
143
+ # -----------------------
144
+ # Warn about workflow files
145
+ # -----------------------
146
+ workflow_files = [p for p in materialized_files if p.parts[:2] == (".github", "workflows")]
147
+
148
+ if workflow_files:
149
+ logger.warning(
150
+ "Workflow files were materialized. Updating these files requires "
151
+ "a token with the 'workflow' permission in GitHub Actions."
152
+ )
153
+
154
+ # -----------------------
155
+ # Write .rhiza.history
156
+ # -----------------------
134
157
  history_file = target / ".rhiza.history"
135
- with open(history_file, "w") as f:
158
+ with history_file.open("w", encoding="utf-8") as f:
136
159
  f.write("# Rhiza Template History\n")
137
160
  f.write("# This file lists all files managed by the Rhiza template.\n")
138
161
  f.write(f"# Template repository: {rhiza_repo}\n")
@@ -141,19 +164,19 @@ def materialize(target: Path, branch: str, force: bool):
141
164
  f.write("# Files under template control:\n")
142
165
  for file_path in sorted(materialized_files):
143
166
  f.write(f"{file_path}\n")
167
+
144
168
  logger.info(f"Created {history_file.relative_to(target)} with {len(materialized_files)} files")
145
169
 
146
170
  logger.success("Rhiza templates materialized successfully")
147
- logger.info("""
148
- Next steps:
149
- 1. Review changes:
150
- git status
151
- git diff
152
-
153
- 2. Commit:
154
- git add .
155
- git commit -m "chore: import rhiza templates"
156
-
157
- This is a one-shot snapshot.
158
- Re-run this script to update templates explicitly.
159
- """)
171
+
172
+ logger.info(
173
+ "Next steps:\n"
174
+ " 1. Review changes:\n"
175
+ " git status\n"
176
+ " git diff\n\n"
177
+ " 2. Commit:\n"
178
+ " git add .\n"
179
+ ' git commit -m "chore: import rhiza templates"\n\n'
180
+ "This is a one-shot snapshot.\n"
181
+ "Re-run this command to update templates explicitly."
182
+ )
@@ -133,4 +133,5 @@ def validate(target: Path) -> bool:
133
133
  return True
134
134
  else:
135
135
  logger.error("✗ Validation failed: template.yml has errors")
136
+ # raise AssertionError("Invalid template.yml")
136
137
  return False
@@ -9,12 +9,35 @@ This module tests:
9
9
  import subprocess
10
10
  import sys
11
11
 
12
+ from rhiza import __version__
12
13
  from rhiza.commands.materialize import expand_paths
13
14
 
14
15
 
15
16
  class TestCliApp:
16
17
  """Tests for the CLI Typer app."""
17
18
 
19
+ def test_version_flag(self):
20
+ """Test that --version flag shows version information."""
21
+ result = subprocess.run(
22
+ [sys.executable, "-m", "rhiza", "--version"],
23
+ capture_output=True,
24
+ text=True,
25
+ )
26
+ assert result.returncode == 0
27
+ assert "rhiza version" in result.stdout
28
+ assert __version__ in result.stdout
29
+
30
+ def test_version_short_flag(self):
31
+ """Test that -v flag shows version information."""
32
+ result = subprocess.run(
33
+ [sys.executable, "-m", "rhiza", "-v"],
34
+ capture_output=True,
35
+ text=True,
36
+ )
37
+ assert result.returncode == 0
38
+ assert "rhiza version" in result.stdout
39
+ assert __version__ in result.stdout
40
+
18
41
 
19
42
  class TestExpandPaths:
20
43
  """Tests for the expand_paths utility function."""
@@ -771,7 +771,7 @@ wheels = [
771
771
 
772
772
  [[package]]
773
773
  name = "rhiza"
774
- version = "0.5.1"
774
+ version = "0.5.3"
775
775
  source = { editable = "." }
776
776
  dependencies = [
777
777
  { name = "loguru" },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes