Skip to content

Commit a61bb2f

Browse files
committed
Fail gracefully if config lacks the mandatory main section
Currently, if a user didn't put `[general]` section in config file, bugwarrior fails like this: Traceback (most recent call last): File "/usr/bin/bugwarrior", line 8, in <module> sys.exit(cli()) ~~~^^ File "/usr/lib/python3.14/site-packages/click/core.py", line 1485, in __call__ return self.main(*args, **kwargs) ~~~~~~~~~^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.14/site-packages/click/core.py", line 1406, in main rv = self.invoke(ctx) File "/usr/lib/python3.14/site-packages/click/core.py", line 1873, in invoke return _process_result(sub_ctx.command.invoke(sub_ctx)) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^ File "/usr/lib/python3.14/site-packages/click/core.py", line 1269, in invoke return ctx.invoke(self.callback, **ctx.params) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.14/site-packages/click/core.py", line 824, in invoke return callback(*args, **kwargs) File "/usr/lib/python3.14/site-packages/click/decorators.py", line 34, in new_func return f(get_current_context(), *args, **kwargs) File "/usr/lib/python3.14/site-packages/bugwarrior/command.py", line 62, in wrapped_subcommand_callback return ctx.invoke(subcommand_callback, *args, **kwargs) ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.14/site-packages/click/core.py", line 824, in invoke return callback(*args, **kwargs) File "/usr/lib/python3.14/site-packages/bugwarrior/command.py", line 105, in pull config = _try_load_config(main_section, interactive, quiet) File "/usr/lib/python3.14/site-packages/bugwarrior/command.py", line 35, in _try_load_config return load_config(main_section, interactive, quiet) File "/usr/lib/python3.14/site-packages/bugwarrior/config/load.py", line 122, in load_config rawconfig['flavor'][main_section]['interactive'] = interactive ~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^ KeyError: 'general' This is confusing — it looks more like bugwarrior broke due to some API change in Python, rather than because of a user mistake. So handle this case with explicit check. Now it will fail instead like this: Validation error found in /home/constantine/.config/bugwarrior/bugwarriorrc See https://bugwarrior.readthedocs.io No section: 'general'
1 parent 715bec8 commit a61bb2f

File tree

2 files changed

+30
-1
lines changed

2 files changed

+30
-1
lines changed

bugwarrior/config/load.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,8 @@ def parse_file(configpath: str) -> dict:
120120
def load_config(main_section, interactive, quiet) -> Config:
121121
configpath = get_config_path()
122122
rawconfig = parse_file(configpath)
123-
rawconfig['flavor'][main_section]['interactive'] = interactive
123+
for flavor in rawconfig['flavor'].values():
124+
flavor['interactive'] = interactive
124125
config = validate_config(rawconfig, main_section, configpath)
125126
configure_logging(
126127
config.main.log_file, 'WARNING' if quiet else config.main.log_level

tests/config/test_load.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,3 +263,31 @@ def test_ini_wrong_prefix(self):
263263

264264
with self.assertRaises(SystemExit):
265265
load.parse_file(config_path)
266+
267+
268+
class TestLoadConfig(LoadTest):
269+
def setUp(self):
270+
self.basedir = Path(__file__).parent
271+
super().setUp()
272+
273+
def test_main_section_does_not_exist(self):
274+
config_path = self.create(".bugwarriorrc")
275+
with open(config_path, 'w') as fout:
276+
fout.write(
277+
textwrap.dedent("""
278+
[redmine]
279+
service = redmine
280+
redmine.url = example.com
281+
""")
282+
)
283+
284+
with self.assertRaises(SystemExit):
285+
load.load_config("general", False, False)
286+
287+
self.assertEqual(len(self.caplog.records), 1)
288+
self.assertIn("No section: 'general'", self.caplog.records[0].message)
289+
290+
def test_interactive_flag_propagated(self):
291+
os.environ['BUGWARRIORRC'] = str(self.basedir / 'example-bugwarriorrc')
292+
config = load.load_config('general', interactive=True, quiet=False)
293+
self.assertTrue(config.main.interactive)

0 commit comments

Comments
 (0)