diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index eae17c8..b7652ac 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,7 +14,7 @@ jobs: strategy: max-parallel: 5 matrix: - python-version: [3.7, 3.8, 3.9, '3.10', '3.11'] + python-version: ['3.7', '3.8', '3.9', '3.10', '3.11'] steps: - name: Checkout 🛎️ uses: actions/checkout@v3 @@ -26,10 +26,7 @@ jobs: run: | python -m pip install --upgrade pip pip install --upgrade . -r dev-requirements.txt - curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/install-poetry.py | python - name: Execute all pre-commit hooks on all files ☑ - if: matrix.python-version != '3.11' - # cf. https://github.com/PyCQA/pylint/issues/7972#issuecomment-1370602977 run: pre-commit run --all-files - name: Running tests ☑ run: pytest diff --git a/.gitignore b/.gitignore index e2f6218..d21b6b4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,7 @@ *.pyc /pre_commit_hooks_safety.egg-info /.cache/ -/.coverage +**/.coverage +/venv/ +/.idea/ +build/ diff --git a/README.md b/README.md index aa0c885..c194a9f 100644 --- a/README.md +++ b/README.md @@ -17,9 +17,9 @@ Note that **telemetry data will be sent with every Safety call**. These data are ``` ## How to Use Arguments -There are a few different arguements that this hook will accept. +There are a few different arguments that this hook will accept. -The first is the `files` arguement. Simply put which file your dependancies are listed in. +The first is the `files` argument. Simply put which file your dependencies are listed in. ```yaml - repo: https://github.com/Lucas-C/pre-commit-hooks-safety rev: v1.3.1 @@ -35,7 +35,15 @@ The next is the `--ignore` flag. This will ignore a comma seperated list of know - id: python-safety-dependencies-check args: ["--ignore=39153,39652"] ``` -You can also select between `--full-report` and `--short-report`. By default safety will use the `--full-report` flag so you can omit it for cleaner code. +The `--groups` flag will allow you to select additional dependency groups, other than the implicit main group. An example: +```yaml +- repo: https://github.com/Lucas-C/pre-commit-hooks-safety + rev: v1.3.1 + hooks: + - id: python-safety-dependencies-check + args: ["--groups=dev,test"] +``` +You can also select between `--full-report` and `--short-report`. By default, safety will use the `--full-report` flag so you can omit it for cleaner code. ```yaml - repo: https://github.com/Lucas-C/pre-commit-hooks-safety rev: v1.3.1 diff --git a/dev-requirements.txt b/dev-requirements.txt index 522f248..b372315 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -1,4 +1,5 @@ pre-commit pylint pytest -pytest-cov \ No newline at end of file +pytest-cov +poetry diff --git a/pre_commit_hooks/safety_check.py b/pre_commit_hooks/safety_check.py index 0f043b0..63e96b2 100644 --- a/pre_commit_hooks/safety_check.py +++ b/pre_commit_hooks/safety_check.py @@ -14,6 +14,11 @@ def build_parser(): parser = argparse.ArgumentParser() + + class AppendStringAction(argparse.Action): # pylint: disable=too-few-public-methods + def __call__(self, _, namespace, values, option_string=None): + setattr(namespace, self.dest, values.split(',')) + parser.add_argument( "--full-report", dest="report_arg", @@ -28,6 +33,7 @@ def build_parser(): const="--short-report", ) parser.add_argument("--ignore", "-i", action="append") + parser.add_argument("--groups", "-g", default=[], action=AppendStringAction) parser.add_argument("files", nargs="+") return parser @@ -43,7 +49,7 @@ def main(argv=None): # pylint: disable=inconsistent-return-statements with pyproject_toml_filepath.open() as pyproject_file: lines = [line.strip() for line in pyproject_file.readlines()] if any(line.startswith("[tool.poetry]") for line in lines): - with convert_poetry_to_requirements(pyproject_toml_filepath) as tmp_requirements: + with convert_poetry_to_requirements(pyproject_toml_filepath, groups=parsed_args.groups) as tmp_requirements: return call_safety_check([tmp_requirements.name], parsed_args.ignore, parsed_args.report_arg, args_rest) parser.error("Unsupported build tool: this pre-commit hook currently only handles pyproject.toml with Poetry" " ([tool.poetry] must be present in pyproject.toml)") @@ -70,7 +76,7 @@ def call_safety_check(requirements_file_paths, ignore_args, report_arg, args_res @contextmanager -def convert_poetry_to_requirements(pyproject_toml_filepath): # Sad function name :( +def convert_poetry_to_requirements(pyproject_toml_filepath, groups): # Sad function name :( poetry_cmd_path = which("poetry") if not poetry_cmd_path: # Using install-poetry.py installation $PATH: poetry_cmd_path = os.path.join(os.environ.get("HOME", ""), ".local", "bin", "poetry") @@ -82,7 +88,11 @@ def convert_poetry_to_requirements(pyproject_toml_filepath): # Sad function nam with ntf: # Placing ourselves in the pyproject.toml parent directory: with chdir(pyproject_toml_filepath.parent): - check_call([poetry_cmd_path, "export", "--with", "dev", "--format", "requirements.txt", "--output", ntf.name]) + cmd = [poetry_cmd_path, "export", "--format", "requirements.txt", "--output", ntf.name] + # Add groups to include in the export command + for group in groups: + cmd.extend(["--with", group]) + check_call(cmd) yield ntf finally: # Manually deleting temporary file: os.remove(ntf.name) diff --git a/tests/safety_test.py b/tests/safety_test.py index 7e49079..fed19dd 100644 --- a/tests/safety_test.py +++ b/tests/safety_test.py @@ -66,7 +66,6 @@ def test_poetry_requirements(tmpdir): # cf. https://github.com/Lucas-C/pre-comm colored-traceback==0.3.0 \ --hash=sha256:6da7ce2b1da869f6bb54c927b415b95727c4bb6d9a84c4615ea77d9872911b05 \ --hash=sha256:f76c21a4b4c72e9e09763d4d1b234afc469c88693152a763ad6786467ef9e79f -configobj==5.0.6 future==0.18.3 six==1.13.0 \ --hash=sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd \ @@ -90,7 +89,8 @@ def test_pyproject_toml_without_deps(tmpdir): name = 'Thing' version = '1.2.3' description = 'Dummy' -authors = ['Lucas Cimon']""") +authors = ['Lucas Cimon'] +""") assert safety([str(pyproject_file)]) == 0 def test_pyproject_toml_with_ko_deps(tmpdir): @@ -103,7 +103,8 @@ def test_pyproject_toml_with_ko_deps(tmpdir): [tool.poetry.dependencies] python = "^3.7" -jsonpickle = '1.4.1'""") +jsonpickle = '1.4.1' +""") assert safety([str(pyproject_file)]) == EXIT_CODE_VULNERABILITIES_FOUND def test_pyproject_toml_with_ko_dev_deps(tmpdir): @@ -117,6 +118,34 @@ def test_pyproject_toml_with_ko_dev_deps(tmpdir): [tool.poetry.dependencies] python = "^3.7" +# Poetry pre-1.2.x style [tool.poetry.dev-dependencies] jsonpickle = '1.4.1'""") - assert safety([str(pyproject_file)]) == EXIT_CODE_VULNERABILITIES_FOUND + assert safety([str(pyproject_file), "--groups=dev"]) == EXIT_CODE_VULNERABILITIES_FOUND + +@pytest.mark.parametrize( + "group_arg,status", + [ + ("--groups=dev", 0), + ("--groups=dev,test", EXIT_CODE_VULNERABILITIES_FOUND), + ("--groups=test", EXIT_CODE_VULNERABILITIES_FOUND), + ] +) +def test_pyproject_toml_with_groups(tmpdir, group_arg, status): + pyproject_file = tmpdir.join('pyproject.toml') + pyproject_file.write("""[tool.poetry] + name = 'Thing' + version = '1.2.3' + description = 'Dummy' + authors = ['Lucas Cimon'] + + [tool.poetry.dependencies] + python = "^3.7" + + # Poetry 1.2.0 style + [tool.poetry.group.dev.dependencies] + colored = "1.4.2" + + [tool.poetry.group.test.dependencies] + insecure-package = '0.1.0'""") + assert safety([str(pyproject_file), group_arg]) == status