rhiza 0.5.4__tar.gz → 0.5.6__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 (67) hide show
  1. {rhiza-0.5.4 → rhiza-0.5.6}/PKG-INFO +1 -1
  2. {rhiza-0.5.4 → rhiza-0.5.6}/pyproject.toml +1 -1
  3. {rhiza-0.5.4 → rhiza-0.5.6}/src/rhiza/cli.py +15 -0
  4. {rhiza-0.5.4 → rhiza-0.5.6}/src/rhiza/commands/materialize.py +76 -43
  5. rhiza-0.5.6/src/rhiza/commands/welcome.py +55 -0
  6. {rhiza-0.5.4 → rhiza-0.5.6}/tests/test_cli_commands.py +32 -62
  7. {rhiza-0.5.4 → rhiza-0.5.6}/uv.lock +1 -1
  8. {rhiza-0.5.4 → rhiza-0.5.6}/.editorconfig +0 -0
  9. {rhiza-0.5.4 → rhiza-0.5.6}/.github/README.md +0 -0
  10. {rhiza-0.5.4 → rhiza-0.5.6}/.github/TOKEN_SETUP.md +0 -0
  11. {rhiza-0.5.4 → rhiza-0.5.6}/.github/actions/setup-project/action.yml +0 -0
  12. {rhiza-0.5.4 → rhiza-0.5.6}/.github/copilot-instructions.md +0 -0
  13. {rhiza-0.5.4 → rhiza-0.5.6}/.github/renovate.json +0 -0
  14. {rhiza-0.5.4 → rhiza-0.5.6}/.github/scripts/book.sh +0 -0
  15. {rhiza-0.5.4 → rhiza-0.5.6}/.github/scripts/bump.sh +0 -0
  16. {rhiza-0.5.4 → rhiza-0.5.6}/.github/scripts/customisations/build-extras.sh +0 -0
  17. {rhiza-0.5.4 → rhiza-0.5.6}/.github/scripts/customisations/post-release.sh +0 -0
  18. {rhiza-0.5.4 → rhiza-0.5.6}/.github/scripts/marimushka.sh +0 -0
  19. {rhiza-0.5.4 → rhiza-0.5.6}/.github/scripts/release.sh +0 -0
  20. {rhiza-0.5.4 → rhiza-0.5.6}/.github/scripts/update-readme-help.sh +0 -0
  21. {rhiza-0.5.4 → rhiza-0.5.6}/.github/template.yml +0 -0
  22. {rhiza-0.5.4 → rhiza-0.5.6}/.github/workflows/book.yml +0 -0
  23. {rhiza-0.5.4 → rhiza-0.5.6}/.github/workflows/ci.yml +0 -0
  24. {rhiza-0.5.4 → rhiza-0.5.6}/.github/workflows/deptry.yml +0 -0
  25. {rhiza-0.5.4 → rhiza-0.5.6}/.github/workflows/marimo.yml +0 -0
  26. {rhiza-0.5.4 → rhiza-0.5.6}/.github/workflows/pre-commit.yml +0 -0
  27. {rhiza-0.5.4 → rhiza-0.5.6}/.github/workflows/release.yml +0 -0
  28. {rhiza-0.5.4 → rhiza-0.5.6}/.github/workflows/scripts/version_matrix.py +0 -0
  29. {rhiza-0.5.4 → rhiza-0.5.6}/.github/workflows/scripts/version_max.py +0 -0
  30. {rhiza-0.5.4 → rhiza-0.5.6}/.github/workflows/structure.yml +0 -0
  31. {rhiza-0.5.4 → rhiza-0.5.6}/.github/workflows/sym2.yml +0 -0
  32. {rhiza-0.5.4 → rhiza-0.5.6}/.github/workflows/sync.yml +0 -0
  33. {rhiza-0.5.4 → rhiza-0.5.6}/.gitignore +0 -0
  34. {rhiza-0.5.4 → rhiza-0.5.6}/.pre-commit-config.yaml +0 -0
  35. {rhiza-0.5.4 → rhiza-0.5.6}/.rhiza.history +0 -0
  36. {rhiza-0.5.4 → rhiza-0.5.6}/CLI.md +0 -0
  37. {rhiza-0.5.4 → rhiza-0.5.6}/CODE_OF_CONDUCT.md +0 -0
  38. {rhiza-0.5.4 → rhiza-0.5.6}/CONTRIBUTING.md +0 -0
  39. {rhiza-0.5.4 → rhiza-0.5.6}/LICENSE +0 -0
  40. {rhiza-0.5.4 → rhiza-0.5.6}/Makefile +0 -0
  41. {rhiza-0.5.4 → rhiza-0.5.6}/README.md +0 -0
  42. {rhiza-0.5.4 → rhiza-0.5.6}/USAGE.md +0 -0
  43. {rhiza-0.5.4 → rhiza-0.5.6}/book/marimo/.gitkeep +0 -0
  44. {rhiza-0.5.4 → rhiza-0.5.6}/pytest.ini +0 -0
  45. {rhiza-0.5.4 → rhiza-0.5.6}/ruff.toml +0 -0
  46. {rhiza-0.5.4 → rhiza-0.5.6}/src/rhiza/__init__.py +0 -0
  47. {rhiza-0.5.4 → rhiza-0.5.6}/src/rhiza/__main__.py +0 -0
  48. {rhiza-0.5.4 → rhiza-0.5.6}/src/rhiza/commands/__init__.py +0 -0
  49. {rhiza-0.5.4 → rhiza-0.5.6}/src/rhiza/commands/init.py +0 -0
  50. {rhiza-0.5.4 → rhiza-0.5.6}/src/rhiza/commands/validate.py +0 -0
  51. {rhiza-0.5.4 → rhiza-0.5.6}/src/rhiza/models.py +0 -0
  52. {rhiza-0.5.4 → rhiza-0.5.6}/tests/test_commands/test_init.py +0 -0
  53. {rhiza-0.5.4 → rhiza-0.5.6}/tests/test_commands/test_materialize.py +0 -0
  54. {rhiza-0.5.4 → rhiza-0.5.6}/tests/test_commands/test_validate.py +0 -0
  55. {rhiza-0.5.4 → rhiza-0.5.6}/tests/test_models.py +0 -0
  56. {rhiza-0.5.4 → rhiza-0.5.6}/tests/test_package.py +0 -0
  57. {rhiza-0.5.4 → rhiza-0.5.6}/tests/test_rhiza/README.md +0 -0
  58. {rhiza-0.5.4 → rhiza-0.5.6}/tests/test_rhiza/conftest.py +0 -0
  59. {rhiza-0.5.4 → rhiza-0.5.6}/tests/test_rhiza/test_bump_script.py +0 -0
  60. {rhiza-0.5.4 → rhiza-0.5.6}/tests/test_rhiza/test_docstrings.py +0 -0
  61. {rhiza-0.5.4 → rhiza-0.5.6}/tests/test_rhiza/test_git_repo_fixture.py +0 -0
  62. {rhiza-0.5.4 → rhiza-0.5.6}/tests/test_rhiza/test_makefile.py +0 -0
  63. {rhiza-0.5.4 → rhiza-0.5.6}/tests/test_rhiza/test_marimushka_script.py +0 -0
  64. {rhiza-0.5.4 → rhiza-0.5.6}/tests/test_rhiza/test_readme.py +0 -0
  65. {rhiza-0.5.4 → rhiza-0.5.6}/tests/test_rhiza/test_release_script.py +0 -0
  66. {rhiza-0.5.4 → rhiza-0.5.6}/tests/test_rhiza/test_structure.py +0 -0
  67. {rhiza-0.5.4 → rhiza-0.5.6}/tests/test_rhiza/test_updatereadme_script.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rhiza
3
- Version: 0.5.4
3
+ Version: 0.5.6
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.4"
7
+ version = "0.5.6"
8
8
  description = "Reusable configuration templates for modern Python projects"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -12,6 +12,7 @@ from rhiza import __version__
12
12
  from rhiza.commands import init as init_cmd
13
13
  from rhiza.commands import materialize as materialize_cmd
14
14
  from rhiza.commands import validate as validate_cmd
15
+ from rhiza.commands.welcome import welcome as welcome_cmd
15
16
 
16
17
  app = typer.Typer(
17
18
  help=(
@@ -171,3 +172,17 @@ def validate(
171
172
  """
172
173
  if not validate_cmd(target):
173
174
  raise typer.Exit(code=1)
175
+
176
+
177
+ @app.command()
178
+ def welcome():
179
+ r"""Display a friendly welcome message and explain what Rhiza is.
180
+
181
+ Shows a welcome message, explains Rhiza's purpose, key features,
182
+ and provides guidance on getting started with the tool.
183
+
184
+ \b
185
+ Examples:
186
+ rhiza welcome
187
+ """
188
+ welcome_cmd()
@@ -6,6 +6,7 @@ into the target Git repository, and records managed files in
6
6
  `.rhiza.history`. Use this to take a one-shot snapshot of template files.
7
7
  """
8
8
 
9
+ import os
9
10
  import shutil
10
11
  import subprocess
11
12
  import sys
@@ -18,7 +19,7 @@ from rhiza.commands import init
18
19
  from rhiza.models import RhizaTemplate
19
20
 
20
21
 
21
- def expand_paths(base_dir: Path, paths: list[str]) -> list[Path]:
22
+ def __expand_paths(base_dir: Path, paths: list[str]) -> list[Path]:
22
23
  """Expand files/directories relative to base_dir into a flat list of files.
23
24
 
24
25
  Given a list of paths relative to ``base_dir``, return a flat list of all
@@ -40,26 +41,26 @@ def expand_paths(base_dir: Path, paths: list[str]) -> list[Path]:
40
41
  def materialize(target: Path, branch: str, target_branch: str | None, force: bool) -> None:
41
42
  """Materialize Rhiza templates into the target repository.
42
43
 
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
-
47
- Parameters
48
- ----------
49
- target:
50
- Path to the target repository.
51
- branch:
52
- The Rhiza template branch to use.
53
- target_branch:
54
- Optional branch name to create/checkout in target repository.
55
- force:
56
- Whether to overwrite existing files.
44
+ This performs a sparse checkout of the template repository and copies the
45
+ selected files into the target repository, recording all files under
46
+ template control in `.rhiza.history`.
47
+
48
+ Args:
49
+ target (Path): Path to the target repository.
50
+ branch (str): The Rhiza template branch to use.
51
+ target_branch (str | None): Optional branch name to create/checkout in
52
+ the target repository.
53
+ force (bool): Whether to overwrite existing files.
57
54
  """
58
55
  target = target.resolve()
59
56
 
60
57
  logger.info(f"Target repository: {target}")
61
58
  logger.info(f"Rhiza branch: {branch}")
62
59
 
60
+ # Set environment to prevent git from prompting for credentials
61
+ git_env = os.environ.copy()
62
+ git_env["GIT_TERMINAL_PROMPT"] = "0"
63
+
63
64
  # -----------------------
64
65
  # Handle target branch creation/checkout if specified
65
66
  # -----------------------
@@ -72,6 +73,7 @@ def materialize(target: Path, branch: str, target_branch: str | None, force: boo
72
73
  cwd=target,
73
74
  capture_output=True,
74
75
  text=True,
76
+ env=git_env,
75
77
  )
76
78
 
77
79
  if result.returncode == 0:
@@ -81,6 +83,7 @@ def materialize(target: Path, branch: str, target_branch: str | None, force: boo
81
83
  ["git", "checkout", target_branch],
82
84
  cwd=target,
83
85
  check=True,
86
+ env=git_env,
84
87
  )
85
88
  else:
86
89
  # Branch doesn't exist, create and checkout
@@ -89,6 +92,7 @@ def materialize(target: Path, branch: str, target_branch: str | None, force: boo
89
92
  ["git", "checkout", "-b", target_branch],
90
93
  cwd=target,
91
94
  check=True,
95
+ env=git_env,
92
96
  )
93
97
  except subprocess.CalledProcessError as e:
94
98
  logger.error(f"Failed to create/checkout branch '{target_branch}': {e}")
@@ -138,41 +142,70 @@ def materialize(target: Path, branch: str, target_branch: str | None, force: boo
138
142
  logger.info(f"Cloning {rhiza_repo}@{rhiza_branch} from {rhiza_host} into temporary directory")
139
143
 
140
144
  try:
141
- subprocess.run(
142
- [
143
- "git",
144
- "clone",
145
- "--depth",
146
- "1",
147
- "--filter=blob:none",
148
- "--sparse",
149
- "--branch",
150
- rhiza_branch,
151
- git_url,
152
- str(tmp_dir),
153
- ],
154
- check=True,
155
- stdout=subprocess.DEVNULL,
156
- )
145
+ # Clone the repository - capture output to avoid blocking
146
+ try:
147
+ subprocess.run(
148
+ [
149
+ "git",
150
+ "clone",
151
+ "--depth",
152
+ "1",
153
+ "--filter=blob:none",
154
+ "--sparse",
155
+ "--branch",
156
+ rhiza_branch,
157
+ git_url,
158
+ str(tmp_dir),
159
+ ],
160
+ check=True,
161
+ capture_output=True,
162
+ text=True,
163
+ env=git_env,
164
+ )
165
+ except subprocess.CalledProcessError as e:
166
+ logger.error(f"Failed to clone repository: {e}")
167
+ if e.stderr:
168
+ logger.error(f"Git error: {e.stderr.strip()}")
169
+ raise
157
170
 
158
- subprocess.run(
159
- ["git", "sparse-checkout", "init", "--cone"],
160
- cwd=tmp_dir,
161
- check=True,
162
- )
171
+ # Initialize sparse checkout
172
+ try:
173
+ subprocess.run(
174
+ ["git", "sparse-checkout", "init", "--cone"],
175
+ cwd=tmp_dir,
176
+ check=True,
177
+ capture_output=True,
178
+ text=True,
179
+ env=git_env,
180
+ )
181
+ except subprocess.CalledProcessError as e:
182
+ logger.error(f"Failed to initialize sparse checkout: {e}")
183
+ if e.stderr:
184
+ logger.error(f"Git error: {e.stderr.strip()}")
185
+ raise
163
186
 
164
- subprocess.run(
165
- ["git", "sparse-checkout", "set", "--skip-checks", *include_paths],
166
- cwd=tmp_dir,
167
- check=True,
168
- )
187
+ # Set sparse checkout paths
188
+ try:
189
+ subprocess.run(
190
+ ["git", "sparse-checkout", "set", "--skip-checks", *include_paths],
191
+ cwd=tmp_dir,
192
+ check=True,
193
+ capture_output=True,
194
+ text=True,
195
+ env=git_env,
196
+ )
197
+ except subprocess.CalledProcessError as e:
198
+ logger.error(f"Failed to set sparse checkout paths: {e}")
199
+ if e.stderr:
200
+ logger.error(f"Git error: {e.stderr.strip()}")
201
+ raise
169
202
 
170
203
  # -----------------------
171
204
  # Expand include/exclude paths
172
205
  # -----------------------
173
- all_files = expand_paths(tmp_dir, include_paths)
206
+ all_files = __expand_paths(tmp_dir, include_paths)
174
207
 
175
- excluded_files = {f.resolve() for f in expand_paths(tmp_dir, excluded_paths)}
208
+ excluded_files = {f.resolve() for f in __expand_paths(tmp_dir, excluded_paths)}
176
209
 
177
210
  files_to_copy = [f for f in all_files if f.resolve() not in excluded_files]
178
211
 
@@ -0,0 +1,55 @@
1
+ # This file is part of the jebel-quant/rhiza repository
2
+ # (https://github.com/jebel-quant/rhiza).
3
+ #
4
+ """Command to display a welcome message and explain Rhiza.
5
+
6
+ This module provides the welcome command that displays a friendly greeting
7
+ and explains what Rhiza is and how it can help manage configuration templates.
8
+ """
9
+
10
+ from rhiza import __version__
11
+
12
+
13
+ def welcome():
14
+ """Display a welcome message and explain what Rhiza is.
15
+
16
+ Shows a friendly greeting, explains Rhiza's purpose, and provides
17
+ next steps for getting started with the tool.
18
+ """
19
+ welcome_message = f"""
20
+ ╭───────────────────────────────────────────────────────────────╮
21
+ │ │
22
+ │ 🌿 Welcome to Rhiza v{__version__:<43} │
23
+ │ │
24
+ ╰───────────────────────────────────────────────────────────────╯
25
+
26
+ Rhiza helps you maintain consistent configuration across multiple
27
+ Python projects using reusable templates stored in a central repository.
28
+
29
+ ✨ What Rhiza can do for you:
30
+
31
+ • Initialize projects with standard configuration templates
32
+ • Materialize (inject) templates into target repositories
33
+ • Validate template configurations
34
+ • Keep project configurations synchronized
35
+
36
+ 🚀 Getting started:
37
+
38
+ 1. Initialize a project:
39
+ $ rhiza init
40
+
41
+ 2. Customize .github/template.yml to match your needs
42
+
43
+ 3. Materialize templates into your project:
44
+ $ rhiza materialize
45
+
46
+ 📚 Learn more:
47
+
48
+ • View all commands: rhiza --help
49
+ • Project repository: https://github.com/jebel-quant/rhiza-cli
50
+ • Documentation: https://jebel-quant.github.io/rhiza-cli/
51
+
52
+ Happy templating! 🎉
53
+ """
54
+
55
+ print(welcome_message)
@@ -14,7 +14,6 @@ import typer
14
14
 
15
15
  from rhiza import __version__
16
16
  from rhiza.cli import version_callback
17
- from rhiza.commands.materialize import expand_paths
18
17
 
19
18
 
20
19
  class TestCliApp:
@@ -59,67 +58,6 @@ class TestCliApp:
59
58
  version_callback(False) # Should not raise
60
59
 
61
60
 
62
- class TestExpandPaths:
63
- """Tests for the expand_paths utility function."""
64
-
65
- def test_expand_single_file(self, tmp_path):
66
- """Test expanding a single file path."""
67
- test_file = tmp_path / "test.txt"
68
- test_file.write_text("content")
69
-
70
- result = expand_paths(tmp_path, ["test.txt"])
71
- assert result == [test_file]
72
-
73
- def test_expand_directory(self, tmp_path):
74
- """Test expanding a directory into all its files."""
75
- test_dir = tmp_path / "dir"
76
- test_dir.mkdir()
77
- file1 = test_dir / "file1.txt"
78
- file2 = test_dir / "file2.txt"
79
- file1.write_text("content1")
80
- file2.write_text("content2")
81
-
82
- result = expand_paths(tmp_path, ["dir"])
83
- assert len(result) == 2
84
- assert file1 in result
85
- assert file2 in result
86
-
87
- def test_expand_nested_directory(self, tmp_path):
88
- """Test expanding a directory with nested subdirectories."""
89
- test_dir = tmp_path / "dir"
90
- sub_dir = test_dir / "subdir"
91
- sub_dir.mkdir(parents=True)
92
- file1 = test_dir / "file1.txt"
93
- file2 = sub_dir / "file2.txt"
94
- file1.write_text("content1")
95
- file2.write_text("content2")
96
-
97
- result = expand_paths(tmp_path, ["dir"])
98
- assert len(result) == 2
99
- assert file1 in result
100
- assert file2 in result
101
-
102
- def test_expand_nonexistent_path(self, tmp_path):
103
- """Test that nonexistent paths are skipped."""
104
- result = expand_paths(tmp_path, ["nonexistent.txt"])
105
- assert result == []
106
-
107
- def test_expand_mixed_paths(self, tmp_path):
108
- """Test expanding a mix of files and directories."""
109
- file1 = tmp_path / "file1.txt"
110
- file1.write_text("content1")
111
-
112
- test_dir = tmp_path / "dir"
113
- test_dir.mkdir()
114
- file2 = test_dir / "file2.txt"
115
- file2.write_text("content2")
116
-
117
- result = expand_paths(tmp_path, ["file1.txt", "dir"])
118
- assert len(result) == 2
119
- assert file1 in result
120
- assert file2 in result
121
-
122
-
123
61
  class TestMainEntry:
124
62
  """Tests for the __main__.py entry point."""
125
63
 
@@ -153,3 +91,35 @@ class TestMainEntry:
153
91
  assert "rhiza" in captured.out.lower()
154
92
  finally:
155
93
  sys.argv = original_argv
94
+
95
+
96
+ class TestWelcomeCommand:
97
+ """Tests for the welcome command."""
98
+
99
+ def test_welcome_command(self, capsys):
100
+ """Test that the welcome command displays welcome message."""
101
+ result = subprocess.run(
102
+ [sys.executable, "-m", "rhiza", "welcome"],
103
+ capture_output=True,
104
+ text=True,
105
+ )
106
+ assert result.returncode == 0
107
+ output = result.stdout
108
+
109
+ # Check for key elements of the welcome message
110
+ assert "Welcome to Rhiza" in output
111
+ assert __version__ in output
112
+ assert "What Rhiza can do" in output
113
+ assert "Getting started" in output
114
+ assert "rhiza init" in output
115
+ assert "rhiza materialize" in output
116
+
117
+ def test_welcome_command_function_coverage(self, capsys):
118
+ """Test the welcome command function directly for coverage."""
119
+ from rhiza.commands.welcome import welcome
120
+
121
+ welcome()
122
+
123
+ captured = capsys.readouterr()
124
+ assert "Welcome to Rhiza" in captured.out
125
+ assert __version__ in captured.out
@@ -771,7 +771,7 @@ wheels = [
771
771
 
772
772
  [[package]]
773
773
  name = "rhiza"
774
- version = "0.5.4"
774
+ version = "0.5.6"
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
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes