Repository navigation
[v1.0] Resolve the inert CLI identity parameter in BatteriesIncludedConfigLoader #313
Description
Goal
Remove the misleading required cli_name argument from the public configuration-loader API or give it a documented, testable identity role before the v1 facade is frozen.
Background
BatteriesIncludedConfigLoader publicly requires cli_name and stores it as self.cli_name:
base-cli/lib/python/base_cli/config.py
Lines 125 to 147 in 1df8bc4
| class BatteriesIncludedConfigLoader: | |
| """Load conventional user, project, environment, and explicit layers.""" | |
| def __init__( | |
| self, | |
| cli_name: str, | |
| *, | |
| user_config_dir: Path, | |
| user_config_name: str = "config.yaml", | |
| project_config_name: str = ".base-cli.yaml", | |
| environment_dir_name: str = "environments", | |
| ) -> None: | |
| if _SAFE_FILENAME.fullmatch(user_config_name) is None: | |
| raise ValueError("user_config_name must be a simple filename") | |
| if _SAFE_FILENAME.fullmatch(project_config_name) is None: | |
| raise ValueError("project_config_name must be a simple filename") | |
| if _SAFE_NAME.fullmatch(environment_dir_name) is None: | |
| raise ValueError("environment_dir_name must be a simple directory name") | |
| self.cli_name = cli_name | |
| self.user_config_dir = user_config_dir.expanduser() | |
| self.user_config_name = user_config_name | |
| self.project_config_name = project_config_name | |
| self.environment_dir_name = environment_dir_name |
No loader method reads that attribute. Configuration paths, merge precedence, validation, and returned provenance are identical for any two cli_name values when the explicit path arguments are the same:
base-cli/lib/python/base_cli/config.py
Lines 149 to 215 in 1df8bc4
| @property | |
| def user_config_path(self) -> Path: | |
| return self.user_config_dir / self.user_config_name | |
| def project_config_path(self, project_root: Path | None) -> Path | None: | |
| if project_root is None: | |
| return None | |
| return project_root / self.project_config_name | |
| def _environment_paths( | |
| self, | |
| project_root: Path | None, | |
| environment: str, | |
| ) -> tuple[Path, Path | None]: | |
| user_path = self.user_config_dir / self.environment_dir_name / f"{environment}.yaml" | |
| project_path = ( | |
| project_root / self.environment_dir_name / f"{environment}.yaml" if project_root is not None else None | |
| ) | |
| return user_path, project_path | |
| def load( | |
| self, | |
| project_root: Path | None, | |
| explicit_path: Path | None, | |
| *, | |
| environment: str | None = None, | |
| ) -> ConfigSnapshot: | |
| user_values = load_yaml_file(self.user_config_path) | |
| project_path = self.project_config_path(project_root) | |
| project_values = load_yaml_file(project_path) if project_path is not None else {} | |
| explicit_values = load_yaml_file(explicit_path, required=True) if explicit_path is not None else {} | |
| selected_environment = environment | |
| if selected_environment is None: | |
| candidate = explicit_values.get("environment") | |
| if candidate is None: | |
| candidate = project_values.get("environment", user_values.get("environment")) | |
| selected_environment = candidate if candidate is not None else DEFAULT_ENVIRONMENT | |
| selected_environment = _validate_environment_name(selected_environment) | |
| user_environment_path, project_environment_path = self._environment_paths( | |
| project_root, | |
| selected_environment, | |
| ) | |
| user_environment = load_yaml_file(user_environment_path) | |
| project_environment = load_yaml_file(project_environment_path) if project_environment_path is not None else {} | |
| merged: dict[str, Any] = {} | |
| provenance: dict[str, str] = {} | |
| _merge_mapping(merged, provenance, {"environment": DEFAULT_ENVIRONMENT}, "default") | |
| for source, values in ( | |
| ("user", user_values), | |
| ("project", project_values), | |
| (f"user:environment:{selected_environment}", user_environment), | |
| (f"project:environment:{selected_environment}", project_environment), | |
| ("explicit", explicit_values), | |
| ): | |
| _merge_mapping(merged, provenance, values, source) | |
| framework_values = {key: merged[key] for key in _FRAMEWORK_KEYS if key in merged} | |
| framework = _validate_framework_config(framework_values) | |
| consumer_config = {key: value for key, value in merged.items() if key not in _FRAMEWORK_KEYS} | |
| return ConfigSnapshot( | |
| config=consumer_config, | |
| framework=framework, | |
| provenance=MappingProxyType(dict(provenance)), | |
| ) |
The generated API reference advertises the parameter as required without explaining an effect. This is an API/UX inconsistency: consumers must invent a value, may assume it namespaces configuration, and cannot tell whether changing it is safe. It should be resolved before #242 freezes the public facade and alongside #267's identity work.
Scope
Choose and document one coherent contract:
- remove the parameter with the pre-1.0 migration/deprecation treatment; or
- make it control a specific validated identity behavior without duplicating caller-provided path policy.
Audit other public constructors for stored-but-inert compatibility parameters.
Acceptance criteria
- Every required public constructor parameter has a documented observable purpose.
- Two loaders with different identities either have intentionally different behavior covered by tests, or no identity parameter exists.
- The change is represented in API stability/migration notes.
- Type hints, generated API reference, examples, and profile factory calls agree.
- [v1.0] Segment and freeze the public API facade #242's facade inventory records the final decision.
Validation
Add public-signature and behavior tests for the chosen contract and run strict consumer typing.
Project fields
- Status: Backlog
- Priority: P2
- Area: Python
- Initiative: v1.0 Readiness
- Size: S
Ownership
- Assignee: @codeforester
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsDone
Activity
fix: derive isolated config identity from CLI name (#325)
codeforester commentedon Sep 2, 2026
Implemented and merged in PR #325 (config identity and isolated default namespaces).