Skip to content

Commit c509d10

Browse files
committed
feat(cli): Merge global config with project config
1 parent e4c17b5 commit c509d10

File tree

5 files changed

+54
-42
lines changed

5 files changed

+54
-42
lines changed

src/vectorcode/cli_utils.py

Lines changed: 36 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -482,31 +482,42 @@ def expand_envs_in_dict(d: dict):
482482
stack.append(curr[k])
483483

484484

485-
async def load_config_file(path: Optional[Union[str, Path]] = None):
486-
"""Load config file from ~/.config/vectorcode/config.json(5)"""
487-
if path is None:
488-
for name in ("config.json5", "config.json"):
489-
p = os.path.join(GLOBAL_CONFIG_DIR, name)
490-
if os.path.isfile(p):
491-
path = str(p)
492-
break
493-
if path and os.path.isfile(path):
494-
logger.debug(f"Loading config from {path}")
495-
with open(path) as fin:
496-
content = fin.read()
497-
if content:
498-
config = json5.loads(content)
499-
if isinstance(config, dict):
500-
expand_envs_in_dict(config)
501-
return await Config.import_from(config)
502-
else:
503-
logger.error("Invalid configuration format!")
504-
raise ValueError("Invalid configuration format!")
505-
else:
506-
logger.debug("Skipping empty json file.")
507-
else:
508-
logger.warning("Loading default config.")
509-
return Config()
485+
async def load_config_file(path: str | Path | None = None) -> Config:
486+
"""
487+
Load config object by merging the project-local and the global config files.
488+
489+
Raises `ValueError` if the config file is not a valid json dictionary.
490+
"""
491+
valid_config_paths = []
492+
# default to load from the global config
493+
for name in ("config.json5", "config.json"):
494+
p = os.path.join(GLOBAL_CONFIG_DIR, name)
495+
if os.path.isfile(p):
496+
valid_config_paths.append(str(p))
497+
break
498+
499+
if path:
500+
if os.path.isfile((path)):
501+
valid_config_paths.append(path)
502+
elif os.path.isdir(path):
503+
for name in ("config.json5", "config.json"):
504+
p = os.path.join(path, ".vectorcode", name)
505+
if os.path.isfile(p):
506+
valid_config_paths.append(str(p))
507+
break
508+
509+
final_config = Config()
510+
511+
for p in valid_config_paths:
512+
with open(p) as fin:
513+
content = json5.load(fin)
514+
logger.info(f"Loaded config from {p}")
515+
if not isinstance(content, dict):
516+
raise ValueError("Invalid configuration format!")
517+
final_config = await final_config.merge_from(await Config.import_from(content))
518+
logger.debug(f"Merged config: {final_config}")
519+
520+
return final_config
510521

511522

512523
async def find_project_config_dir(start_from: Union[str, Path] = "."):

src/vectorcode/lsp_main.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
expand_globs,
4747
expand_path,
4848
find_project_root,
49-
get_project_config,
49+
load_config_file,
5050
parse_cli_args,
5151
)
5252
from vectorcode.common import ClientManager, get_collection, list_collection_files
@@ -112,7 +112,7 @@ async def execute_command(ls: LanguageServer, args: list[str]):
112112
parsed_args.project_root = os.path.abspath(str(parsed_args.project_root))
113113

114114
final_configs = await (
115-
await get_project_config(parsed_args.project_root)
115+
await load_config_file(parsed_args.project_root)
116116
).merge_from(parsed_args)
117117
final_configs.pipe = True
118118
else:

src/vectorcode/main.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
CliAction,
1212
config_logging,
1313
find_project_root,
14-
get_project_config,
14+
load_config_file,
1515
parse_cli_args,
1616
)
1717
from vectorcode.common import ClientManager
@@ -43,7 +43,7 @@ async def async_main():
4343

4444
try:
4545
final_configs = await (
46-
await get_project_config(cli_args.project_root)
46+
await load_config_file(cli_args.project_root)
4747
).merge_from(cli_args)
4848
except IOError as e:
4949
traceback.print_exception(e, file=sys.stderr)

tests/test_cli_utils.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,8 @@ async def test_load_config_file_empty_file():
261261
with open(config_path, "w") as f:
262262
f.write("")
263263

264-
assert await load_config_file(config_path) == Config()
264+
with pytest.raises(ValueError):
265+
await load_config_file(config_path)
265266

266267

267268
@pytest.mark.asyncio

tests/test_main.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ async def test_async_main_ioerror(monkeypatch):
4343
"vectorcode.main.parse_cli_args", AsyncMock(return_value=mock_cli_args)
4444
)
4545
monkeypatch.setattr(
46-
"vectorcode.main.get_project_config",
46+
"vectorcode.main.load_config_file",
4747
AsyncMock(side_effect=IOError("Test Error")),
4848
)
4949

@@ -62,7 +62,7 @@ async def test_async_main_cli_action_check(monkeypatch):
6262
mock_check = AsyncMock(return_value=0)
6363
monkeypatch.setattr("vectorcode.subcommands.check", mock_check)
6464
monkeypatch.setattr(
65-
"vectorcode.main.get_project_config",
65+
"vectorcode.main.load_config_file",
6666
AsyncMock(return_value=MagicMock(merge_from=AsyncMock())),
6767
)
6868

@@ -79,7 +79,7 @@ async def test_async_main_cli_action_init(monkeypatch):
7979
)
8080
mock_init = AsyncMock(return_value=0)
8181
monkeypatch.setattr("vectorcode.subcommands.init", mock_init)
82-
monkeypatch.setattr("vectorcode.main.get_project_config", AsyncMock())
82+
monkeypatch.setattr("vectorcode.main.load_config_file", AsyncMock())
8383

8484
return_code = await async_main()
8585
assert return_code == 0
@@ -95,7 +95,7 @@ async def test_async_main_cli_action_chunks(monkeypatch):
9595
mock_chunks = AsyncMock(return_value=0)
9696
monkeypatch.setattr("vectorcode.subcommands.chunks", mock_chunks)
9797
monkeypatch.setattr(
98-
"vectorcode.main.get_project_config", AsyncMock(return_value=Config())
98+
"vectorcode.main.load_config_file", AsyncMock(return_value=Config())
9999
)
100100
monkeypatch.setattr("vectorcode.common.try_server", AsyncMock(return_value=True))
101101

@@ -126,7 +126,7 @@ async def test_async_main_cli_action_prompts(monkeypatch):
126126
mock_prompts = MagicMock(return_value=0)
127127
monkeypatch.setattr("vectorcode.subcommands.prompts", mock_prompts)
128128
monkeypatch.setattr(
129-
"vectorcode.main.get_project_config", AsyncMock(return_value=Config())
129+
"vectorcode.main.load_config_file", AsyncMock(return_value=Config())
130130
)
131131

132132
return_code = await async_main()
@@ -144,7 +144,7 @@ async def test_async_main_cli_action_query(monkeypatch):
144144
db_url="http://test_host:1234", action=CliAction.query, pipe=False
145145
)
146146
monkeypatch.setattr(
147-
"vectorcode.main.get_project_config",
147+
"vectorcode.main.load_config_file",
148148
AsyncMock(
149149
return_value=AsyncMock(
150150
merge_from=AsyncMock(return_value=mock_final_configs)
@@ -175,7 +175,7 @@ async def test_async_main_cli_action_vectorise(monkeypatch):
175175
db_url="http://test_host:1234", action=CliAction.vectorise, include_hidden=True
176176
)
177177
monkeypatch.setattr(
178-
"vectorcode.main.get_project_config",
178+
"vectorcode.main.load_config_file",
179179
AsyncMock(
180180
return_value=AsyncMock(
181181
merge_from=AsyncMock(return_value=mock_final_configs)
@@ -199,7 +199,7 @@ async def test_async_main_cli_action_drop(monkeypatch):
199199
)
200200
mock_final_configs = Config(db_url="http://test_host:1234", action=CliAction.drop)
201201
monkeypatch.setattr(
202-
"vectorcode.main.get_project_config",
202+
"vectorcode.main.load_config_file",
203203
AsyncMock(
204204
return_value=AsyncMock(
205205
merge_from=AsyncMock(return_value=mock_final_configs)
@@ -223,7 +223,7 @@ async def test_async_main_cli_action_ls(monkeypatch):
223223
)
224224
mock_final_configs = Config(db_url="http://test_host:1234", action=CliAction.ls)
225225
monkeypatch.setattr(
226-
"vectorcode.main.get_project_config",
226+
"vectorcode.main.load_config_file",
227227
AsyncMock(
228228
return_value=AsyncMock(
229229
merge_from=AsyncMock(return_value=mock_final_configs)
@@ -259,7 +259,7 @@ async def test_async_main_cli_action_update(monkeypatch):
259259
)
260260
mock_final_configs = Config(db_url="http://test_host:1234", action=CliAction.update)
261261
monkeypatch.setattr(
262-
"vectorcode.main.get_project_config",
262+
"vectorcode.main.load_config_file",
263263
AsyncMock(
264264
return_value=AsyncMock(
265265
merge_from=AsyncMock(return_value=mock_final_configs)
@@ -283,7 +283,7 @@ async def test_async_main_cli_action_clean(monkeypatch):
283283
)
284284
mock_final_configs = Config(db_url="http://test_host:1234", action=CliAction.clean)
285285
monkeypatch.setattr(
286-
"vectorcode.main.get_project_config",
286+
"vectorcode.main.load_config_file",
287287
AsyncMock(
288288
return_value=AsyncMock(
289289
merge_from=AsyncMock(return_value=mock_final_configs)
@@ -307,7 +307,7 @@ async def test_async_main_exception_handling(monkeypatch):
307307
)
308308
mock_final_configs = Config(db_url="http://test_host:1234", action=CliAction.query)
309309
monkeypatch.setattr(
310-
"vectorcode.main.get_project_config",
310+
"vectorcode.main.load_config_file",
311311
AsyncMock(
312312
return_value=AsyncMock(
313313
merge_from=AsyncMock(return_value=mock_final_configs)

0 commit comments

Comments
 (0)