Skip to content
Draft
2 changes: 2 additions & 0 deletions .github/workflows/PR.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ jobs:
run: poetry install -vvv
- name: Lint the Code
run: poetry run ni-python-styleguide lint
- name: (bandit) Lint the Code
run: poetry run ni-python-styleguide bandit

tests:
runs-on: ${{ matrix.os }}
Expand Down
20 changes: 16 additions & 4 deletions ni_python_styleguide/_cli.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import logging
import pathlib
import sys

import click
import toml

from ni_python_styleguide import _acknowledge_existing_errors
from ni_python_styleguide import _fix
from ni_python_styleguide import _Flake8Error
from ni_python_styleguide import _lint
from ni_python_styleguide import _acknowledge_existing_errors, _fix, _Flake8Error, _lint


def _qs_or_vs(verbosity):
Expand Down Expand Up @@ -190,3 +188,17 @@ def fix(obj, extend_ignore, file_or_dir, aggressive):
file_or_dir=file_or_dir or [pathlib.Path.cwd()],
aggressive=aggressive,
)


@main.command()
@click.argument("file_or_dir", nargs=-1)
@click.pass_obj
def bandit(obj, file_or_dir):
"""Run Bandit security linter on the file(s)/directory(s) given."""
logging.basicConfig(level=logging.DEBUG)
pyproj_bandit_config = obj.get("PYPROJECT", {}).get("tool", {}).get("bandit", {})
_lint.lint_bandit(
qs_or_vs=_qs_or_vs(obj["VERBOSITY"]),
pyproject_config=pyproj_bandit_config,
file_or_dir=file_or_dir or [pathlib.Path.cwd()],
Copy link
Contributor

Choose a reason for hiding this comment

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

FYI, there is an optional targets key in the config file.

I think defaulting to . and supporting excludes is probably enough for most projects, though.

)
1 change: 1 addition & 0 deletions ni_python_styleguide/_config_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
FLAKE8_CONFIG_FILE = __FILE_DIR / "config.ini"
BLACK_CONFIG_FILE = __FILE_DIR / "config.toml"
ISORT_CONFIG_FILE = BLACK_CONFIG_FILE
BANDIT_CONFIG_FILE = BLACK_CONFIG_FILE
79 changes: 77 additions & 2 deletions ni_python_styleguide/_lint.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
"""Linting methods."""

import contextlib
import copy
import io
import logging
import pathlib
import sys
import typing

import bandit.cli.main
import flake8.main.application
import toml

from ni_python_styleguide import _config_constants, _Flake8Error, _utils

from ni_python_styleguide import _config_constants
from ni_python_styleguide import _Flake8Error
_logger = logging.getLogger(__name__)
_logger.addHandler(logging.NullHandler())


def lint(qs_or_vs, exclude, app_import_names, format, extend_ignore, file_or_dir):
Expand Down Expand Up @@ -41,3 +51,68 @@ def get_lint_output(qs_or_vs, exclude, app_import_names, format, extend_ignore,
pass
capture.seek(0)
return capture.read()


@contextlib.contextmanager
def _temp_sys_argv(args):
old = sys.argv
sys.argv = list(args)
try:
yield
finally:
sys.argv = old


@contextlib.contextmanager
def _temp_merged_config(base_config: dict, override_config_file: pathlib.Path):
with _utils.temp_file.multi_access_tempfile(suffix=".toml") as temp:
target = copy.deepcopy(base_config)
merged = {"tool": {"bandit": target}}
override_config = toml.load(override_config_file)["tool"]["bandit"]
for key, value in override_config.items():
if key in target and isinstance(value, list):
_logger.debug("Merging %s: %s", key, value)
target[key].extend(value)
Comment on lines +73 to +75
Copy link
Contributor

Choose a reason for hiding this comment

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

Good, this allows projects to extend exclude_dirs.

elif key in target and isinstance(value, dict):
_logger.debug("Merging %s: %s", key, value)
target[key].update(value)
else:
_logger.debug("Overriding %s: %s", key, value)
target[key] = value
_logger.debug("Merged config: %s", merged)

with temp.open("w") as fout:
toml.dump(merged, fout)

yield temp


def _relative_to_cwd(path: str) -> pathlib.Path:
try:
return (pathlib.Path(path)).relative_to(pathlib.Path.cwd())
except ValueError:
return path


def lint_bandit(qs_or_vs, file_or_dir: typing.Tuple[pathlib.Path], pyproject_config: dict):
"""Run the bandit linter."""
with _temp_merged_config(
pyproject_config, _config_constants.BANDIT_CONFIG_FILE
) as merged_config:
args_list = list(
filter(
None,
[
"bandit",
qs_or_vs,
"-c",
str(merged_config),
"-r",
*[str(_relative_to_cwd(p)) for p in file_or_dir],
],
)
)
_logger.debug("Running bandit with args: %s", args_list)
with _temp_sys_argv(args_list):
# return
bandit.cli.main.main()
15 changes: 15 additions & 0 deletions ni_python_styleguide/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,18 @@ line-length = 100
[tool.isort]
profile = "black"
combine_as_imports = true

[tool.bandit]
# Tests are expected to use assert, and are not shipped to users.
assert_used = {skips = ["*/tests/**/test_*.py", "*/tests/test_*.py"]}
# These are common dirs that are not part of the codebase.
exclude_dirs = [
"__pycache__",
".coverage",
".git",
".mypy_cache",
".pytest_cache",
".venv",
".vs",
".vscode",
]
Loading
Loading