rhiza 0.9.1__py3-none-any.whl → 0.10.1__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.
@@ -0,0 +1,78 @@
1
+ """Bundle resolution logic for template configuration.
2
+
3
+ This module provides functions to load and resolve bundle configurations
4
+ from the template repository's template_bundles.yml file.
5
+ """
6
+
7
+ from pathlib import Path
8
+
9
+ from rhiza.models import RhizaBundles, RhizaTemplate
10
+
11
+
12
+ def load_bundles_from_clone(tmp_dir: Path) -> RhizaBundles | None:
13
+ """Load .rhiza/template_bundles.yml from cloned template repo.
14
+
15
+ Args:
16
+ tmp_dir: Path to the cloned template repository.
17
+
18
+ Returns:
19
+ RhizaBundles if template_bundles.yml exists, None otherwise.
20
+
21
+ Raises:
22
+ yaml.YAMLError: If template_bundles.yml is malformed.
23
+ ValueError: If template_bundles.yml is invalid.
24
+ """
25
+ bundles_file = tmp_dir / ".rhiza" / "template_bundles.yml"
26
+ if not bundles_file.exists():
27
+ return None
28
+ return RhizaBundles.from_yaml(bundles_file)
29
+
30
+
31
+ def resolve_include_paths(
32
+ template: RhizaTemplate,
33
+ bundles_config: RhizaBundles | None,
34
+ ) -> list[str]:
35
+ """Resolve template configuration to file paths.
36
+
37
+ Supports:
38
+ - Template-based mode (templates field)
39
+ - Path-based mode (include field)
40
+ - Hybrid mode (both templates and include)
41
+
42
+ Args:
43
+ template: The template configuration.
44
+ bundles_config: The loaded bundles configuration, or None if not available.
45
+
46
+ Returns:
47
+ List of file paths to materialize.
48
+
49
+ Raises:
50
+ ValueError: If configuration is invalid or bundles.yml is missing.
51
+ """
52
+ paths = []
53
+
54
+ # Resolve templates to paths if specified
55
+ if template.templates:
56
+ if not bundles_config:
57
+ msg = "Template uses templates but template_bundles.yml not found in template repository"
58
+ raise ValueError(msg)
59
+ paths.extend(bundles_config.resolve_to_paths(template.templates))
60
+
61
+ # Add include paths if specified
62
+ if template.include:
63
+ paths.extend(template.include)
64
+
65
+ # At least one must be specified
66
+ if not paths:
67
+ msg = "Template configuration must specify either 'templates' or 'include'"
68
+ raise ValueError(msg)
69
+
70
+ # Deduplicate while preserving order
71
+ seen = set()
72
+ deduplicated = []
73
+ for path in paths:
74
+ if path not in seen:
75
+ deduplicated.append(path)
76
+ seen.add(path)
77
+
78
+ return deduplicated
rhiza/commands/init.py CHANGED
@@ -84,8 +84,24 @@ def _prompt_git_host() -> str:
84
84
  return str(git_host)
85
85
 
86
86
 
87
+ def _get_default_templates_for_host(git_host: str) -> list[str]:
88
+ """Get default templates based on git hosting platform.
89
+
90
+ Args:
91
+ git_host: Git hosting platform.
92
+
93
+ Returns:
94
+ List of template names.
95
+ """
96
+ common = ["core", "tests", "docs"]
97
+ if git_host == "gitlab":
98
+ return [*common, "gitlab"]
99
+ else:
100
+ return [*common, "github"]
101
+
102
+
87
103
  def _get_include_paths_for_host(git_host: str) -> list[str]:
88
- """Get include paths based on git hosting platform.
104
+ """Get include paths based on git hosting platform (legacy, path-based).
89
105
 
90
106
  Args:
91
107
  git_host: Git hosting platform.
@@ -129,6 +145,7 @@ def _create_template_file(
129
145
  git_host: str,
130
146
  template_repository: str | None = None,
131
147
  template_branch: str | None = None,
148
+ use_templates: bool = True,
132
149
  ) -> None:
133
150
  """Create default template.yml file.
134
151
 
@@ -137,6 +154,7 @@ def _create_template_file(
137
154
  git_host: Git hosting platform.
138
155
  template_repository: Custom template repository (format: owner/repo).
139
156
  template_branch: Custom template branch.
157
+ use_templates: Use template-based configuration if True, path-based if False.
140
158
  """
141
159
  rhiza_dir = target / ".rhiza"
142
160
  template_file = rhiza_dir / "template.yml"
@@ -147,8 +165,6 @@ def _create_template_file(
147
165
  logger.info("Creating default .rhiza/template.yml")
148
166
  logger.debug("Using default template configuration")
149
167
 
150
- include_paths = _get_include_paths_for_host(git_host)
151
-
152
168
  # Use custom template repository/branch if provided, otherwise use defaults
153
169
  repo = template_repository or "jebel-quant/rhiza"
154
170
  branch = template_branch or "main"
@@ -159,11 +175,22 @@ def _create_template_file(
159
175
  if template_branch:
160
176
  logger.info(f"Using custom template branch: {branch}")
161
177
 
162
- default_template = RhizaTemplate(
163
- template_repository=repo,
164
- template_branch=branch,
165
- include=include_paths,
166
- )
178
+ if use_templates:
179
+ templates = _get_default_templates_for_host(git_host)
180
+ logger.info(f"Using template-based configuration with templates: {', '.join(templates)}")
181
+ default_template = RhizaTemplate(
182
+ template_repository=repo,
183
+ template_branch=branch,
184
+ templates=templates,
185
+ )
186
+ else:
187
+ include_paths = _get_include_paths_for_host(git_host)
188
+ logger.info("Using path-based configuration")
189
+ default_template = RhizaTemplate(
190
+ template_repository=repo,
191
+ template_branch=branch,
192
+ include=include_paths,
193
+ )
167
194
 
168
195
  logger.debug(f"Writing default template to: {template_file}")
169
196
  default_template.to_yaml(template_file)
@@ -15,6 +15,7 @@ from pathlib import Path
15
15
 
16
16
  from loguru import logger
17
17
 
18
+ from rhiza.bundle_resolver import load_bundles_from_clone, resolve_include_paths
18
19
  from rhiza.commands.validate import validate
19
20
  from rhiza.models import RhizaTemplate
20
21
  from rhiza.subprocess_utils import get_git_executable
@@ -117,19 +118,28 @@ def _validate_and_load_template(target: Path, branch: str) -> tuple[RhizaTemplat
117
118
  logger.error("template-repository is not configured in template.yml")
118
119
  raise RuntimeError("template-repository is required") # noqa: TRY003
119
120
  rhiza_branch = template.template_branch or branch
120
- include_paths = template.include
121
121
  excluded_paths = template.exclude
122
122
 
123
- # Validate that we have paths to include
124
- if not include_paths:
125
- logger.error("No include paths found in template.yml")
126
- logger.error("Add at least one path to the 'include' list in template.yml")
127
- raise RuntimeError("No include paths found in template.yml") # noqa: TRY003
123
+ # Note: We'll resolve templates to paths after cloning the template repo,
124
+ # since we need access to template_bundles.yml from the template
125
+ include_paths = template.include
128
126
 
129
- # Log the paths we'll be including
130
- logger.info("Include paths:")
131
- for p in include_paths:
132
- logger.info(f" - {p}")
127
+ # Validate that we have either templates or include paths
128
+ if not template.templates and not include_paths:
129
+ logger.error("No templates or include paths found in template.yml")
130
+ logger.error("Add either 'templates' or 'include' list in template.yml")
131
+ raise RuntimeError("No templates or include paths found in template.yml") # noqa: TRY003
132
+
133
+ # Log what we'll be using
134
+ if template.templates:
135
+ logger.info("Templates:")
136
+ for t in template.templates:
137
+ logger.info(f" - {t}")
138
+
139
+ if include_paths:
140
+ logger.info("Include paths:")
141
+ for p in include_paths:
142
+ logger.info(f" - {p}")
133
143
 
134
144
  if excluded_paths:
135
145
  logger.info("Exclude paths:")
@@ -165,6 +175,37 @@ def _construct_git_url(rhiza_repo: str, rhiza_host: str) -> str:
165
175
  return git_url
166
176
 
167
177
 
178
+ def _update_sparse_checkout(
179
+ tmp_dir: Path,
180
+ include_paths: list[str],
181
+ git_executable: str,
182
+ git_env: dict[str, str],
183
+ ) -> None:
184
+ """Update sparse checkout paths in an already-cloned repository.
185
+
186
+ Args:
187
+ tmp_dir: Temporary directory with cloned repository.
188
+ include_paths: Paths to include in sparse checkout.
189
+ git_executable: Path to git executable.
190
+ git_env: Environment variables for git commands.
191
+ """
192
+ try:
193
+ logger.debug(f"Updating sparse checkout paths: {include_paths}")
194
+ subprocess.run( # nosec B603
195
+ [git_executable, "sparse-checkout", "set", "--skip-checks", *include_paths],
196
+ cwd=tmp_dir,
197
+ check=True,
198
+ capture_output=True,
199
+ text=True,
200
+ env=git_env,
201
+ )
202
+ logger.debug("Sparse checkout paths updated")
203
+ except subprocess.CalledProcessError as e:
204
+ logger.error("Failed to update sparse checkout paths")
205
+ _log_git_stderr_errors(e.stderr)
206
+ sys.exit(1)
207
+
208
+
168
209
  def _clone_template_repository(
169
210
  tmp_dir: Path,
170
211
  git_url: str,
@@ -179,7 +220,7 @@ def _clone_template_repository(
179
220
  tmp_dir: Temporary directory for cloning.
180
221
  git_url: Git repository URL.
181
222
  rhiza_branch: Branch to clone.
182
- include_paths: Paths to include in sparse checkout.
223
+ include_paths: Initial paths to include in sparse checkout.
183
224
  git_executable: Path to git executable.
184
225
  git_env: Environment variables for git commands.
185
226
  """
@@ -504,7 +545,25 @@ def materialize(target: Path, branch: str, target_branch: str | None, force: boo
504
545
  logger.debug(f"Temporary directory: {tmp_dir}")
505
546
 
506
547
  try:
507
- _clone_template_repository(tmp_dir, git_url, rhiza_branch, include_paths, git_executable, git_env)
548
+ # Clone with initial minimal checkout to load template_bundles.yml if needed
549
+ initial_paths = [".rhiza"] if template.templates else include_paths
550
+ _clone_template_repository(tmp_dir, git_url, rhiza_branch, initial_paths, git_executable, git_env)
551
+
552
+ # Load template_bundles.yml and resolve templates to paths if using template mode
553
+ if template.templates:
554
+ logger.info("Resolving templates to file paths...")
555
+ try:
556
+ bundles_config = load_bundles_from_clone(tmp_dir)
557
+ resolved_paths = resolve_include_paths(template, bundles_config)
558
+ logger.info(f"Resolved {len(template.templates)} template(s) to {len(resolved_paths)} path(s)")
559
+ logger.debug(f"Resolved paths: {resolved_paths}")
560
+ # Update sparse checkout with resolved paths
561
+ _update_sparse_checkout(tmp_dir, resolved_paths, git_executable, git_env)
562
+ include_paths = resolved_paths
563
+ except ValueError as e:
564
+ logger.error(f"Failed to resolve templates: {e}")
565
+ sys.exit(1)
566
+
508
567
  materialized_files = _copy_files_to_target(tmp_dir, target, include_paths, excluded_paths, force)
509
568
  finally:
510
569
  logger.debug(f"Cleaning up temporary directory: {tmp_dir}")
@@ -126,6 +126,78 @@ def _parse_yaml_file(template_file: Path) -> tuple[bool, dict[str, Any] | None]:
126
126
  return True, config
127
127
 
128
128
 
129
+ def _validate_configuration_mode(config: dict[str, Any]) -> bool:
130
+ """Validate that at least one of templates or include is specified.
131
+
132
+ Args:
133
+ config: Configuration dictionary.
134
+
135
+ Returns:
136
+ True if configuration mode is valid, False otherwise.
137
+ """
138
+ logger.debug("Validating configuration mode")
139
+ has_templates = "templates" in config and config["templates"]
140
+ has_include = "include" in config and config["include"]
141
+
142
+ # Error if old "bundles" field is used
143
+ if "bundles" in config:
144
+ logger.error("Field 'bundles' has been renamed to 'templates'")
145
+ logger.error("Update your .rhiza/template.yml:")
146
+ logger.error(" bundles: [...] → templates: [...]")
147
+ return False
148
+
149
+ # Require at least one of templates or include
150
+ if not has_templates and not has_include:
151
+ logger.error("Must specify at least one of 'templates' or 'include' in template.yml")
152
+ logger.error("Options:")
153
+ logger.error(" • Template-based: templates: [core, tests, github]")
154
+ logger.error(" • Path-based: include: [.rhiza, .github, ...]")
155
+ logger.error(" • Hybrid: specify both templates and include")
156
+ return False
157
+
158
+ # Log what mode is being used
159
+ if has_templates and has_include:
160
+ logger.success("Using hybrid mode (templates + include)")
161
+ elif has_templates:
162
+ logger.success("Using template-based mode")
163
+ else:
164
+ logger.success("Using path-based mode")
165
+
166
+ return True
167
+
168
+
169
+ def _validate_templates(config: dict[str, Any]) -> bool:
170
+ """Validate templates field if present.
171
+
172
+ Args:
173
+ config: Configuration dictionary.
174
+
175
+ Returns:
176
+ True if templates field is valid, False otherwise.
177
+ """
178
+ logger.debug("Validating templates field")
179
+ if "templates" not in config:
180
+ return True
181
+
182
+ templates = config["templates"]
183
+ if not isinstance(templates, list):
184
+ logger.error(f"templates must be a list, got {type(templates).__name__}")
185
+ logger.error("Example: templates: [core, tests, github]")
186
+ return False
187
+ elif len(templates) == 0:
188
+ logger.error("templates list cannot be empty")
189
+ logger.error("Add at least one template to materialize")
190
+ return False
191
+ else:
192
+ logger.success(f"templates list has {len(templates)} template(s)")
193
+ for template in templates:
194
+ if not isinstance(template, str):
195
+ logger.warning(f"template name should be a string, got {type(template).__name__}: {template}")
196
+ else:
197
+ logger.info(f" - {template}")
198
+ return True
199
+
200
+
129
201
  def _validate_required_fields(config: dict[str, Any]) -> bool:
130
202
  """Validate required fields exist and have correct types.
131
203
 
@@ -136,9 +208,10 @@ def _validate_required_fields(config: dict[str, Any]) -> bool:
136
208
  True if all validations pass, False otherwise.
137
209
  """
138
210
  logger.debug("Validating required fields")
211
+ # template-repository is required
212
+ # include or bundles is required (validated separately)
139
213
  required_fields = {
140
214
  "template-repository": str,
141
- "include": list,
142
215
  }
143
216
 
144
217
  validation_passed = True
@@ -305,6 +378,10 @@ def validate(target: Path) -> bool:
305
378
  if not success or config is None:
306
379
  return False
307
380
 
381
+ # Validate configuration mode (templates OR include)
382
+ if not _validate_configuration_mode(config):
383
+ return False
384
+
308
385
  # Validate required fields
309
386
  validation_passed = _validate_required_fields(config)
310
387
 
@@ -312,8 +389,15 @@ def validate(target: Path) -> bool:
312
389
  if not _validate_repository_format(config):
313
390
  validation_passed = False
314
391
 
315
- if not _validate_include_paths(config):
316
- validation_passed = False
392
+ # Validate templates if present
393
+ if config.get("templates"):
394
+ if not _validate_templates(config):
395
+ validation_passed = False
396
+
397
+ # Validate include if present
398
+ if config.get("include"):
399
+ if not _validate_include_paths(config):
400
+ validation_passed = False
317
401
 
318
402
  # Validate optional fields
319
403
  _validate_optional_fields(config)
rhiza/models.py CHANGED
@@ -11,6 +11,12 @@ from typing import Any
11
11
 
12
12
  import yaml # type: ignore[import-untyped]
13
13
 
14
+ __all__ = [
15
+ "BundleDefinition",
16
+ "RhizaBundles",
17
+ "RhizaTemplate",
18
+ ]
19
+
14
20
 
15
21
  def _normalize_to_list(value: str | list[str] | None) -> list[str]:
16
22
  r"""Convert a value to a list of strings.
@@ -56,6 +62,162 @@ def _normalize_to_list(value: str | list[str] | None) -> list[str]:
56
62
  return []
57
63
 
58
64
 
65
+ @dataclass
66
+ class BundleDefinition:
67
+ """Represents a single bundle from template_bundles.yml.
68
+
69
+ Attributes:
70
+ name: The bundle identifier (e.g., "core", "tests", "github").
71
+ description: Human-readable description of the bundle.
72
+ files: List of file paths included in this bundle.
73
+ workflows: List of workflow file paths included in this bundle.
74
+ depends_on: List of bundle names that this bundle depends on.
75
+ """
76
+
77
+ name: str
78
+ description: str
79
+ files: list[str] = field(default_factory=list)
80
+ workflows: list[str] = field(default_factory=list)
81
+ depends_on: list[str] = field(default_factory=list)
82
+
83
+ def all_paths(self) -> list[str]:
84
+ """Return combined files and workflows."""
85
+ return self.files + self.workflows
86
+
87
+
88
+ @dataclass
89
+ class RhizaBundles:
90
+ """Represents the structure of template_bundles.yml.
91
+
92
+ Attributes:
93
+ version: Version string of the bundles configuration format.
94
+ bundles: Dictionary mapping bundle names to their definitions.
95
+ """
96
+
97
+ version: str
98
+ bundles: dict[str, BundleDefinition] = field(default_factory=dict)
99
+
100
+ @classmethod
101
+ def from_yaml(cls, file_path: Path) -> "RhizaBundles":
102
+ """Load RhizaBundles from a YAML file.
103
+
104
+ Args:
105
+ file_path: Path to the template_bundles.yml file.
106
+
107
+ Returns:
108
+ The loaded bundles configuration.
109
+
110
+ Raises:
111
+ FileNotFoundError: If the file does not exist.
112
+ yaml.YAMLError: If the YAML is malformed.
113
+ ValueError: If the file is invalid or missing required fields.
114
+ TypeError: If bundle data has invalid types.
115
+ """
116
+ with open(file_path) as f:
117
+ config = yaml.safe_load(f)
118
+
119
+ if not config:
120
+ raise ValueError("Bundles file is empty") # noqa: TRY003
121
+
122
+ version = config.get("version")
123
+ if not version:
124
+ raise ValueError("Bundles file missing required field: version") # noqa: TRY003
125
+
126
+ bundles_config = config.get("bundles", {})
127
+ if not isinstance(bundles_config, dict):
128
+ msg = "Bundles must be a dictionary"
129
+ raise TypeError(msg)
130
+
131
+ bundles: dict[str, BundleDefinition] = {}
132
+ for bundle_name, bundle_data in bundles_config.items():
133
+ if not isinstance(bundle_data, dict):
134
+ msg = f"Bundle '{bundle_name}' must be a dictionary"
135
+ raise TypeError(msg)
136
+
137
+ files = _normalize_to_list(bundle_data.get("files"))
138
+ workflows = _normalize_to_list(bundle_data.get("workflows"))
139
+ depends_on = _normalize_to_list(bundle_data.get("depends-on"))
140
+
141
+ bundles[bundle_name] = BundleDefinition(
142
+ name=bundle_name,
143
+ description=bundle_data.get("description", ""),
144
+ files=files,
145
+ workflows=workflows,
146
+ depends_on=depends_on,
147
+ )
148
+
149
+ return cls(version=version, bundles=bundles)
150
+
151
+ def resolve_dependencies(self, bundle_names: list[str]) -> list[str]:
152
+ """Resolve bundle dependencies using topological sort.
153
+
154
+ Args:
155
+ bundle_names: List of bundle names to resolve.
156
+
157
+ Returns:
158
+ Ordered list of bundle names with dependencies first, no duplicates.
159
+
160
+ Raises:
161
+ ValueError: If a bundle doesn't exist or circular dependency detected.
162
+ """
163
+ # Validate all bundles exist
164
+ for name in bundle_names:
165
+ if name not in self.bundles:
166
+ raise ValueError(f"Bundle '{name}' not found in template_bundles.yml") # noqa: TRY003
167
+
168
+ resolved: list[str] = []
169
+ visiting: set[str] = set()
170
+ visited: set[str] = set()
171
+
172
+ def visit(bundle_name: str) -> None:
173
+ if bundle_name in visited:
174
+ return
175
+ if bundle_name in visiting:
176
+ raise ValueError(f"Circular dependency detected involving '{bundle_name}'") # noqa: TRY003
177
+
178
+ visiting.add(bundle_name)
179
+ bundle = self.bundles[bundle_name]
180
+
181
+ for dep in bundle.depends_on:
182
+ if dep not in self.bundles:
183
+ raise ValueError(f"Bundle '{bundle_name}' depends on unknown bundle '{dep}'") # noqa: TRY003
184
+ visit(dep)
185
+
186
+ visiting.remove(bundle_name)
187
+ visited.add(bundle_name)
188
+ resolved.append(bundle_name)
189
+
190
+ for name in bundle_names:
191
+ visit(name)
192
+
193
+ return resolved
194
+
195
+ def resolve_to_paths(self, bundle_names: list[str]) -> list[str]:
196
+ """Convert bundle names to deduplicated file paths.
197
+
198
+ Args:
199
+ bundle_names: List of bundle names to resolve.
200
+
201
+ Returns:
202
+ Deduplicated list of file paths from all bundles and their dependencies.
203
+
204
+ Raises:
205
+ ValueError: If a bundle doesn't exist or circular dependency detected.
206
+ """
207
+ resolved_bundles = self.resolve_dependencies(bundle_names)
208
+ paths: list[str] = []
209
+ seen: set[str] = set()
210
+
211
+ for bundle_name in resolved_bundles:
212
+ bundle = self.bundles[bundle_name]
213
+ for path in bundle.all_paths():
214
+ if path not in seen:
215
+ paths.append(path)
216
+ seen.add(path)
217
+
218
+ return paths
219
+
220
+
59
221
  @dataclass
60
222
  class RhizaTemplate:
61
223
  """Represents the structure of .rhiza/template.yml.
@@ -67,8 +229,10 @@ class RhizaTemplate:
67
229
  Can be None if not specified in the template file (defaults to "main" when creating).
68
230
  template_host: The git hosting platform ("github" or "gitlab").
69
231
  Defaults to "github" if not specified in the template file.
70
- include: List of paths to include from the template repository.
232
+ include: List of paths to include from the template repository (path-based mode).
71
233
  exclude: List of paths to exclude from the template repository (default: empty list).
234
+ templates: List of template names to include (template-based mode).
235
+ Can be used together with include to merge paths.
72
236
  """
73
237
 
74
238
  template_repository: str | None = None
@@ -76,6 +240,7 @@ class RhizaTemplate:
76
240
  template_host: str = "github"
77
241
  include: list[str] = field(default_factory=list)
78
242
  exclude: list[str] = field(default_factory=list)
243
+ templates: list[str] = field(default_factory=list)
79
244
 
80
245
  @classmethod
81
246
  def from_yaml(cls, file_path: Path) -> "RhizaTemplate":
@@ -104,6 +269,7 @@ class RhizaTemplate:
104
269
  template_host=config.get("template-host", "github"),
105
270
  include=_normalize_to_list(config.get("include")),
106
271
  exclude=_normalize_to_list(config.get("exclude")),
272
+ templates=_normalize_to_list(config.get("templates")),
107
273
  )
108
274
 
109
275
  def to_yaml(self, file_path: Path) -> None:
@@ -130,8 +296,13 @@ class RhizaTemplate:
130
296
  if self.template_host and self.template_host != "github":
131
297
  config["template-host"] = self.template_host
132
298
 
133
- # Include is always present as it's a required field for the config to be useful
134
- config["include"] = self.include
299
+ # Write templates if present
300
+ if self.templates:
301
+ config["templates"] = self.templates
302
+
303
+ # Write include if present (can coexist with templates)
304
+ if self.include:
305
+ config["include"] = self.include
135
306
 
136
307
  # Only include exclude if it's not empty
137
308
  if self.exclude:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rhiza
3
- Version: 0.9.1
3
+ Version: 0.10.1
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
@@ -29,7 +29,7 @@ Description-Content-Type: text/markdown
29
29
 
30
30
  <div align="center">
31
31
 
32
- # <img src="https://raw.githubusercontent.com/Jebel-Quant/rhiza/main/assets/rhiza-logo.svg" alt="Rhiza Logo" width="30" style="vertical-align: middle;"> rhiza-cli
32
+ # <img src="https://raw.githubusercontent.com/Jebel-Quant/rhiza/main/.rhiza/assets/rhiza-logo.svg" alt="Rhiza Logo" width="30" style="vertical-align: middle;"> rhiza-cli
33
33
  ![Synced with Rhiza](https://img.shields.io/badge/synced%20with-rhiza-2FA4A9?color=2FA4A9)
34
34
 
35
35
  [![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/downloads/)
@@ -1,21 +1,22 @@
1
1
  rhiza/__init__.py,sha256=4-Dy7AKbJneQNBfv30WhSsUin4y-g4Yp4veO6-YdjVg,1926
2
2
  rhiza/__main__.py,sha256=GlTahVC8F6fKEpMw_nZyhp4PB-fyC9UQ_RjTZcpD9P4,837
3
+ rhiza/bundle_resolver.py,sha256=ErIgZeoxhjL81aXPxMggqIi7-AD6HicPkOBMahVPN1o,2293
3
4
  rhiza/cli.py,sha256=y2qzFbrd29-j5OXslM96hZB9vDLvLv5FBnR9SnDdQWE,10613
4
- rhiza/models.py,sha256=39oWJM13D_5FupCFUguiB8VMe5Mk4O8CPEpjPftNUjs,5039
5
+ rhiza/models.py,sha256=-gVzDQNl_-zWKvdGqx4gL3DSFjbbBHuKAUtyvBMJ13o,10839
5
6
  rhiza/subprocess_utils.py,sha256=Pr5TysIKP76hc64fmqhTd6msMGn5DU43hOSR_v_GFb8,745
6
7
  rhiza/_templates/basic/__init__.py.jinja2,sha256=gs8qN4LAKcdFd6iO9gZVLuVetODmZP_TGuEjWrbinC0,27
7
8
  rhiza/_templates/basic/main.py.jinja2,sha256=uTCahxf9Bftao1IghHue4cSZ9YzBYmBEXeIhEmK9UXQ,362
8
9
  rhiza/_templates/basic/pyproject.toml.jinja2,sha256=Mizpnnd_kFQd-pCWOxG-KWhvg4_ZhZaQppTt2pz0WOc,695
9
10
  rhiza/commands/__init__.py,sha256=DV1nlcXxeeHXmHobVcfOsGeiZBZ55Xz1mud073AXDGc,1839
10
- rhiza/commands/init.py,sha256=4pjNblghF8zFxKJUfw_wA5Ne_xKbovHR7iPxOPQ8zHg,9798
11
- rhiza/commands/materialize.py,sha256=f6B6luANF089Wn4WzXq7V6oGeFdV6UiMY_-JxUyNfwQ,19741
11
+ rhiza/commands/init.py,sha256=fD_Nc-EtNo9Fb0PVRLkN2RYMteVv_BUTinAa2jz0_Q4,10745
12
+ rhiza/commands/materialize.py,sha256=ZKliQu-_L89Y5j_tq2KF5sKmgUgzngcm70MRmKwuVf8,22256
12
13
  rhiza/commands/migrate.py,sha256=A5t4nw7CrdtILCwuSoAqtmM0LpMK8KmX87gzlNgi7fQ,7522
13
14
  rhiza/commands/summarise.py,sha256=vgc7M3dwuGjUuVs3XKQr2_g3qURnQH_R3eFoSSrnOro,11758
14
15
  rhiza/commands/uninstall.py,sha256=6oO7kdv11Bq4JXjrBg9rsFtoRgttQ4m30zGr6NhZrkQ,7479
15
- rhiza/commands/validate.py,sha256=z3ePMPYm2nYOYSxbVNWVU4cz52MNjCIz8ChAMnCsIes,10906
16
+ rhiza/commands/validate.py,sha256=_yGx7ARk5K4hqfwzPpJg_7JoG4un0lqHJYJU-4cmYvI,13948
16
17
  rhiza/commands/welcome.py,sha256=VknKaUh-bD6dM-zcD1eP4H-D_npyezpfF3bTSl_0Dio,2383
17
- rhiza-0.9.1.dist-info/METADATA,sha256=5gMfSF0HgsGGEpE2IlblyWJwXxdzjzAZt6-rzExvkQk,26298
18
- rhiza-0.9.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
19
- rhiza-0.9.1.dist-info/entry_points.txt,sha256=NAwZUpbXvfKv50a_Qq-PxMHl3lcjAyZO63IBeuUNgfY,45
20
- rhiza-0.9.1.dist-info/licenses/LICENSE,sha256=4m5X7LhqX-6D0Ks79Ys8CLpmza8cxDG34g4S9XSNAGY,1077
21
- rhiza-0.9.1.dist-info/RECORD,,
18
+ rhiza-0.10.1.dist-info/METADATA,sha256=OGXKAYAT45X6_JOJZ53fMWlMRAO_sz5vrQgoF3o8GCE,26306
19
+ rhiza-0.10.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
20
+ rhiza-0.10.1.dist-info/entry_points.txt,sha256=NAwZUpbXvfKv50a_Qq-PxMHl3lcjAyZO63IBeuUNgfY,45
21
+ rhiza-0.10.1.dist-info/licenses/LICENSE,sha256=4m5X7LhqX-6D0Ks79Ys8CLpmza8cxDG34g4S9XSNAGY,1077
22
+ rhiza-0.10.1.dist-info/RECORD,,
File without changes