Skip to content

Set screen formatter colours based on terminal BG #1276

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: main
Choose a base branch
from

Conversation

savchenko
Copy link

@savchenko savchenko commented Jun 15, 2025

Two-stage background detection: $COLORFGBG followed by OSC11.
Fallback to dark if colour can't be detected using the methods above.
Can be overriden by $BANDIT_LIGHT_BG environment variable.

Resolves: #1271

Bright BG

light_bg

Dark BG

dark_bg

Two-stage background detection: $COLORFGBG followed by OSC11.
Fallback to dark if colour can't be detected using the methods above.
Can be overriden by $BANDIT_LIGHT_BG environment variable.
@savchenko
Copy link
Author

Ping @ericwb , @lukehinds , @sigmavirus24

@sigmavirus24
Copy link
Member

Fix the broken CI then someone may spend the time reviewing this

Two-stage background detection: $COLORFGBG followed by OSC11.
Fallback to dark if colour can't be detected using the methods above.
Can be overriden by $BANDIT_LIGHT_BG environment variable.
@savchenko
Copy link
Author

savchenko commented Jun 27, 2025

@sigmavirus24 I take you meant to say "please fix your contribution", not "fix our broken CI" :)
I've made the (only) file I have edited to pass the pre-commit hooks (black, imports, etc)

Running tests as per CONTRIBUTING.md:

tox run -e pep8
tox run -e py37
tox run -e docs
tox run -e cover

From the list above:

  1. tox run -e pep8 implodes spectacularly.
  2. tox run -e py37 does not execute due to unavailable interpreter (3.7 is EOL for a number of years)
  3. tox run -e docs succeeds with a single warning.
  4. tox run -e cover fails with reassuring totals:
======
Totals
======
Ran: 272 tests in 1.5158 sec.
 - Passed: 272
 - Skipped: 0
 - Expected Fail: 0
 - Unexpected Success: 0
 - Failed: 0
Sum of execute time for each test: 4.2907 sec.

[...]

cover: commands[2]> coverage report
No data to report.
cover: exit 1 (0.05 seconds) /home/user/code/bandit> coverage report pid=1823815
  cover: FAIL code 1 (3.53=setup[1.04]+cmd[0.04,2.40,0.05] seconds)
  evaluation failed :( (3.60 seconds)

@sigmavirus24
Copy link
Member

@sigmavirus24 I take you meant to say "please fix your contribution", not "fix our broken CI" :)
I've made the (only) file I have edited to pass the pre-commit hooks (black, imports, etc)

Running tests as per CONTRIBUTING.md:

tox run -e pep8
tox run -e py37
tox run -e docs
tox run -e cover

From the list above:

  1. tox run -e pep8 implodes spectacularly.
  2. tox run -e py37 does not execute due to unavailable interpreter (3.7 is EOL for a number of years)
  3. tox run -e docs succeeds with a single warning.
  4. tox run -e cover fails with reassuring totals:
======
Totals
======
Ran: 272 tests in 1.5158 sec.
 - Passed: 272
 - Skipped: 0
 - Expected Fail: 0
 - Unexpected Success: 0
 - Failed: 0
Sum of execute time for each test: 4.2907 sec.

[...]

cover: commands[2]> coverage report
No data to report.
cover: exit 1 (0.05 seconds) /home/user/code/bandit> coverage report pid=1823815
  cover: FAIL code 1 (3.53=setup[1.04]+cmd[0.04,2.40,0.05] seconds)
  evaluation failed :( (3.60 seconds)

https://github.com/PyCQA/bandit/actions/runs/15929218056/job/44934249349#step:5:42

That's failing due to your change. If you're unwilling to contribute in good faith and instead are going to be hyper-literal, I'm not sure it's worth my spending more time helping you land this

@savchenko
Copy link
Author

@sigmavirus24 , I have no clue what are you talking about.
How else should someone follow the file called CONTRBUTING.md if not literally?
Here is what I see on my side, zero error details:

image

I have tested the patch in foot and XFce terminal; it works as designed.

I have ticked the box "allow edits by maintainers" (which you are). You are very welcome to do further changes that will satisfy the CI. If GitHub machinery can be changed in a way that allows me to see these errors as well - I am equally happy to take a look at them myself.

@sigmavirus24
Copy link
Member

You say pep8 "implodes spectacularly" but you don't share the implosion or try to fix it.

 pep8: commands[0]> flake8 bandit
bandit/formatters/screen.py:160:50: E203 whitespace before ':'
                rgb_part = response[rgb_start + 4 :]
                                                 ^
pep8: exit 1 (0.91 seconds) /home/runner/work/bandit/bandit> flake8 bandit pid=2532
pep8: commands[1]> flake8 tests
pep8: commands[2]> pylint --rcfile=pylintrc bandit
Traceback (most recent call last):
  File "/home/runner/work/bandit/bandit/.tox/pep8/lib/python3.9/site-packages/astroid/astpeephole.py", line 17, in <module>
    _TYPES = (_ast.Str, _ast.Bytes)
AttributeError: module '_ast' has no attribute 'Str'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/runner/work/bandit/bandit/.tox/pep8/bin/pylint", line 8, in <module>
    sys.exit(run_pylint())
  File "/home/runner/work/bandit/bandit/.tox/pep8/lib/python3.9/site-packages/pylint/__init__.py", line 15, in run_pylint
    from pylint.lint import Run
  File "/home/runner/work/bandit/bandit/.tox/pep8/lib/python3.9/site-packages/pylint/lint.py", line 64, in <module>
    import astroid
  File "/home/runner/work/bandit/bandit/.tox/pep8/lib/python3.9/site-packages/astroid/__init__.py", line 67, in <module>
    from astroid.builder import parse, extract_node
  File "/home/runner/work/bandit/bandit/.tox/pep8/lib/python3.9/site-packages/astroid/builder.py", line 26, in <module>
    from astroid import rebuilder
  File "/home/runner/work/bandit/bandit/.tox/pep8/lib/python3.9/site-packages/astroid/rebuilder.py", line 17, in <module>
    from astroid import astpeephole
  File "/home/runner/work/bandit/bandit/.tox/pep8/lib/python3.9/site-packages/astroid/astpeephole.py", line 19, in <module>
    _TYPES = (_ast.Str, )
AttributeError: module '_ast' has no attribute 'Str'

Is what is in the logs. I don't know what's wrong with your browser configuration that you cannot see the logs but I can always see the logs on any project I contribute to regardless of whether I'm a maintainer or not.

tox -e py39 gives you a hint, as to why pep8 is "imploding spectacularly":

 py39: commands[1]> stestr run

=========================
Failures during discovery
=========================
--- import errors ---
Failed to import test module: tests.unit.formatters.test_screen
Traceback (most recent call last):
  File "/opt/hostedtoolcache/Python/3.9.23/x64/lib/python3.9/unittest/loader.py", line 436, in _find_test_path
    module = self._get_module_from_name(name)
  File "/opt/hostedtoolcache/Python/3.9.23/x64/lib/python3.9/unittest/loader.py", line 377, in _get_module_from_name
    __import__(name)
  File "/home/runner/work/bandit/bandit/tests/unit/formatters/test_screen.py", line 16, in <module>
    from bandit.formatters import screen
  File "/home/runner/work/bandit/bandit/bandit/formatters/screen.py", line 70, in <module>
    def term_detect_bg() -> bool | None:
TypeError: unsupported operand type(s) for |: 'type' and 'NoneType'

================================================================================
The above traceback was encountered during test discovery which imports all the found test modules in the specified test_path.

If you see 3.9 is failing here you can run locally tox -e py39. tox -e py37 would fail similarly because you're using syntax only introduced in 3.10. Instead of

def term_detect_bg() -> bool | None:

You can use

import typing as t
# ...
def term_detect_bg() -> t.Optional[bool]:

I'm uninterested in merging this as it doesn't scratch any itch I have so I will not fix this for you.

@savchenko
Copy link
Author

tox run -e pep8
$ tox run -e pep8
.pkg: _optional_hooks> python /home/user/code/bandit/env/lib/python3.13/site-packages/pyproject_api/_backend.py True setuptools.build_meta __legacy__
.pkg: get_requires_for_build_sdist> python /home/user/code/bandit/env/lib/python3.13/site-packages/pyproject_api/_backend.py True setuptools.build_meta __legacy__
.pkg: get_requires_for_build_wheel> python /home/user/code/bandit/env/lib/python3.13/site-packages/pyproject_api/_backend.py True setuptools.build_meta __legacy__
.pkg: get_requires_for_build_editable> python /home/user/code/bandit/env/lib/python3.13/site-packages/pyproject_api/_backend.py True setuptools.build_meta __legacy__
.pkg: build_wheel> python /home/user/code/bandit/env/lib/python3.13/site-packages/pyproject_api/_backend.py True setuptools.build_meta __legacy__
.pkg: build_sdist> python /home/user/code/bandit/env/lib/python3.13/site-packages/pyproject_api/_backend.py True setuptools.build_meta __legacy__
pep8: install_package> pip install --force-reinstall --no-deps /home/user/code/bandit/.tox/.tmp/package/14/bandit-0.0.1.dev1461.tar.gz
pep8: commands[0]> flake8 bandit
pep8: commands[1]> flake8 tests
pep8: commands[2]> pylint --rcfile=pylintrc bandit
/home/user/code/bandit/.tox/pep8/lib/python3.13/site-packages/pylint/__pkginfo__.py:25: UserWarning: pkg_resources is deprecated as an API. See https://setuptools.pypa.io/en/latest/pkg_resources.html. The pkg_resources package is slated for removal as early as 2025-11-30. Refrain from using this package or pin to Setuptools<81.
  from pkg_resources import parse_version
Traceback (most recent call last):
  File "/home/user/code/bandit/.tox/pep8/bin/pylint", line 8, in <module>
    sys.exit(run_pylint())
             ~~~~~~~~~~^^
  File "/home/user/code/bandit/.tox/pep8/lib/python3.13/site-packages/pylint/__init__.py", line 15, in run_pylint
    from pylint.lint import Run
  File "/home/user/code/bandit/.tox/pep8/lib/python3.13/site-packages/pylint/lint.py", line 64, in <module>
    import astroid
  File "/home/user/code/bandit/.tox/pep8/lib/python3.13/site-packages/astroid/__init__.py", line 57, in <module>
    from astroid.nodes import *
  File "/home/user/code/bandit/.tox/pep8/lib/python3.13/site-packages/astroid/nodes.py", line 18, in <module>
    from astroid.node_classes import (
    ...<15 lines>...
    )
  File "/home/user/code/bandit/.tox/pep8/lib/python3.13/site-packages/astroid/node_classes.py", line 25, in <module>
    from astroid import bases
  File "/home/user/code/bandit/.tox/pep8/lib/python3.13/site-packages/astroid/bases.py", line 25, in <module>
    MANAGER = manager.AstroidManager()
              ^^^^^^^^^^^^^^^^^^^^^^
  File "/home/user/code/bandit/.tox/pep8/lib/python3.13/site-packages/lazy_object_proxy/simple.py", line 131, in __getattr__
    return getattr(self.__wrapped__, name)
                   ^^^^^^^^^^^^^^^^
  File "/home/user/code/bandit/.tox/pep8/lib/python3.13/site-packages/lazy_object_proxy/utils.py", line 58, in __get__
    value = obj.__dict__[self.func.__name__] = self.func(obj)
                                               ~~~~~~~~~^^^^^
  File "/home/user/code/bandit/.tox/pep8/lib/python3.13/site-packages/lazy_object_proxy/simple.py", line 81, in __wrapped__
    return factory()
  File "/home/user/code/bandit/.tox/pep8/lib/python3.13/site-packages/astroid/util.py", line 24, in <lambda>
    lambda: importlib.import_module('.' + module_name, 'astroid'))
            ~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.13/importlib/__init__.py", line 88, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/user/code/bandit/.tox/pep8/lib/python3.13/site-packages/astroid/manager.py", line 21, in <module>
    from astroid.interpreter._import import spec
  File "/home/user/code/bandit/.tox/pep8/lib/python3.13/site-packages/astroid/interpreter/_import/spec.py", line 6, in <module>
    import imp
ModuleNotFoundError: No module named 'imp'
pep8: exit 1 (0.08 seconds) /home/user/code/bandit> pylint --rcfile=pylintrc bandit pid=2080145
pep8: command failed but is marked ignore outcome so handling it as success
pep8: commands[3]> bandit-baseline -r bandit -ll -ii
[   INFO ] No output format specified, using terminal
[   INFO ] Got current commit: [6d9ffff9a036a32bd9ab3cd8476029937b5b3b2a term_autocolours]
[   INFO ] Got parent commit: [e16f98f79c7c05352c69c3bfaa901636f834d4d0 remotes/origin/term_autocolours]
[   INFO ] Getting Bandit baseline results
[main]	INFO	profile include tests: None
[main]	INFO	profile exclude tests: None
[main]	INFO	cli include tests: None
[main]	INFO	cli exclude tests: None
[manager]	WARNING	Test in comment: _lines is not a test name or id, ignoring
[manager]	WARNING	Test in comment: is is not a test name or id, ignoring
[manager]	WARNING	Test in comment: a is not a test name or id, ignoring
[manager]	WARNING	Test in comment: dict is not a test name or id, ignoring
[manager]	WARNING	Test in comment: of is not a test name or id, ignoring
[manager]	WARNING	Test in comment: line is not a test name or id, ignoring
[manager]	WARNING	Test in comment: number is not a test name or id, ignoring
[manager]	WARNING	Test in comment: set is not a test name or id, ignoring
[manager]	WARNING	Test in comment: of is not a test name or id, ignoring
[manager]	WARNING	Test in comment: tests is not a test name or id, ignoring
[manager]	WARNING	Test in comment: to is not a test name or id, ignoring
[manager]	WARNING	Test in comment: ignore is not a test name or id, ignoring
[manager]	WARNING	Test in comment: tkelsey is not a test name or id, ignoring
[manager]	WARNING	Test in comment: catching is not a test name or id, ignoring
[manager]	WARNING	Test in comment: expected is not a test name or id, ignoring
[manager]	WARNING	Test in comment: exception is not a test name or id, ignoring
[tester]	WARNING	nosec encountered (B108), but no failed test on line 61
[json]	INFO	JSON output written to file: /home/user/.cache/TMPDIR/tmpqzr_qjzm/_bandit_baseline_run.json_
[   INFO ] Comparing Bandit results to baseline
[main]	INFO	profile include tests: None
[main]	INFO	profile exclude tests: None
[main]	INFO	cli include tests: None
[main]	INFO	cli exclude tests: None
[main]	INFO	running on Python 3.13.3
[manager]	WARNING	Test in comment: _lines is not a test name or id, ignoring
[manager]	WARNING	Test in comment: is is not a test name or id, ignoring
[manager]	WARNING	Test in comment: a is not a test name or id, ignoring
[manager]	WARNING	Test in comment: dict is not a test name or id, ignoring
[manager]	WARNING	Test in comment: of is not a test name or id, ignoring
[manager]	WARNING	Test in comment: line is not a test name or id, ignoring
[manager]	WARNING	Test in comment: number is not a test name or id, ignoring
[manager]	WARNING	Test in comment: set is not a test name or id, ignoring
[manager]	WARNING	Test in comment: of is not a test name or id, ignoring
[manager]	WARNING	Test in comment: tests is not a test name or id, ignoring
[manager]	WARNING	Test in comment: to is not a test name or id, ignoring
[manager]	WARNING	Test in comment: ignore is not a test name or id, ignoring
[manager]	WARNING	Test in comment: tkelsey is not a test name or id, ignoring
[manager]	WARNING	Test in comment: catching is not a test name or id, ignoring
[manager]	WARNING	Test in comment: expected is not a test name or id, ignoring
[manager]	WARNING	Test in comment: exception is not a test name or id, ignoring
[tester]	WARNING	nosec encountered (B108), but no failed test on line 61
Working... ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100% 0:00:00
Run started:2025-06-28 14:35:36.512120

Test results:
	No issues identified.

Code scanned:
	Total lines of code: 8656
	Total lines skipped (#nosec): 0
	Total potential issues skipped due to specifically being disabled (e.g., #nosec BXXX): 10

Run metrics:
	Total issues (by severity):
		Undefined: 0
		Low: 3
		Medium: 0
		High: 1
	Total issues (by confidence):
		Undefined: 0
		Low: 0
		Medium: 1
		High: 3
Files skipped (0):

^[]11;rgb:ffff/ffff/ffff^[\  pep8: OK (4.29=setup[2.77]+cmd[0.11,0.13,0.08,1.20] seconds)
  congratulations :) (4.35 seconds)
$
 /ffff/ffff^[\
tox run -e pep8 from Python 3.9 / Debian 11 container
$ tox run -e pep8
pep8: install_deps> pip install . -r /app/requirements.txt -r /app/test-requirements.txt
Processing /app
  Installing build dependencies ... done
  Getting requirements to build wheel ... done
  Installing backend dependencies ... done
  Preparing metadata (pyproject.toml) ... error

  error: subprocess-exited-with-error
  
  × Preparing metadata (pyproject.toml) did not run successfully.
  │ exit code: 1
  ╰─> [18 lines of output]
      /tmp/pip-build-env-pre14snp/normal/lib/python3.9/site-packages/pbr/util.py:75: UserWarning: pkg_resources is deprecated as an API. See https://setuptools.pypa.io/en/latest/pkg_resources.html. The pkg_resources package is slated for removal as early as 2025-11-30. Refrain from using this package or pin to Setuptools<81.
        import pkg_resources
      Error parsing
      Traceback (most recent call last):
        File "/tmp/pip-build-env-pre14snp/normal/lib/python3.9/site-packages/pbr/core.py", line 105, in pbr
          attrs = util.cfg_to_args(path, dist.script_args)
        File "/tmp/pip-build-env-pre14snp/normal/lib/python3.9/site-packages/pbr/util.py", line 272, in cfg_to_args
          pbr.hooks.setup_hook(config)
        File "/tmp/pip-build-env-pre14snp/normal/lib/python3.9/site-packages/pbr/hooks/__init__.py", line 25, in setup_hook
          metadata_config.run()
        File "/tmp/pip-build-env-pre14snp/normal/lib/python3.9/site-packages/pbr/hooks/base.py", line 27, in run
          self.hook()
        File "/tmp/pip-build-env-pre14snp/normal/lib/python3.9/site-packages/pbr/hooks/metadata.py", line 25, in hook
          self.config['version'] = packaging.get_version(
        File "/tmp/pip-build-env-pre14snp/normal/lib/python3.9/site-packages/pbr/packaging.py", line 866, in get_version
          raise Exception("Versioning for this project requires either an sdist"
      Exception: Versioning for this project requires either an sdist tarball, or access to an upstream git repository. It's also possible that there is a mismatch between the package name in setup.cfg and the argument given to pbr.version.VersionInfo. Project name bandit was given, but was not able to be found.
      error in setup command: Error parsing /app/setup.cfg: Exception: Versioning for this project requires either an sdist tarball, or access to an upstream git repository. It's also possible that there is a mismatch between the package name in setup.cfg and the argument given to pbr.version.VersionInfo. Project name bandit was given, but was not able to be found.
      [end of output]
  
  note: This error originates from a subprocess, and is likely not a problem with pip.
error: metadata-generation-failed

× Encountered error while generating package metadata.
╰─> See above for output.

note: This is an issue with the package mentioned above, not pip.
hint: See above for details.
pep8: exit 1 (1.67 seconds) /app> pip install . -r /app/requirements.txt -r /app/test-requirements.txt pid=435
  pep8: FAIL code 1 (1.68 seconds)
  evaluation failed :( (1.74 seconds)

The stestr run shown in your listing attempts to import modules not present in test-requirements.txt, so it will fail even if code is Python3.9-compliant:

$ stestr run

=========================
Failures during discovery
=========================
--- import errors ---
Failed to import test module: tests.unit.cli.test_baseline
Traceback (most recent call last):
  File "/usr/local/lib/python3.9/unittest/loader.py", line 436, in _find_test_path
    module = self._get_module_from_name(name)
  File "/usr/local/lib/python3.9/unittest/loader.py", line 377, in _get_module_from_name
    __import__(name)
  File "/app/tests/unit/cli/test_baseline.py", line 10, in <module>
    import git
ModuleNotFoundError: No module named 'git'

Failed to import test module: tests.unit.formatters.test_sarif
Traceback (most recent call last):
  File "/usr/local/lib/python3.9/unittest/loader.py", line 436, in _find_test_path
    module = self._get_module_from_name(name)
  File "/usr/local/lib/python3.9/unittest/loader.py", line 377, in _get_module_from_name
    __import__(name)
  File "/app/tests/unit/formatters/test_sarif.py", line 15, in <module>
    from bandit.formatters import sarif
  File "/app/bandit/formatters/sarif.py", line 134, in <module>
    import sarif_om as om
ModuleNotFoundError: No module named 'sarif_om'

================================================================================
The above traceback was encountered during test discovery which imports all the found test modules in the specified test_path.

Anyway, with the latest changes the code should work with Python 3.9-3.13.

Copy link
Member

@ericwb ericwb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So Bandit already has a dependency on rich. I'd rather see a solution that can utilize the rich library to do theming or Styles based on a configurable light or dark mode. I believe this is the direction you're going somewhat now. So how can we capitalize on rich to do it.

https://rich.readthedocs.io/en/stable/style.html#style-themes

@savchenko
Copy link
Author

savchenko commented Jun 29, 2025

@ericwb , I'd be happy to, however I am not certain where and how this would work.
My first take was along the following lines:

def term_serve_colourscheme() -> Dict[str, Style]:
    light = term_detect_bg()
    return {
        "DEFAULT": Style(),
        "HEADER": Style(color="blue", bold=True) if light else Style(color="cyan", bold=True),
        "LOW": Style(color="green", bold=True) if light else Style(color="green", bold=True),
        "MEDIUM": Style(color="magenta", bold=True) if light else Style(color="yellow", bold=True),
        "HIGH": Style(color="red", bold=True) if light else Style(color="red", bold=True),
    }

...but then we will need to change the rest of the code that relies on COLOR being a dict of the particular format with ANSI escapes. Which will result in a much larger refactor.

In regards to the colour detection itself - I have not found anything of use in the documentation, certainly not a "please tell me if it's a dark terminal" utility.

Thoughts?

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.

Allow customising colours
3 participants