diff --git a/aider/args.py b/aider/args.py index 08c9bde76..6c633d00b 100644 --- a/aider/args.py +++ b/aider/args.py @@ -766,8 +766,8 @@ def get_parser(default_config_files, git_root): is_config_file=True, metavar="CONFIG_FILE", help=( - "Specify the config file (default: search for .aider.conf.yml in git root, cwd" - " or home directory)" + "Specify the config file (default: searches for .aider.conf.yml then .aider.conf.yaml" + " in git root, cwd or home directory)" ), ).complete = shtab.FILE # This is a duplicate of the argument in the preparser and is a no-op by this time of diff --git a/aider/main.py b/aider/main.py index ea344f0ba..9407b8d2a 100644 --- a/aider/main.py +++ b/aider/main.py @@ -448,6 +448,20 @@ def sanity_check_repo(repo, io): return False +def get_config_file(prefix=None): + conf_files = [ + prefix / name if prefix else Path(name) + for name in [".aider.conf.yml", ".aider.conf.yaml"] + if (prefix / name if prefix else Path(name)).exists() + ] + if len(conf_files) > 1: + print( + f"Warning: Both .aider.conf.yml and .aider.conf.yaml are present at {prefix}." + f" Defaulting to {prefix}/.aider.conf.yml." + ) + return conf_files[0] if conf_files else None + + def main(argv=None, input=None, output=None, force_git_root=None, return_coder=False): report_uncaught_exceptions() @@ -461,19 +475,22 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F else: git_root = get_git_root() - conf_fname = Path(".aider.conf.yml") + conf_fname = get_config_file() default_config_files = [] - try: - default_config_files += [conf_fname.resolve()] # CWD - except OSError: - pass + if conf_fname: + try: + default_config_files += [conf_fname.resolve()] # CWD + except OSError: + pass if git_root: - git_conf = Path(git_root) / conf_fname # git root - if git_conf not in default_config_files: - default_config_files.append(git_conf) - default_config_files.append(Path.home() / conf_fname) # homedir + git_conf = get_config_file(Path(git_root)) # git root + if git_conf and git_conf not in default_config_files: + default_config_files.append(git_conf.resolve()) + home_conf = get_config_file(Path.home()) # home dir + if home_conf and home_conf not in default_config_files: + default_config_files.append(home_conf.resolve()) default_config_files = list(map(str, default_config_files)) parser = get_parser(default_config_files, git_root) diff --git a/tests/basic/test_main.py b/tests/basic/test_main.py index ebdc4d450..869f0ea7b 100644 --- a/tests/basic/test_main.py +++ b/tests/basic/test_main.py @@ -406,65 +406,105 @@ class TestMain(TestCase): self.assertRegex(relevant_output, r"dark_mode:\s+True") def test_yaml_config_file_loading(self): - with GitTemporaryDirectory() as git_dir: - git_dir = Path(git_dir) + scenarios = [ + "yml_only", # Test with only .yml files + "yaml_only", # Test with only .yaml files + "both_formats", # Test with both .yml and .yaml files + ] - # Create fake home directory - fake_home = git_dir / "fake_home" - fake_home.mkdir() - os.environ["HOME"] = str(fake_home) + for scenario in scenarios: + with self.subTest(scenario=scenario): + with GitTemporaryDirectory() as git_dir: + git_dir = Path(git_dir) - # Create subdirectory as current working directory - cwd = git_dir / "subdir" - cwd.mkdir() - os.chdir(cwd) + # Create fake home directory + fake_home = git_dir / "fake_home" + fake_home.mkdir() + os.environ["HOME"] = str(fake_home) - # Create .aider.conf.yml files in different locations - home_config = fake_home / ".aider.conf.yml" - git_config = git_dir / ".aider.conf.yml" - cwd_config = cwd / ".aider.conf.yml" - named_config = git_dir / "named.aider.conf.yml" + # Create subdirectory as current working directory + cwd = git_dir / "subdir" + cwd.mkdir() + os.chdir(cwd) - cwd_config.write_text("model: gpt-4-32k\nmap-tokens: 4096\n") - git_config.write_text("model: gpt-4\nmap-tokens: 2048\n") - home_config.write_text("model: gpt-3.5-turbo\nmap-tokens: 1024\n") - named_config.write_text("model: gpt-4-1106-preview\nmap-tokens: 8192\n") + # Create config files in different locations based on scenario + home_config_yml = fake_home / ".aider.conf.yml" + home_config_yaml = fake_home / ".aider.conf.yaml" + git_config_yml = git_dir / ".aider.conf.yml" + git_config_yaml = git_dir / ".aider.conf.yaml" + cwd_config_yml = cwd / ".aider.conf.yml" + cwd_config_yaml = cwd / ".aider.conf.yaml" + named_config = git_dir / "named.aider.conf.yml" - with ( - patch("pathlib.Path.home", return_value=fake_home), - patch("aider.coders.Coder.create") as MockCoder, - ): - # Test loading from specified config file - main( - ["--yes", "--exit", "--config", str(named_config)], - input=DummyInput(), - output=DummyOutput(), - ) - _, kwargs = MockCoder.call_args - self.assertEqual(kwargs["main_model"].name, "gpt-4-1106-preview") - self.assertEqual(kwargs["map_tokens"], 8192) + # Create files based on the scenario + if scenario == "yml_only": + cwd_config_yml.write_text("model: gpt-4-32k\nmap-tokens: 4096\n") + git_config_yml.write_text("model: gpt-4\nmap-tokens: 2048\n") + home_config_yml.write_text("model: gpt-3.5-turbo\nmap-tokens: 1024\n") + named_config.write_text("model: gpt-4-1106-preview\nmap-tokens: 8192\n") + elif scenario == "yaml_only": + cwd_config_yaml.write_text("model: gpt-4-32k\nmap-tokens: 4096\n") + git_config_yaml.write_text("model: gpt-4\nmap-tokens: 2048\n") + home_config_yaml.write_text("model: gpt-3.5-turbo\nmap-tokens: 1024\n") + named_config.write_text("model: gpt-4-1106-preview\nmap-tokens: 8192\n") + else: # both_formats + # Create both formats, with .yaml taking precedence in each directory + cwd_config_yml.write_text("model: gpt-4-32k\nmap-tokens: 4096\n") + cwd_config_yaml.write_text("model: gpt-4-32k\nmap-tokens: 4096\n") + git_config_yml.write_text("model: gpt-4\nmap-tokens: 2048\n") + git_config_yaml.write_text("model: gpt-4\nmap-tokens: 2048\n") + home_config_yml.write_text("model: gpt-3.5-turbo\nmap-tokens: 1024\n") + home_config_yaml.write_text("model: gpt-3.5-turbo\nmap-tokens: 1024\n") + named_config.write_text("model: gpt-4-1106-preview\nmap-tokens: 8192\n") - # Test loading from current working directory - main(["--yes", "--exit"], input=DummyInput(), output=DummyOutput()) - _, kwargs = MockCoder.call_args - print("kwargs:", kwargs) # Add this line for debugging - self.assertIn("main_model", kwargs, "main_model key not found in kwargs") - self.assertEqual(kwargs["main_model"].name, "gpt-4-32k") - self.assertEqual(kwargs["map_tokens"], 4096) + with ( + patch("pathlib.Path.home", return_value=fake_home), + patch("aider.coders.Coder.create") as MockCoder, + ): + # Test loading from specified config file + main( + ["--yes", "--exit", "--config", str(named_config)], + input=DummyInput(), + output=DummyOutput(), + ) + _, kwargs = MockCoder.call_args + self.assertEqual(kwargs["main_model"].name, "gpt-4-1106-preview") + self.assertEqual(kwargs["map_tokens"], 8192) - # Test loading from git root - cwd_config.unlink() - main(["--yes", "--exit"], input=DummyInput(), output=DummyOutput()) - _, kwargs = MockCoder.call_args - self.assertEqual(kwargs["main_model"].name, "gpt-4") - self.assertEqual(kwargs["map_tokens"], 2048) + # Test loading from current working directory + main(["--yes", "--exit"], input=DummyInput(), output=DummyOutput()) + _, kwargs = MockCoder.call_args + self.assertIn("main_model", kwargs, "main_model key not found in kwargs") + self.assertEqual(kwargs["main_model"].name, "gpt-4-32k") + self.assertEqual(kwargs["map_tokens"], 4096) - # Test loading from home directory - git_config.unlink() - main(["--yes", "--exit"], input=DummyInput(), output=DummyOutput()) - _, kwargs = MockCoder.call_args - self.assertEqual(kwargs["main_model"].name, "gpt-3.5-turbo") - self.assertEqual(kwargs["map_tokens"], 1024) + # Test loading from git root by removing cwd config files + if scenario == "yml_only": + cwd_config_yml.unlink() + elif scenario == "yaml_only": + cwd_config_yaml.unlink() + else: # both_formats + cwd_config_yml.unlink() + cwd_config_yaml.unlink() + + main(["--yes", "--exit"], input=DummyInput(), output=DummyOutput()) + _, kwargs = MockCoder.call_args + self.assertEqual(kwargs["main_model"].name, "gpt-4") + self.assertEqual(kwargs["map_tokens"], 2048) + + # Test loading from home directory by removing git root config files + if scenario == "yml_only": + git_config_yml.unlink() + elif scenario == "yaml_only": + git_config_yaml.unlink() + else: # both_formats + git_config_yml.unlink() + git_config_yaml.unlink() + + main(["--yes", "--exit"], input=DummyInput(), output=DummyOutput()) + _, kwargs = MockCoder.call_args + self.assertEqual(kwargs["main_model"].name, "gpt-3.5-turbo") + self.assertEqual(kwargs["map_tokens"], 1024) def test_map_tokens_option(self): with GitTemporaryDirectory():