Skip to content

Commit dbed11c

Browse files
authored
CG-10801: support --global flag in config cmds (#487)
1 parent 7dd3a04 commit dbed11c

File tree

6 files changed

+70
-55
lines changed

6 files changed

+70
-55
lines changed

src/codegen/cli/commands/config/main.py

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
from codegen.cli.auth.session import CodegenSession
99
from codegen.cli.workspace.decorators import requires_init
10+
from codegen.shared.configs.session_configs import global_config
1011

1112

1213
@click.group(name="config")
@@ -17,7 +18,8 @@ def config_command():
1718

1819
@config_command.command(name="list")
1920
@requires_init
20-
def list_command(session: CodegenSession):
21+
@click.option("--global", "is_global", is_flag=True, help="Lists the global configuration values")
22+
def list_command(session: CodegenSession, is_global: bool):
2123
"""List current configuration values."""
2224
table = Table(title="Configuration Values", border_style="blue", show_header=True)
2325
table.add_column("Key", style="cyan", no_wrap=True)
@@ -37,7 +39,8 @@ def flatten_dict(data: dict, prefix: str = "") -> dict:
3739
return items
3840

3941
# Get flattened config and sort by keys
40-
flat_config = flatten_dict(session.config.model_dump())
42+
config = global_config.global_session if is_global else session.config
43+
flat_config = flatten_dict(config.model_dump())
4144
sorted_items = sorted(flat_config.items(), key=lambda x: x[0])
4245

4346
# Group by top-level prefix
@@ -58,9 +61,11 @@ def get_prefix(item):
5861
@config_command.command(name="get")
5962
@requires_init
6063
@click.argument("key")
61-
def get_command(session: CodegenSession, key: str):
64+
@click.option("--global", "is_global", is_flag=True, help="Get the global configuration value")
65+
def get_command(session: CodegenSession, key: str, is_global: bool):
6266
"""Get a configuration value."""
63-
value = session.config.get(key)
67+
config = global_config.global_session if is_global else session.config
68+
value = config.get(key)
6469
if value is None:
6570
rich.print(f"[red]Error: Configuration key '{key}' not found[/red]")
6671
return
@@ -72,16 +77,18 @@ def get_command(session: CodegenSession, key: str):
7277
@requires_init
7378
@click.argument("key")
7479
@click.argument("value")
75-
def set_command(session: CodegenSession, key: str, value: str):
80+
@click.option("--global", "is_global", is_flag=True, help="Sets the global configuration value")
81+
def set_command(session: CodegenSession, key: str, value: str, is_global: bool):
7682
"""Set a configuration value and write to config.toml."""
77-
cur_value = session.config.get(key)
83+
config = global_config.global_session if is_global else session.config
84+
cur_value = config.get(key)
7885
if cur_value is None:
7986
rich.print(f"[red]Error: Configuration key '{key}' not found[/red]")
8087
return
8188

8289
if cur_value.lower() != value.lower():
8390
try:
84-
session.config.set(key, value)
91+
config.set(key, value)
8592
except Exception as e:
8693
logging.exception(e)
8794
rich.print(f"[red]{e}[/red]")

src/codegen/shared/configs/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,4 @@
1818
GLOBAL_CONFIG_DIR = Path("~/.config/codegen-sh").expanduser()
1919
AUTH_FILE = GLOBAL_CONFIG_DIR / "auth.json"
2020
SESSION_FILE = GLOBAL_CONFIG_DIR / "session.json"
21+
GLOBAL_CONFIG_PATH = GLOBAL_CONFIG_DIR / CONFIG_FILENAME

src/codegen/shared/configs/models/global_session.py renamed to src/codegen/shared/configs/models/global_config.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@
66
from pydantic_settings import BaseSettings
77

88
from codegen.shared.configs.constants import SESSION_FILE
9+
from codegen.shared.configs.models.session import SessionConfig
910

1011

11-
class GlobalSessionConfig(BaseSettings):
12+
class GlobalConfig(BaseSettings):
1213
active_session_path: str | None = None
1314
sessions: list[str]
15+
global_session: SessionConfig
1416

1517
def get_session(self, session_root_path: Path) -> str | None:
1618
return next((s for s in self.sessions if s == str(session_root_path)), None)
@@ -27,8 +29,8 @@ def set_active_session(self, session_root_path: Path) -> None:
2729
raise ValueError(msg)
2830

2931
self.active_session_path = str(session_root_path)
30-
if session_root_path.name not in self.sessions:
31-
self.sessions.append(str(session_root_path))
32+
if self.active_session_path not in self.sessions:
33+
self.sessions.append(self.active_session_path)
3234

3335
self.save()
3436

@@ -38,3 +40,11 @@ def save(self) -> None:
3840

3941
with open(SESSION_FILE, "w") as f:
4042
json.dump(self.model_dump(), f)
43+
44+
self.global_session.save()
45+
46+
def __str__(self) -> str:
47+
active = self.active_session_path or "None"
48+
sessions_str = "\n ".join(self.sessions) if self.sessions else "None"
49+
50+
return f"GlobalConfig:\n Active Session: {active}\n Sessions:\n {sessions_str}\n Global Session:\n {self.global_session}"

src/codegen/shared/configs/session_configs.py

Lines changed: 26 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,43 +3,53 @@
33

44
import tomllib
55

6-
from codegen.shared.configs.constants import CONFIG_PATH, SESSION_FILE
7-
from codegen.shared.configs.models.global_session import GlobalSessionConfig
6+
from codegen.shared.configs.constants import CONFIG_PATH, GLOBAL_CONFIG_PATH, SESSION_FILE
7+
from codegen.shared.configs.models.global_config import GlobalConfig
88
from codegen.shared.configs.models.session import SessionConfig
99

1010

11-
def load_session_config(config_path: Path) -> SessionConfig:
11+
def load_session_config(config_path: Path, base_config: SessionConfig | None = None) -> SessionConfig:
1212
"""Loads configuration from various sources."""
13-
# Load from .env file
14-
env_config = _load_from_env(config_path)
15-
16-
# Load from .codegen/config.toml file
13+
base_config = base_config or global_config.global_session
1714
toml_config = _load_from_toml(config_path)
18-
19-
# Merge configurations recursively
20-
config_dict = _merge_configs(env_config.model_dump(), toml_config.model_dump())
15+
config_dict = _merge_configs(base_config.model_dump(), toml_config)
2116
loaded_config = SessionConfig(**config_dict)
17+
loaded_config.file_path = str(config_path)
2218

2319
# Save the configuration to file if it doesn't exist
2420
if not config_path.exists():
2521
loaded_config.save()
2622
return loaded_config
2723

2824

25+
def _load_global_config() -> GlobalConfig:
26+
"""Load configuration from the JSON file."""
27+
base_session = _load_from_env(GLOBAL_CONFIG_PATH)
28+
global_session = load_session_config(GLOBAL_CONFIG_PATH, base_config=base_session)
29+
30+
if SESSION_FILE.exists():
31+
with open(SESSION_FILE) as f:
32+
json_config = json.load(f)
33+
json_config["global_session"] = global_session.model_dump()
34+
return GlobalConfig.model_validate(json_config, strict=False)
35+
36+
new_config = GlobalConfig(sessions=[], global_session=global_session)
37+
new_config.save()
38+
return new_config
39+
40+
2941
def _load_from_env(config_path: Path) -> SessionConfig:
3042
"""Load configuration from the environment variables."""
3143
return SessionConfig(file_path=str(config_path))
3244

3345

34-
def _load_from_toml(config_path: Path) -> SessionConfig:
46+
def _load_from_toml(config_path: Path) -> dict[str, any]:
3547
"""Load configuration from the TOML file."""
3648
if config_path.exists():
3749
with open(config_path, "rb") as f:
3850
toml_config = tomllib.load(f)
39-
toml_config["file_path"] = str(config_path)
40-
return SessionConfig.model_validate(toml_config, strict=False)
41-
42-
return SessionConfig(file_path=str(config_path))
51+
return toml_config
52+
return {}
4353

4454

4555
def _merge_configs(base: dict, override: dict) -> dict:
@@ -55,20 +65,8 @@ def _merge_configs(base: dict, override: dict) -> dict:
5565
return merged
5666

5767

58-
def _load_global_config() -> GlobalSessionConfig:
59-
"""Load configuration from the JSON file."""
60-
if SESSION_FILE.exists():
61-
with open(SESSION_FILE) as f:
62-
json_config = json.load(f)
63-
return GlobalSessionConfig.model_validate(json_config, strict=False)
64-
65-
new_config = GlobalSessionConfig(sessions=[])
66-
new_config.save()
67-
return new_config
68-
69-
70-
config = load_session_config(CONFIG_PATH)
7168
global_config = _load_global_config()
69+
config = load_session_config(CONFIG_PATH)
7270

7371

7472
if __name__ == "__main__":

tests/shared/configs/sample_config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
openai_api_key = "sk-123456"
66
77
[repository]
8-
organization_name = "test-org"
8+
full_name = "test-org/test-repo"
99
repo_name = "test-repo"
1010
1111
[feature_flags.codebase]

tests/unit/codegen/shared/configs/test_config.py

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -43,22 +43,22 @@ def test_merge_configs_empty_string():
4343
# Test _load_from_toml
4444
def test_load_from_toml_existing_file(temp_config_file):
4545
config = _load_from_toml(temp_config_file)
46-
assert isinstance(config, SessionConfig)
47-
assert config.secrets.github_token == "gh_token123"
48-
assert config.repository.repo_name == "test-repo"
49-
assert config.feature_flags.codebase.debug is True
50-
assert config.feature_flags.codebase.typescript.ts_dependency_manager is True
51-
assert config.feature_flags.codebase.import_resolution_overrides == {"@org/pkg": "./local/path"}
46+
assert isinstance(config, dict)
47+
assert config["secrets"]["github_token"] == "gh_token123"
48+
assert config["repository"]["full_name"] == "test-org/test-repo"
49+
assert config["feature_flags"]["codebase"]["debug"] is True
50+
assert config["feature_flags"]["codebase"]["typescript"]["ts_dependency_manager"] is True
51+
assert config["feature_flags"]["codebase"]["import_resolution_overrides"] == {"@org/pkg": "./local/path"}
5252

5353

5454
@patch.dict("os.environ", {})
5555
@patch("codegen.shared.configs.models.secrets.SecretsConfig.model_config", {"env_file": "nonexistent.env"})
5656
def test_load_from_toml_nonexistent_file():
5757
config = _load_from_toml(Path("nonexistent.toml"))
58-
assert isinstance(config, SessionConfig)
59-
assert config.secrets.github_token is None
60-
assert config.repository.full_name is None
61-
assert config.feature_flags.codebase.debug is False
58+
assert isinstance(config, dict)
59+
assert "secrets" not in config
60+
assert "repository" not in config
61+
assert "feature_flags" not in config
6262

6363

6464
# Test _load_from_env
@@ -72,21 +72,20 @@ def test_load_from_env():
7272

7373
# Test load function
7474
@patch.dict("os.environ", {}, clear=True) # Clear all env vars for this test
75-
@patch("codegen.shared.configs.session_configs._load_from_env")
7675
@patch("codegen.shared.configs.session_configs._load_from_toml")
7776
@patch("codegen.shared.configs.models.secrets.SecretsConfig.model_config", {"env_file": None, "env_prefix": "CODEGEN_SECRETS__"})
78-
def test_load_with_both_configs(mock_toml, mock_env):
77+
def test_load_with_both_configs(mock_toml):
7978
# Setup mock returns
80-
mock_env.return_value = SessionConfig(file_path=str(CONFIG_PATH), secrets=SecretsConfig(github_token="env_token"), feature_flags=FeatureFlagsConfig(codebase=CodebaseFeatureFlags(debug=True)))
81-
mock_toml.return_value = SessionConfig(file_path=str(CONFIG_PATH), secrets={"openai_api_key": "openai_key"}, repository={"full_name": "codegen-org/test-repo"})
79+
base_config = SessionConfig(file_path=str(CONFIG_PATH), secrets=SecretsConfig(github_token="env_token"), feature_flags=FeatureFlagsConfig(codebase=CodebaseFeatureFlags(debug=True)))
80+
mock_toml.return_value = {"secrets": {"openai_api_key": "openai_key"}, "repository": {"full_name": "codegen-org/test-repo"}}
8281

83-
config = load_session_config(CONFIG_PATH)
82+
config = load_session_config(CONFIG_PATH, base_config)
8483

8584
assert isinstance(config, SessionConfig)
8685
assert config.secrets.github_token == "env_token"
8786
assert config.secrets.openai_api_key == "openai_key"
8887
assert config.repository.full_name == "codegen-org/test-repo"
89-
assert config.feature_flags.codebase.debug is False
88+
assert config.feature_flags.codebase.debug is True
9089

9190

9291
# Error cases

0 commit comments

Comments
 (0)