Skip to content

Fail gracefully if config lacks the mandatory main section#1190

Open
Hi-Angel wants to merge 1 commit intoGothenburgBitFactory:developfrom
Hi-Angel:fix-missing-section-error
Open

Fail gracefully if config lacks the mandatory main section#1190
Hi-Angel wants to merge 1 commit intoGothenburgBitFactory:developfrom
Hi-Angel:fix-missing-section-error

Conversation

@Hi-Angel
Copy link
Copy Markdown

@Hi-Angel Hi-Angel commented Mar 23, 2026

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'

@Hi-Angel Hi-Angel force-pushed the fix-missing-section-error branch from 0b5f8e7 to 0457696 Compare March 23, 2026 15:11
@Hi-Angel Hi-Angel changed the title Fail gracefully if config is missing mandatory main section Fail gracefully if config lacks the mandatory main section Mar 23, 2026
@Hi-Angel Hi-Angel force-pushed the fix-missing-section-error branch 2 times, most recently from 50ff230 to b205677 Compare March 23, 2026 15:18
@Hi-Angel
Copy link
Copy Markdown
Author

Oh, I thought __init__.py files were autogenerated. Fixed.

Btw, while at it — I haven't found a way to test bugwarrior directlyr from the repo, so I had to test it by manually copying the files into my system. Would appreciate a pointer on how to make it run from the source code dir.

@Hi-Angel Hi-Angel force-pushed the fix-missing-section-error branch 3 times, most recently from 80cf187 to 3b10984 Compare March 23, 2026 15:34
@ryneeverett
Copy link
Copy Markdown
Collaborator

@Lotram do you want to review this one? I would have thought this would be caught by the schema but that doesn't appear to be the case even in 2.0.0.

Btw, while at it — I haven't found a way to test bugwarrior directlyr from the repo, so I had to test it by manually copying the files into my system. Would appreciate a pointer on how to make it run from the source code dir.

Have you seen the contributing docs?

@Hi-Angel
Copy link
Copy Markdown
Author

Have you seen the contributing docs?

Thanks, I see, so the only way to do this is to create a venv. Gotcha.

@Lotram
Copy link
Copy Markdown
Contributor

Lotram commented Mar 24, 2026

@Lotram do you want to review this one? I would have thought this would be caught by the schema but that doesn't appear to be the case even in 2.0.0.

Sure. The problem is I tested the proper error message is returned inside validate_config (test_main_section_required), but I forgot to test load_config

@Hi-Angel We already have a function validating evertyhing (validate_config), ensuring all messages are consistent, so I think we should keep all the logic there.

IMO, we need to add two tests:

  • Checking the proper error message is raised when the main_section does not exist in the config
  • while ensuring the interactive parameter of load_config is taken into account in the resulting MainSectionConfig object

The quickest way to enforce both tests is to replace the faulty line by something like

    for flavor in rawconfig["flavor"]:
        flavor["interactive"] = interactive

Another solution could be to pass interactive to validate_config, with a default value, to avoid too many changes in the tests.

@ryneeverett
Copy link
Copy Markdown
Collaborator

Thanks, I see, so the only way to do this is to create a venv. Gotcha.

Or you could use uv. Notice the tabs on the code blocks in that doc.

@Hi-Angel
Copy link
Copy Markdown
Author

Thank you for review, FTR, I'll probably look at it on the weekend

@Hi-Angel
Copy link
Copy Markdown
Author

Hi-Angel commented Mar 29, 2026

@Hi-Angel We already have a function validating evertyhing (validate_config), ensuring all messages are consistent, so I think we should keep all the logic there.

@Lotram thank you! I'm looking at it and I'm a bit confused — do you want me to move the logic inside validate_config? If yes, then the problem is that, from a cursory look, validate_config does already check for main_section presence, but the exception happens before validate_config gets chance to run. It is triggered by this line inside load_config():

    […]
    rawconfig['flavor'][main_section]['interactive'] = interactive
    […]

validate_config() resides on the next line.

@Lotram
Copy link
Copy Markdown
Contributor

Lotram commented Mar 29, 2026

Yes, that's why I suggested two possible solutions:

  • replacing rawconfig['flavor'][main_section]['interactive'] = interactive by
    for flavor in rawconfig["flavor"]:
        flavor["interactive"] = interactive

to avoid the KeyError

  • Or remove that line entirely, and pass interactive to validate_config, so it can be set there.

@ryneeverett
Copy link
Copy Markdown
Collaborator

IMO it's a bit more consistent with the current separation of concerns to take the first approach of iterating over the main_sections that actually exist than to set additional properties in validate_config.

@Hi-Angel
Copy link
Copy Markdown
Author

Hi-Angel commented Mar 29, 2026

Yes, that's why I suggested two possible solutions:

Ah, sorry, your suggestions were in context of tests, so I thought you were suggesting about how to implement tests which I did not yet look into.

  • replacing rawconfig['flavor'][main_section]['interactive'] = interactive by
    for flavor in rawconfig["flavor"]:
        flavor["interactive"] = interactive

to avoid the KeyError

In essence, this just bypasses setting interactive if the main_section doesn't exist, but the for-cycle hides the actual intention.

How about being explicit about not setting the interactive like this:

# technically, lack of `main_section` is an error, but detecting such error is the job of `validate_config` below, so do nothing here
if main_section in rawconfig['flavor']:
    rawconfig['flavor'][main_section]['interactive'] = interactive
  • Or remove that line entirely, and pass interactive to validate_config, so it can be set there.

validate_config doesn't seem to use interactive inside it, so it would be odd to have validate_config set it. Besides, the naming implies it is a "checking" function, so it shouldn't mutate whatever was passed inside.

@ryneeverett
Copy link
Copy Markdown
Collaborator

@Hi-Angel I agree that it isn't ideal to set interactive in validate_config, but I would note that none of the separation of concerns is really "clean" here. If you look at what parse_file does, we do a good deal of mutation of the configuration prior to validation (for ini files). And if you look at validate_config, it is also constructing and returning a Config that we use in the rest of the program.

I don't think iterating over the actually existing flavors is all that hacky, and if I'm not mistaken we should also be able to remove the default value from schema.py:

    # added during configuration loading
    #: Interactive status.
    interactive: bool = False

@Hi-Angel
Copy link
Copy Markdown
Author

Alrighty, you folks know the code better, will do the for-cycle then 😊

@Hi-Angel
Copy link
Copy Markdown
Author

So, I'm attempting to run tests on the current develop branch (so not even mine), and I get a bunch of importing errors. I successfully did everything venv-related from the contributing section, down to pip install -e .[all], so presumably I have all the necessary deps installed.

But this is running test_load.py alone:

Details
╰─λ pytest tests/config/test_load.py                                                                                                                                                                                                                                       2 
============================================================================================================================= test session starts =============================================================================================================================
platform linux -- Python 3.14.3, pytest-8.4.2, pluggy-1.6.0
benchmark: 5.0.1 (defaults: timer=time.perf_counter disable_gc=False min_rounds=5 min_time=0.000005 max_time=1.0 calibration_precision=10 warmup=False warmup_iterations=100000)
rootdir: /home/constantine/Projects/bugwarrior
configfile: pyproject.toml
plugins: anyio-4.12.1, asyncio-1.3.0, flaky-3.8.1, benchmark-5.0.1, typeguard-4.5.1
asyncio: mode=Mode.STRICT, debug=False, asyncio_default_fixture_loop_scope=None, asyncio_default_test_loop_scope=function
collected 18 items

tests/config/test_load.py F.................                                                                                                                                                                                                                            [100%]

================================================================================================================================== FAILURES ===================================================================================================================================
__________________________________________________________________________________________________________________________ ExampleTest.test_example ___________________________________________________________________________________________________________________________

self = <tests.config.test_load.ExampleTest testMethod=test_example>

    def test_example(self):
        for rcfile in ('example-bugwarriorrc', 'example-bugwarrior.toml'):
            with self.subTest(rcfile=rcfile):
                os.environ['BUGWARRIORRC'] = str(self.basedir / rcfile)
>               load.load_config('general', False, False)

tests/config/test_load.py:40:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
bugwarrior/config/load.py:124: in load_config
    config = validate_config(rawconfig, main_section, configpath)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
bugwarrior/config/validation.py:166: in validate_config
    ServiceConfigType = get_service_config_union_type(raw_service_configs)
                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
bugwarrior/config/validation.py:130: in get_service_config_union_type
    service_config_classes = tuple(
                             ^^^^^
bugwarrior/config/validation.py:131: in <genexpr>
    get_service(service["service"]).CONFIG_SCHEMA
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
bugwarrior/collect.py:40: in get_service
    return service.load()
           ^^^^^^^^^^^^^^
/usr/lib/python3.14/importlib/metadata/__init__.py:179: in load
    module = import_module(match.group('module'))
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1398: in _gcd_import
    ???
<frozen importlib._bootstrap>:1371: in _find_and_load
    ???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
    ???
<frozen importlib._bootstrap>:938: in _load_unlocked
    ???
<frozen importlib._bootstrap_external>:759: in exec_module
    ???
<frozen importlib._bootstrap>:491: in _call_with_frames_removed
    ???
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

    import csv
    import io as StringIO
    import logging
    import typing
    import urllib.parse

>   import offtrac
E   ModuleNotFoundError: No module named 'offtrac'

bugwarrior/services/trac.py:7: ModuleNotFoundError
============================================================================================================================== warnings summary ===============================================================================================================================
../../../../usr/lib/python3.14/site-packages/taskw/fields/commaseparateduuid.py:9
  /usr/lib/python3.14/site-packages/taskw/fields/commaseparateduuid.py:9: DeprecationWarning: distutils Version classes are deprecated. Use packaging.version instead.
    version = LooseVersion('2.4')

../../../../usr/lib/python3.14/site-packages/taskw/warrior.py:573
  /usr/lib/python3.14/site-packages/taskw/warrior.py:573: DeprecationWarning: distutils Version classes are deprecated. Use packaging.version instead.
    return LooseVersion(taskwarrior_version.decode())

../../../../usr/lib/python3.14/site-packages/taskw/warrior.py:556
  /usr/lib/python3.14/site-packages/taskw/warrior.py:556: DeprecationWarning: distutils Version classes are deprecated. Use packaging.version instead.
    return cls.get_version() > LooseVersion('2')

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================================================================================================================== short test summary info ===========================================================================================================================
FAILED tests/config/test_load.py::ExampleTest::test_example - ModuleNotFoundError: No module named 'offtrac'
================================================================================================================== 1 failed, 17 passed, 3 warnings in 0.42s ===================================================================================================================

and this is running plain pytest:

Details
╰─λ pytest
============================================================================================================================= test session starts =============================================================================================================================
platform linux -- Python 3.14.3, pytest-8.4.2, pluggy-1.6.0
benchmark: 5.0.1 (defaults: timer=time.perf_counter disable_gc=False min_rounds=5 min_time=0.000005 max_time=1.0 calibration_precision=10 warmup=False warmup_iterations=100000)
rootdir: /home/constantine/Projects/bugwarrior
configfile: pyproject.toml
plugins: anyio-4.12.1, asyncio-1.3.0, flaky-3.8.1, benchmark-5.0.1, typeguard-4.5.1
asyncio: mode=Mode.STRICT, debug=False, asyncio_default_fixture_loop_scope=None, asyncio_default_test_loop_scope=function
collected 240 items / 8 errors

=================================================================================================================================== ERRORS ====================================================================================================================================
_____________________________________________________________________________________________________________________ ERROR collecting tests/test_bts.py ______________________________________________________________________________________________________________________
ImportError while importing test module '/home/constantine/Projects/bugwarrior/tests/test_bts.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_bts.py:4: in <module>
    from bugwarrior.services import bts
bugwarrior/services/bts.py:4: in <module>
    import debianbts
E   ModuleNotFoundError: No module named 'debianbts'
___________________________________________________________________________________________________________________ ERROR collecting tests/test_bugzilla.py ___________________________________________________________________________________________________________________
ImportError while importing test module '/home/constantine/Projects/bugwarrior/tests/test_bugzilla.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_bugzilla.py:6: in <module>
    from bugwarrior.services.bz import BugzillaService
bugwarrior/services/bz.py:9: in <module>
    import bugzilla
E   ModuleNotFoundError: No module named 'bugzilla'
____________________________________________________________________________________________________________________ ERROR collecting tests/test_gmail.py _____________________________________________________________________________________________________________________
ImportError while importing test module '/home/constantine/Projects/bugwarrior/tests/test_gmail.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_gmail.py:8: in <module>
    from google.oauth2.credentials import Credentials
E   ModuleNotFoundError: No module named 'google.oauth2'
_____________________________________________________________________________________________________________________ ERROR collecting tests/test_jira.py _____________________________________________________________________________________________________________________
ImportError while importing test module '/home/constantine/Projects/bugwarrior/tests/test_jira.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_jira.py:8: in <module>
    from bugwarrior.services.jira import JiraExtraFields, JiraService
bugwarrior/services/jira.py:7: in <module>
    from jira.client import JIRA as BaseJIRA
E   ModuleNotFoundError: No module named 'jira'
___________________________________________________________________________________________________________________ ERROR collecting tests/test_kanboard.py ___________________________________________________________________________________________________________________
ImportError while importing test module '/home/constantine/Projects/bugwarrior/tests/test_kanboard.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_kanboard.py:5: in <module>
    from bugwarrior.services.kanboard import KanboardService
bugwarrior/services/kanboard.py:7: in <module>
    from kanboard import Client
E   ModuleNotFoundError: No module named 'kanboard'
_____________________________________________________________________________________________________________________ ERROR collecting tests/test_phab.py _____________________________________________________________________________________________________________________
ImportError while importing test module '/home/constantine/Projects/bugwarrior/tests/test_phab.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_phab.py:4: in <module>
    from bugwarrior.services.phab import PhabricatorService
bugwarrior/services/phab.py:4: in <module>
    import phabricator
E   ModuleNotFoundError: No module named 'phabricator'
___________________________________________________________________________________________________________________ ERROR collecting tests/test_todoist.py ____________________________________________________________________________________________________________________
ImportError while importing test module '/home/constantine/Projects/bugwarrior/tests/test_todoist.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_todoist.py:5: in <module>
    from todoist_api_python.models import (
E   ModuleNotFoundError: No module named 'todoist_api_python'
_____________________________________________________________________________________________________________________ ERROR collecting tests/test_trac.py _____________________________________________________________________________________________________________________
ImportError while importing test module '/home/constantine/Projects/bugwarrior/tests/test_trac.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_trac.py:2: in <module>
    from bugwarrior.services.trac import TracService
bugwarrior/services/trac.py:7: in <module>
    import offtrac
E   ModuleNotFoundError: No module named 'offtrac'
============================================================================================================================== warnings summary ===============================================================================================================================
../../../../usr/lib/python3.14/site-packages/taskw/fields/commaseparateduuid.py:9
  /usr/lib/python3.14/site-packages/taskw/fields/commaseparateduuid.py:9: DeprecationWarning: distutils Version classes are deprecated. Use packaging.version instead.
    version = LooseVersion('2.4')

../../../../usr/lib/python3.14/site-packages/taskw/warrior.py:573
  /usr/lib/python3.14/site-packages/taskw/warrior.py:573: DeprecationWarning: distutils Version classes are deprecated. Use packaging.version instead.
    return LooseVersion(taskwarrior_version.decode())

../../../../usr/lib/python3.14/site-packages/taskw/warrior.py:556
  /usr/lib/python3.14/site-packages/taskw/warrior.py:556: DeprecationWarning: distutils Version classes are deprecated. Use packaging.version instead.
    return cls.get_version() > LooseVersion('2')

tests/test_clickup.py:11
  /home/constantine/Projects/bugwarrior/tests/test_clickup.py:11: PytestCollectionWarning: cannot collect test class 'TestData' because it has a __init__ constructor (from: tests/test_clickup.py)
    class TestData:

tests/test_deck.py:11
  /home/constantine/Projects/bugwarrior/tests/test_deck.py:11: PytestCollectionWarning: cannot collect test class 'TestData' because it has a __init__ constructor (from: tests/test_deck.py)
    @dataclasses.dataclass

tests/test_gitbug.py:11
  /home/constantine/Projects/bugwarrior/tests/test_gitbug.py:11: PytestCollectionWarning: cannot collect test class 'TestData' because it has a __init__ constructor (from: tests/test_gitbug.py)
    @dataclasses.dataclass

tests/test_gitlab.py:11
  /home/constantine/Projects/bugwarrior/tests/test_gitlab.py:11: PytestCollectionWarning: cannot collect test class 'TestData' because it has a __init__ constructor (from: tests/test_gitlab.py)
    class TestData:

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================================================================================================================== short test summary info ===========================================================================================================================
ERROR tests/test_bts.py
ERROR tests/test_bugzilla.py
ERROR tests/test_gmail.py
ERROR tests/test_jira.py
ERROR tests/test_kanboard.py
ERROR tests/test_phab.py
ERROR tests/test_todoist.py
ERROR tests/test_trac.py
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Interrupted: 8 errors during collection !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
======================================================================================================================== 7 warnings, 8 errors in 1.62s ========================================================================================================================

@ryneeverett
Copy link
Copy Markdown
Collaborator

You're looking at the "stable" docs. You need the "latest" docs for this purpose: https://bugwarrior.readthedocs.io/en/latest/contributing.html

@Hi-Angel
Copy link
Copy Markdown
Author

Hi-Angel commented Mar 29, 2026

You're looking at the "stable" docs. You need the "latest" docs for this purpose: https://bugwarrior.readthedocs.io/en/latest/contributing.html

I see, so, compared to stable docs the only new thing to do is pip install --group test (my pip is already 25.3, so upgrade step isn't necessary).

Now I did this too, but running pytest commands afterwards I don't seem to see any change in errors. It still complains No module named 'offtrac', etc.

@Lotram
Copy link
Copy Markdown
Contributor

Lotram commented Mar 29, 2026

The message clearly shows that the optional dependencies were not installed. Are you sure the pip install -e .[all] command worked as expected ? I tried to reproduce locally, but I got an error because I'm using zsh, and it interprets the brackets as a glob pattern apparently. I had to run: pip install -e ".[all]". After that, all tests passed

@Hi-Angel
Copy link
Copy Markdown
Author

@Lotram same here, I too am using zsh, and I also used the double quotes to bypass the error. Here's the output from both the command and the pytest combined:

Details
╰─λ pip install -e ".[all]"
Obtaining file:///home/constantine/Projects/bugwarrior
  Installing build dependencies ... done
  Checking if build backend supports build_editable ... done
  Getting requirements to build editable ... done
  Preparing editable metadata (pyproject.toml) ... done
Requirement already satisfied: click in ./.venv/lib/python3.14/site-packages (from bugwarrior==2.1.0.post0) (8.3.1)
Requirement already satisfied: dogpile.cache>=0.5.3 in ./.venv/lib/python3.14/site-packages (from bugwarrior==2.1.0.post0) (1.5.0)
Requirement already satisfied: jinja2>=2.7.2 in ./.venv/lib/python3.14/site-packages (from bugwarrior==2.1.0.post0) (3.1.6)
Requirement already satisfied: lockfile>=0.9.1 in ./.venv/lib/python3.14/site-packages (from bugwarrior==2.1.0.post0) (0.12.2)
Requirement already satisfied: pydantic>=2 in ./.venv/lib/python3.14/site-packages (from pydantic[email]>=2->bugwarrior==2.1.0.post0) (2.12.5)
Requirement already satisfied: python-dateutil in ./.venv/lib/python3.14/site-packages (from bugwarrior==2.1.0.post0) (2.9.0.post0)
Requirement already satisfied: requests in ./.venv/lib/python3.14/site-packages (from bugwarrior==2.1.0.post0) (2.33.0)
Requirement already satisfied: setuptools in ./.venv/lib/python3.14/site-packages (from bugwarrior==2.1.0.post0) (82.0.1)
Requirement already satisfied: taskw>=0.8 in ./.venv/lib/python3.14/site-packages (from bugwarrior==2.1.0.post0) (2.0.0)
Requirement already satisfied: decorator>=4.0.0 in ./.venv/lib/python3.14/site-packages (from dogpile.cache>=0.5.3->bugwarrior==2.1.0.post0) (5.2.1)
Requirement already satisfied: stevedore>=3.0.0 in ./.venv/lib/python3.14/site-packages (from dogpile.cache>=0.5.3->bugwarrior==2.1.0.post0) (5.7.0)
Requirement already satisfied: MarkupSafe>=2.0 in ./.venv/lib/python3.14/site-packages (from jinja2>=2.7.2->bugwarrior==2.1.0.post0) (3.0.3)
Requirement already satisfied: annotated-types>=0.6.0 in ./.venv/lib/python3.14/site-packages (from pydantic>=2->pydantic[email]>=2->bugwarrior==2.1.0.post0) (0.7.0)
Requirement already satisfied: pydantic-core==2.41.5 in ./.venv/lib/python3.14/site-packages (from pydantic>=2->pydantic[email]>=2->bugwarrior==2.1.0.post0) (2.41.5)
Requirement already satisfied: typing-extensions>=4.14.1 in ./.venv/lib/python3.14/site-packages (from pydantic>=2->pydantic[email]>=2->bugwarrior==2.1.0.post0) (4.15.0)
Requirement already satisfied: typing-inspection>=0.4.2 in ./.venv/lib/python3.14/site-packages (from pydantic>=2->pydantic[email]>=2->bugwarrior==2.1.0.post0) (0.4.2)
Requirement already satisfied: email-validator>=2.0.0 in ./.venv/lib/python3.14/site-packages (from pydantic[email]>=2->bugwarrior==2.1.0.post0) (2.3.0)
Requirement already satisfied: dnspython>=2.0.0 in ./.venv/lib/python3.14/site-packages (from email-validator>=2.0.0->pydantic[email]>=2->bugwarrior==2.1.0.post0) (2.8.0)
Requirement already satisfied: idna>=2.0.0 in ./.venv/lib/python3.14/site-packages (from email-validator>=2.0.0->pydantic[email]>=2->bugwarrior==2.1.0.post0) (3.11)
Requirement already satisfied: kitchen in ./.venv/lib/python3.14/site-packages (from taskw>=0.8->bugwarrior==2.1.0.post0) (1.2.6)
Requirement already satisfied: pytz in ./.venv/lib/python3.14/site-packages (from taskw>=0.8->bugwarrior==2.1.0.post0) (2026.1.post1)
Requirement already satisfied: python-debianbts>=4.1.1 in ./.venv/lib/python3.14/site-packages (from bugwarrior==2.1.0.post0) (4.1.1)
Requirement already satisfied: python-bugzilla>=2.0.0 in ./.venv/lib/python3.14/site-packages (from bugwarrior==2.1.0.post0) (3.3.0)
Requirement already satisfied: google-api-python-client in ./.venv/lib/python3.14/site-packages (from bugwarrior==2.1.0.post0) (2.193.0)
Requirement already satisfied: google-auth-oauthlib in ./.venv/lib/python3.14/site-packages (from bugwarrior==2.1.0.post0) (1.3.0)
Requirement already satisfied: ini2toml[full] in ./.venv/lib/python3.14/site-packages (from bugwarrior==2.1.0.post0) (0.15)
Requirement already satisfied: jira>=3.10.0 in ./.venv/lib/python3.14/site-packages (from bugwarrior==2.1.0.post0) (3.10.5)
Requirement already satisfied: kanboard in ./.venv/lib/python3.14/site-packages (from bugwarrior==2.1.0.post0) (1.1.8)
Requirement already satisfied: keyring in ./.venv/lib/python3.14/site-packages (from bugwarrior==2.1.0.post0) (25.7.0)
Requirement already satisfied: phabricator in ./.venv/lib/python3.14/site-packages (from bugwarrior==2.1.0.post0) (0.9.1)
Requirement already satisfied: todoist_api_python>=3.0.0 in ./.venv/lib/python3.14/site-packages (from bugwarrior==2.1.0.post0) (4.0.0)
Requirement already satisfied: offtrac in ./.venv/lib/python3.14/site-packages (from bugwarrior==2.1.0.post0) (0.1.0)
Requirement already satisfied: defusedxml in ./.venv/lib/python3.14/site-packages (from jira>=3.10.0->bugwarrior==2.1.0.post0) (0.7.1)
Requirement already satisfied: packaging in ./.venv/lib/python3.14/site-packages (from jira>=3.10.0->bugwarrior==2.1.0.post0) (26.0)
Requirement already satisfied: requests-oauthlib>=1.1.0 in ./.venv/lib/python3.14/site-packages (from jira>=3.10.0->bugwarrior==2.1.0.post0) (2.0.0)
Requirement already satisfied: requests_toolbelt in ./.venv/lib/python3.14/site-packages (from jira>=3.10.0->bugwarrior==2.1.0.post0) (1.0.0)
Requirement already satisfied: charset_normalizer<4,>=2 in ./.venv/lib/python3.14/site-packages (from requests->bugwarrior==2.1.0.post0) (3.4.6)
Requirement already satisfied: urllib3<3,>=1.26 in ./.venv/lib/python3.14/site-packages (from requests->bugwarrior==2.1.0.post0) (2.6.3)
Requirement already satisfied: certifi>=2023.5.7 in ./.venv/lib/python3.14/site-packages (from requests->bugwarrior==2.1.0.post0) (2026.2.25)
Requirement already satisfied: oauthlib>=3.0.0 in ./.venv/lib/python3.14/site-packages (from requests-oauthlib>=1.1.0->jira>=3.10.0->bugwarrior==2.1.0.post0) (3.3.1)
Requirement already satisfied: dataclass-wizard<1.0,>=0.35.4 in ./.venv/lib/python3.14/site-packages (from todoist_api_python>=3.0.0->bugwarrior==2.1.0.post0) (0.39.1)
Requirement already satisfied: httpx<1,>=0.28.1 in ./.venv/lib/python3.14/site-packages (from todoist_api_python>=3.0.0->bugwarrior==2.1.0.post0) (0.28.1)
Requirement already satisfied: anyio in ./.venv/lib/python3.14/site-packages (from httpx<1,>=0.28.1->todoist_api_python>=3.0.0->bugwarrior==2.1.0.post0) (4.13.0)
Requirement already satisfied: httpcore==1.* in ./.venv/lib/python3.14/site-packages (from httpx<1,>=0.28.1->todoist_api_python>=3.0.0->bugwarrior==2.1.0.post0) (1.0.9)
Requirement already satisfied: h11>=0.16 in ./.venv/lib/python3.14/site-packages (from httpcore==1.*->httpx<1,>=0.28.1->todoist_api_python>=3.0.0->bugwarrior==2.1.0.post0) (0.16.0)
Requirement already satisfied: httplib2<1.0.0,>=0.19.0 in ./.venv/lib/python3.14/site-packages (from google-api-python-client->bugwarrior==2.1.0.post0) (0.31.2)
Requirement already satisfied: google-auth!=2.24.0,!=2.25.0,<3.0.0,>=1.32.0 in ./.venv/lib/python3.14/site-packages (from google-api-python-client->bugwarrior==2.1.0.post0) (2.49.1)
Requirement already satisfied: google-auth-httplib2<1.0.0,>=0.2.0 in ./.venv/lib/python3.14/site-packages (from google-api-python-client->bugwarrior==2.1.0.post0) (0.3.0)
Requirement already satisfied: google-api-core!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.0,<3.0.0,>=1.31.5 in ./.venv/lib/python3.14/site-packages (from google-api-python-client->bugwarrior==2.1.0.post0) (2.30.0)
Requirement already satisfied: uritemplate<5,>=3.0.1 in ./.venv/lib/python3.14/site-packages (from google-api-python-client->bugwarrior==2.1.0.post0) (4.2.0)
Requirement already satisfied: googleapis-common-protos<2.0.0,>=1.56.3 in ./.venv/lib/python3.14/site-packages (from google-api-core!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.0,<3.0.0,>=1.31.5->google-api-python-client->bugwarrior==2.1.0.post0) (1.73.1)
Requirement already satisfied: protobuf<7.0.0,>=4.25.8 in ./.venv/lib/python3.14/site-packages (from google-api-core!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.0,<3.0.0,>=1.31.5->google-api-python-client->bugwarrior==2.1.0.post0) (6.33.6)
Requirement already satisfied: proto-plus<2.0.0,>=1.22.3 in ./.venv/lib/python3.14/site-packages (from google-api-core!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.0,<3.0.0,>=1.31.5->google-api-python-client->bugwarrior==2.1.0.post0) (1.27.2)
Requirement already satisfied: pyasn1-modules>=0.2.1 in ./.venv/lib/python3.14/site-packages (from google-auth!=2.24.0,!=2.25.0,<3.0.0,>=1.32.0->google-api-python-client->bugwarrior==2.1.0.post0) (0.4.2)
Requirement already satisfied: cryptography>=38.0.3 in ./.venv/lib/python3.14/site-packages (from google-auth!=2.24.0,!=2.25.0,<3.0.0,>=1.32.0->google-api-python-client->bugwarrior==2.1.0.post0) (46.0.6)
Requirement already satisfied: pyparsing<4,>=3.1 in ./.venv/lib/python3.14/site-packages (from httplib2<1.0.0,>=0.19.0->google-api-python-client->bugwarrior==2.1.0.post0) (3.3.2)
Requirement already satisfied: cffi>=2.0.0 in ./.venv/lib/python3.14/site-packages (from cryptography>=38.0.3->google-auth!=2.24.0,!=2.25.0,<3.0.0,>=1.32.0->google-api-python-client->bugwarrior==2.1.0.post0) (2.0.0)
Requirement already satisfied: pycparser in ./.venv/lib/python3.14/site-packages (from cffi>=2.0.0->cryptography>=38.0.3->google-auth!=2.24.0,!=2.25.0,<3.0.0,>=1.32.0->google-api-python-client->bugwarrior==2.1.0.post0) (3.0)
Requirement already satisfied: pyasn1<0.7.0,>=0.6.1 in ./.venv/lib/python3.14/site-packages (from pyasn1-modules>=0.2.1->google-auth!=2.24.0,!=2.25.0,<3.0.0,>=1.32.0->google-api-python-client->bugwarrior==2.1.0.post0) (0.6.3)
Requirement already satisfied: configupdater<4,>=3.0.1 in ./.venv/lib/python3.14/site-packages (from ini2toml[full]; extra == "ini2toml"->bugwarrior==2.1.0.post0) (3.2)
Requirement already satisfied: tomlkit<2,>=0.10.0 in ./.venv/lib/python3.14/site-packages (from ini2toml[full]; extra == "ini2toml"->bugwarrior==2.1.0.post0) (0.14.0)
Requirement already satisfied: SecretStorage>=3.2 in ./.venv/lib/python3.14/site-packages (from keyring->bugwarrior==2.1.0.post0) (3.5.0)
Requirement already satisfied: jeepney>=0.4.2 in ./.venv/lib/python3.14/site-packages (from keyring->bugwarrior==2.1.0.post0) (0.9.0)
Requirement already satisfied: jaraco.classes in ./.venv/lib/python3.14/site-packages (from keyring->bugwarrior==2.1.0.post0) (3.4.0)
Requirement already satisfied: jaraco.functools in ./.venv/lib/python3.14/site-packages (from keyring->bugwarrior==2.1.0.post0) (4.4.0)
Requirement already satisfied: jaraco.context in ./.venv/lib/python3.14/site-packages (from keyring->bugwarrior==2.1.0.post0) (6.1.2)
Requirement already satisfied: more-itertools in ./.venv/lib/python3.14/site-packages (from jaraco.classes->keyring->bugwarrior==2.1.0.post0) (10.8.0)
Requirement already satisfied: six>=1.5 in ./.venv/lib/python3.14/site-packages (from python-dateutil->bugwarrior==2.1.0.post0) (1.17.0)
Building wheels for collected packages: bugwarrior
  Building editable for bugwarrior (pyproject.toml) ... done
  Created wheel for bugwarrior: filename=bugwarrior-2.1.0.post0-0.editable-py3-none-any.whl size=17714 sha256=e0101746a9755b924312906f7a011688248fa3d69ea6e458ae071a2aaa258cf0
  Stored in directory: /tmp/pip-ephem-wheel-cache-mbhwc98b/wheels/10/c5/69/08f0487fd5e3938e8f18f7ce85fdb74ceae30b1c50dc394abb
Successfully built bugwarrior
Installing collected packages: bugwarrior
  Attempting uninstall: bugwarrior
    Found existing installation: bugwarrior 2.1.0.post0
    Uninstalling bugwarrior-2.1.0.post0:
      Successfully uninstalled bugwarrior-2.1.0.post0
Successfully installed bugwarrior-2.1.0.post0

[notice] A new release of pip is available: 25.3 -> 26.0.1
[notice] To update, run: pip install --upgrade pip
(.venv) [30.03.2026-00:07:55] constantine@dell-g15  ~/Projects/bugwarrior node-›  ‹› (715bec8)
╰─λ pytest
============================================================================================================================= test session starts =============================================================================================================================
platform linux -- Python 3.14.3, pytest-8.4.2, pluggy-1.6.0
benchmark: 5.0.1 (defaults: timer=time.perf_counter disable_gc=False min_rounds=5 min_time=0.000005 max_time=1.0 calibration_precision=10 warmup=False warmup_iterations=100000)
rootdir: /home/constantine/Projects/bugwarrior
configfile: pyproject.toml
plugins: anyio-4.12.1, asyncio-1.3.0, flaky-3.8.1, benchmark-5.0.1, typeguard-4.5.1
asyncio: mode=Mode.STRICT, debug=False, asyncio_default_fixture_loop_scope=None, asyncio_default_test_loop_scope=function
collected 240 items / 8 errors

=================================================================================================================================== ERRORS ====================================================================================================================================
_____________________________________________________________________________________________________________________ ERROR collecting tests/test_bts.py ______________________________________________________________________________________________________________________
ImportError while importing test module '/home/constantine/Projects/bugwarrior/tests/test_bts.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_bts.py:4: in <module>
    from bugwarrior.services import bts
bugwarrior/services/bts.py:4: in <module>
    import debianbts
E   ModuleNotFoundError: No module named 'debianbts'
___________________________________________________________________________________________________________________ ERROR collecting tests/test_bugzilla.py ___________________________________________________________________________________________________________________
ImportError while importing test module '/home/constantine/Projects/bugwarrior/tests/test_bugzilla.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_bugzilla.py:6: in <module>
    from bugwarrior.services.bz import BugzillaService
bugwarrior/services/bz.py:9: in <module>
    import bugzilla
E   ModuleNotFoundError: No module named 'bugzilla'
____________________________________________________________________________________________________________________ ERROR collecting tests/test_gmail.py _____________________________________________________________________________________________________________________
ImportError while importing test module '/home/constantine/Projects/bugwarrior/tests/test_gmail.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_gmail.py:8: in <module>
    from google.oauth2.credentials import Credentials
E   ModuleNotFoundError: No module named 'google.oauth2'
_____________________________________________________________________________________________________________________ ERROR collecting tests/test_jira.py _____________________________________________________________________________________________________________________
ImportError while importing test module '/home/constantine/Projects/bugwarrior/tests/test_jira.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_jira.py:8: in <module>
    from bugwarrior.services.jira import JiraExtraFields, JiraService
bugwarrior/services/jira.py:7: in <module>
    from jira.client import JIRA as BaseJIRA
E   ModuleNotFoundError: No module named 'jira'
___________________________________________________________________________________________________________________ ERROR collecting tests/test_kanboard.py ___________________________________________________________________________________________________________________
ImportError while importing test module '/home/constantine/Projects/bugwarrior/tests/test_kanboard.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_kanboard.py:5: in <module>
    from bugwarrior.services.kanboard import KanboardService
bugwarrior/services/kanboard.py:7: in <module>
    from kanboard import Client
E   ModuleNotFoundError: No module named 'kanboard'
_____________________________________________________________________________________________________________________ ERROR collecting tests/test_phab.py _____________________________________________________________________________________________________________________
ImportError while importing test module '/home/constantine/Projects/bugwarrior/tests/test_phab.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_phab.py:4: in <module>
    from bugwarrior.services.phab import PhabricatorService
bugwarrior/services/phab.py:4: in <module>
    import phabricator
E   ModuleNotFoundError: No module named 'phabricator'
___________________________________________________________________________________________________________________ ERROR collecting tests/test_todoist.py ____________________________________________________________________________________________________________________
ImportError while importing test module '/home/constantine/Projects/bugwarrior/tests/test_todoist.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_todoist.py:5: in <module>
    from todoist_api_python.models import (
E   ModuleNotFoundError: No module named 'todoist_api_python'
_____________________________________________________________________________________________________________________ ERROR collecting tests/test_trac.py _____________________________________________________________________________________________________________________
ImportError while importing test module '/home/constantine/Projects/bugwarrior/tests/test_trac.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_trac.py:2: in <module>
    from bugwarrior.services.trac import TracService
bugwarrior/services/trac.py:7: in <module>
    import offtrac
E   ModuleNotFoundError: No module named 'offtrac'
============================================================================================================================== warnings summary ===============================================================================================================================
../../../../usr/lib/python3.14/site-packages/taskw/fields/commaseparateduuid.py:9
  /usr/lib/python3.14/site-packages/taskw/fields/commaseparateduuid.py:9: DeprecationWarning: distutils Version classes are deprecated. Use packaging.version instead.
    version = LooseVersion('2.4')

../../../../usr/lib/python3.14/site-packages/taskw/warrior.py:573
  /usr/lib/python3.14/site-packages/taskw/warrior.py:573: DeprecationWarning: distutils Version classes are deprecated. Use packaging.version instead.
    return LooseVersion(taskwarrior_version.decode())

../../../../usr/lib/python3.14/site-packages/taskw/warrior.py:556
  /usr/lib/python3.14/site-packages/taskw/warrior.py:556: DeprecationWarning: distutils Version classes are deprecated. Use packaging.version instead.
    return cls.get_version() > LooseVersion('2')

tests/test_clickup.py:11
  /home/constantine/Projects/bugwarrior/tests/test_clickup.py:11: PytestCollectionWarning: cannot collect test class 'TestData' because it has a __init__ constructor (from: tests/test_clickup.py)
    class TestData:

tests/test_deck.py:11
  /home/constantine/Projects/bugwarrior/tests/test_deck.py:11: PytestCollectionWarning: cannot collect test class 'TestData' because it has a __init__ constructor (from: tests/test_deck.py)
    @dataclasses.dataclass

tests/test_gitbug.py:11
  /home/constantine/Projects/bugwarrior/tests/test_gitbug.py:11: PytestCollectionWarning: cannot collect test class 'TestData' because it has a __init__ constructor (from: tests/test_gitbug.py)
    @dataclasses.dataclass

tests/test_gitlab.py:11
  /home/constantine/Projects/bugwarrior/tests/test_gitlab.py:11: PytestCollectionWarning: cannot collect test class 'TestData' because it has a __init__ constructor (from: tests/test_gitlab.py)
    class TestData:

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================================================================================================================== short test summary info ===========================================================================================================================
ERROR tests/test_bts.py
ERROR tests/test_bugzilla.py
ERROR tests/test_gmail.py
ERROR tests/test_jira.py
ERROR tests/test_kanboard.py
ERROR tests/test_phab.py
ERROR tests/test_todoist.py
ERROR tests/test_trac.py
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Interrupted: 8 errors during collection !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
======================================================================================================================== 7 warnings, 8 errors in 1.68s ========================================================================================================================

@Lotram
Copy link
Copy Markdown
Contributor

Lotram commented Mar 29, 2026

Your tests apparently use /usr/lib/python, not the one from your venv. Are you sure tou ran the activate command ?

@Hi-Angel
Copy link
Copy Markdown
Author

Oh, you were right! This is weird though — I did run this command, and I just pulled it out of my history to execute again, and it certainly was in the same terminal session. But now the tests are passing! Odd.

Thank you!

@Hi-Angel Hi-Angel force-pushed the fix-missing-section-error branch 2 times, most recently from 3a21a67 to f640fbb Compare March 29, 2026 22:02
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'
@Hi-Angel Hi-Angel force-pushed the fix-missing-section-error branch from f640fbb to 833e60b Compare March 29, 2026 22:06
@Hi-Angel
Copy link
Copy Markdown
Author

So, I did the changes… Did the discussed for-cycle solution, but renamed favor variable to section, since from what I understand the cycle iterates over sections.

Stumbled upon a problem: some rawconfig['flavor'] values are strings and not dicts, so I had to also add a check isinstance(section, dict).

IMO, we need to add two tests:

{…}

  • while ensuring the interactive parameter of load_config is taken into account in the resulting MainSectionConfig object

Sorry, I re-read it multiple times, but I didn't understand the second test suggestion. It might be because I'm not familiar with what interactive does; but then again, it's not even used in validate_config, and load_config simply sets it, so… I'm not quite clear what do you want me to test here 🤔


The CI is failing, but from what I understand this doesn't seem to be related to my changes…?

Screenshot_20260330_010815

It fails to resolve tomli module in CI, however the 3 lines I changed couldn't have caused an import fail 🤔

@ryneeverett
Copy link
Copy Markdown
Collaborator

So, I did the changes… Did the discussed for-cycle solution, but renamed favor variable to section, since from what I understand the cycle iterates over sections.

Flavors are a subcategory of sections but refer more specifically to "main section" entries. "Flavor" is more specific and correct.

Stumbled upon a problem: some rawconfig['flavor'] values are strings and not dicts, so I had to also add a check isinstance(section, dict).

Seems like this is a bug in our test data and we should fix the test data rather than working around it.

IMO, we need to add two tests:
{…}

  • while ensuring the interactive parameter of load_config is taken into account in the resulting MainSectionConfig object

Sorry, I re-read it multiple times, but I didn't understand the second test suggestion. It might be because I'm not familiar with what interactive does; but then again, it's not even used in validate_config, and load_config simply sets it, so… I'm not quite clear what do you want me to test here 🤔

I'm not exactly sure what was intended here either, but maybe we should try removing the default value in this PR? That would demonstrate that it is always set prior to validation. In schema.py:

- 
-    # added during configuration loading
-    #: Interactive status.
-    interactive: bool = False
+    interactive: bool

@Hi-Angel
Copy link
Copy Markdown
Author

Stumbled upon a problem: some rawconfig['flavor'] values are strings and not dicts, so I had to also add a check isinstance(section, dict).

Seems like this is a bug in our test data and we should fix the test data rather than working around it.

Looked at it quickly — I think it's actually a bug in parser. The tests with the issue are ExampleTest.test_example (rcfile='example-bugwarriorrc') and ExampleTest.test_example (rcfile='example-bugwarrior.toml'), and in both cases it comes to data inside the configuration files. The strings that appear instead of dict are myflavor, which comes from the section name flavor.myflavor. Renaming it to flavor_myflavor works around the problem, but it still fails on the next string — this time it is, amusingly, general, which only appears in section name [general].

@Lotram
Copy link
Copy Markdown
Contributor

Lotram commented Mar 30, 2026

while ensuring the interactive parameter of load_config is taken into account in the resulting MainSectionConfig object

What I meant is: The naive solution to our KeyError would be to simply remove the rawconfig['flavor'][main_section]['interactive'] = interactive line from load_config. All the tests would pass.
But the interactive value wouldn't be set in the result Config object. That's why I was suggesting a test for that.

Something simple, looking like:

    def test_interactive_flag_propagated(self):
        config = load.load_config('general', interactive=True, quiet=False)
        self.assertTrue(config.main.interactive)

Removing the default value in MainSectionConfig, as @ryneeverett suggests, is also a good idea, though it'll probably require more changes in the tests to ensure we always set the value ?

@ryneeverett
Copy link
Copy Markdown
Collaborator

Stumbled upon a problem: some rawconfig['flavor'] values are strings and not dicts, so I had to also add a check isinstance(section, dict).

Seems like this is a bug in our test data and we should fix the test data rather than working around it.

Looked at it quickly — I think it's actually a bug in parser. The tests with the issue are ExampleTest.test_example (rcfile='example-bugwarriorrc') and ExampleTest.test_example (rcfile='example-bugwarrior.toml'), and in both cases it comes to data inside the configuration files. The strings that appear instead of dict are myflavor, which comes from the section name flavor.myflavor. Renaming it to flavor_myflavor works around the problem, but it still fails on the next string — this time it is, amusingly, general, which only appears in section name [general].

Oh, I see the problem now! We're iterating over the str keys and not the dict values. So instead:

for flavor in rawconfig['flavor'].values():
    flavor['interactive'] = interactive

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants