Skip to content

Fail gracefully if config lacks the mandatory main section#1190

Open
Hi-Angel wants to merge 2 commits intoGothenburgBitFactory:developfrom
Hi-Angel:fix-missing-section-error
Open

Fail gracefully if config lacks the mandatory main section#1190
Hi-Angel wants to merge 2 commits 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 3 times, most recently 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 🤔

@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

@Hi-Angel Hi-Angel force-pushed the fix-missing-section-error branch from 833e60b to fad03e2 Compare March 31, 2026 17:28
@Hi-Angel
Copy link
Copy Markdown
Author

Oh, thank you, you are right! I somehow thought it's iterating over values, my bad! Thank you!

@Hi-Angel Hi-Angel force-pushed the fix-missing-section-error branch from fad03e2 to a61bb2f Compare March 31, 2026 17:34
@Hi-Angel
Copy link
Copy Markdown
Author

Hi-Angel commented Mar 31, 2026

I added the interactive test too.

The CI I presume doesn't pass right now in upstream. It fails on import with ty check inside CI, and I can reproduce it locally too. However, I can reproduce it even if I git checkout HEAD^ to the commit 715bec8, so the ty check failure is certainly unrelated to my changes.

@ryneeverett
Copy link
Copy Markdown
Collaborator

I confirmed that CI is broken upstream by re-running develop and it indeed fails. I presume the underlying cause is somewhere in the releases from ty-0.0.24 in which CI passed and ty-0.0.27 in which we're now observing failure.

Could you also remove the default value in this PR since you're making it unnecessary?

@Hi-Angel
Copy link
Copy Markdown
Author

Hi-Angel commented Mar 31, 2026

Could you also remove the default value in this PR since you're making it unnecessary?

I do? Sorry, I just don't know the details of what interactive does. It was previously mentioned that the default value of interactive may be removed from schema.py, but I just tried to blindly remove it (whether completely or just the False value), and it makes most tests fail with validation failure.

I presume more work is needed for this. I could take a look at it later, but I'd need a clarification what it does and why do we want to remove it 😊

@Hi-Angel
Copy link
Copy Markdown
Author

Could you also remove the default value in this PR since you're making it unnecessary?

I do?

Just to clarify — AFAICT I just add protection against missing main_section. If there's a side-effect due to it being set via a for-cycle — which was an advised solution to this problem — I certainly didn't know/expect it 🤔

@ryneeverett
Copy link
Copy Markdown
Collaborator

No worries, if it breaks the tests then we can worry about it later -- I opened #1193 to track. Basically, interactive was optional because it was only set conditionally for the actual main section used before and now we're setting it for all main sections so we no longer need that.

@Hi-Angel
Copy link
Copy Markdown
Author

Hi-Angel commented Apr 5, 2026

No worries, if it breaks the tests then we can worry about it later -- I opened #1193 to track. Basically, interactive was optional because it was only set conditionally for the actual main section used before and now we're setting it for all main sections so we no longer need that.

Thank you, okay, I did what you asked, somewhat blindly because I don't understand the reasoning… I looked at the tests failures and found that one of them may be fixed by moving interactive = False to validate_config(), which I did in the same comit.

I isolated the discussed change solely inside 2nd commit, because strictly speaking it is a regression (at least in terms of tests passage), whereas the 1st commit is a pure fix with no regressions.

Please review 😊

@Hi-Angel Hi-Angel closed this Apr 5, 2026
@Hi-Angel Hi-Angel reopened this Apr 5, 2026
@ryneeverett
Copy link
Copy Markdown
Collaborator

ryneeverett commented Apr 5, 2026

I looked at the tests failures and found that one of them may be fixed by moving interactive = False to validate_config(), which I did in the same comit.

IMO that's not a terrible approach but it would be better to set interactive in the test code since in any "real" run of bugwarrior the failure to set that key should cause an error.

I wouldn't think there would be very many tests needing changed since the validation.py module was just written. In fact, I would think it might be as simple as the following in tests/config/test_validation.py:

class TestValidation(ConfigTest):
    def setUp(self):
        super().setUp()
        self.config = {
-            'general': {'targets': ['my_service', 'my_kan', 'my_gitlab']},
+            'general': {'targets': ['my_service', 'my_kan', 'my_gitlab'], 'interactive': False},

@Hi-Angel Hi-Angel force-pushed the fix-missing-section-error branch 2 times, most recently from bee3079 to 8303723 Compare April 6, 2026 05:31
@Lotram
Copy link
Copy Markdown
Contributor

Lotram commented Apr 6, 2026

@Hi-Angel
Sorry but I think you were right about your previous change, you do need to add a value for interactive on that line (used when there is a problem with the main section):

main = flavors.get(main_section, MainSectionConfig(targets=[]))

If you need me to take over, I can do it.

@ryneeverett After thinking about it, I don't see any reason to actually put interactive inside the MainSectionConfig object, it could be at the root of Config. We would need to pass it as a parameter to validate_config, but it would simplify everything (no for-loop to set in all flavors, no need to pass it to the "default" MainSectionConfig)

@ryneeverett
Copy link
Copy Markdown
Collaborator

@Hi-Angel Sorry but I think you were right about your previous change, you do need to add a value for interactive on that line (used when there is a problem with the main section):

main = flavors.get(main_section, MainSectionConfig(targets=[]))

I'm confused -- this doesn't seem aligned with your suggestion to move interactive to Config,

@ryneeverett After thinking about it, I don't see any reason to actually put interactive inside the MainSectionConfig object, it could be at the root of Config. We would need to pass it as a parameter to validate_config, but it would simplify everything (no for-loop to set in all flavors, no need to pass it to the "default" MainSectionConfig)

Makes sense to me. This way we can remove it from schema.py altogether which is nice since it isn't actually part of the configuration schema.

@Hi-Angel I also want to express my apologies for our waffling on the design. You were trying to make a simple improvement but you stepped into a sensitive piece of the code base.

@Hi-Angel Hi-Angel force-pushed the fix-missing-section-error branch from 8303723 to 6741181 Compare April 6, 2026 16:37
@Hi-Angel
Copy link
Copy Markdown
Author

Hi-Angel commented Apr 6, 2026

It's okay 😊

So, if I'm correctly reading it, do you want me to return previous version of the 2nd commit…? If yes, then it's done 😊

@Lotram
Copy link
Copy Markdown
Contributor

Lotram commented Apr 7, 2026

I'm confused -- this doesn't seem aligned with your suggestion to move interactive to Config,
Yes, but I wanted to split this into 2 PR:

  1. Let's merge the changes we already have, to fix the error when there is no general section
  2. move interactive to the root Config.

Those changes are not really the same, and @Hi-Angel mostly wanted to fix the first one.

@Hi-Angel , looking at the failing tests, you need to add an interactive value in 2 tests inside test_validation.py:

  • test_flavors
  • test_load_and_validate_example_files

@ryneeverett
Copy link
Copy Markdown
Collaborator

Please rebase now that CI is fixed.

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 6741181 to eb9eb50 Compare April 7, 2026 20:30
@Hi-Angel
Copy link
Copy Markdown
Author

Hi-Angel commented Apr 7, 2026

@Hi-Angel , looking at the failing tests, you need to add an interactive value in 2 tests inside test_validation.py:

  • test_flavors
  • test_load_and_validate_example_files

Okay, fixed, please review.

Per my understanding, the fix in test_load_and_validate_example_files required adding a cycle similar to the one added inside load_config() in the 1st commit.

Please rebase now that CI is fixed.

Done 😊

Per @ryneeverett's request, the value should be removed from the
schema.py. Doing so required changing some of the tests.
@Hi-Angel
Copy link
Copy Markdown
Author

Hi-Angel commented Apr 7, 2026

Oh, I forgot to change the commit description, wait a minute

UPD: done!

@Hi-Angel Hi-Angel force-pushed the fix-missing-section-error branch from eb9eb50 to 924990b Compare April 7, 2026 20:38
@Lotram
Copy link
Copy Markdown
Contributor

Lotram commented Apr 8, 2026

@ryneeverett , these changes look ok to me

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