-
Notifications
You must be signed in to change notification settings - Fork 142
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
Add setuptools integration. #218
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -147,6 +147,22 @@ and displays a status on GitHub. | |
|
||
![Safety CI](https://github.com/pyupio/safety/raw/master/safety_ci.png) | ||
|
||
## Using Safety from setup.py | ||
|
||
Safety includes a [distutils extension] that runs the check command. | ||
|
||
``` | ||
python setup.py safety --full-report | ||
``` | ||
|
||
The command-line options can also be configured in *setup.cfg*: | ||
|
||
``` | ||
[safety] | ||
full-report yes | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just curious, don't we need an There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes ... yes you do 😞 |
||
``` | ||
|
||
[distutils extension]: https://setuptools.readthedocs.io/en/latest/setuptools.html#extending-and-reusing-setuptools | ||
|
||
# Using Safety in production | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
# -*- coding: utf-8 -*- | ||
from __future__ import absolute_import | ||
from distutils import cmd | ||
import os | ||
|
||
from safety import cli | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shall we use My understanding is that |
||
|
||
class SafetyCommand(cmd.Command): | ||
description = 'Check this project using the safety utility' | ||
user_options = [ | ||
("bare", None, "Output vulnerable packages only."), | ||
("cache", None, "Cache requests to vulnerability database locally."), | ||
("db=", None, "Path to a local vulnerability database."), | ||
("files=", None, "Read input from one or more requirement files."), | ||
("full-report", None, "Generate a full report."), | ||
("ignore=", None, "Ignore one or more vulnerabilities by ID."), | ||
("json", None, "Output vulnerabilities in JSON format."), | ||
("key=", None, "API key for pyup.io's vulnerability database."), | ||
("output=", None, "Path to where output file will be placed."), | ||
("proxy-host=", None, "Proxy host IP or DNS."), | ||
("proxy-port=", None, "Proxy port number."), | ||
("proxy-protocol=", None, "Proxy protocol (https or http)."), | ||
("stdin", None, "Read input from stdin."), | ||
] | ||
boolean_options = ["bare", "cache", "full-report", "json", "stdin"] | ||
|
||
def initialize_options(self): | ||
self.bare = False | ||
self.cache = False | ||
self.db = "" | ||
self.files = [] | ||
self.full_report = False | ||
self.ignore = [] | ||
self.json = False | ||
self.key = os.environ.get("SAFETY_API_KEY", "") | ||
self.output = "" | ||
self.proxy_host = None | ||
self.proxy_port = None | ||
self.proxy_protocol = None | ||
self.stdin = False | ||
|
||
def finalize_options(self): # pragma: no-cover | ||
if len(self.files) > 0: | ||
self.ensure_string_list("files") | ||
if len(self.ignore) > 0: | ||
self.ensure_string_list("ignore") | ||
|
||
def run(self): | ||
args = [] | ||
if self.bare: | ||
args.append("--bare") | ||
if self.cache: | ||
args.append("--cache") | ||
if self.db: | ||
args.extend(["--db", self.db]) | ||
for file_name in self.files: | ||
args.extend(["--files", file_name]) | ||
if self.full_report: | ||
args.append("--full-report") | ||
for ignore_id in self.ignore: | ||
args.extend(["--ignore", ignore_id]) | ||
if self.json: | ||
args.append("--json") | ||
if self.key: | ||
args.extend(["--key", self.key]) | ||
if self.output: | ||
args.extend(["--output", self.output]) | ||
if self.proxy_host: | ||
args.extend(["--proxy-host", self.proxy_host]) | ||
if self.proxy_protocol: | ||
args.extend(["--proxy-protocol", self.proxy_protocol]) | ||
if self.proxy_port: | ||
args.extend(["--proxy-port", self.proxy_port]) | ||
if self.stdin: | ||
args.append("--stdin") | ||
cli.check(args=args) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
from __future__ import absolute_import | ||
from distutils import dist | ||
import unittest | ||
|
||
from safety import command | ||
import mock | ||
|
||
|
||
class CommandFinalizeOptionsTests(unittest.TestCase): | ||
def setUp(self): | ||
super(CommandFinalizeOptionsTests, self).setUp() | ||
self.command = command.SafetyCommand(dist.Distribution()) | ||
self.command.initialize_options() | ||
|
||
def test_files_option_parsing(self): | ||
self.command.files = "one two three" | ||
self.command.finalize_options() | ||
self.assertEqual(self.command.files, ["one", "two", "three"]) | ||
|
||
def test_ignore_option_parsing(self): | ||
self.command.ignore = "ID1 ID2 ID3" | ||
self.command.finalize_options() | ||
self.assertEqual(self.command.ignore, ["ID1", "ID2", "ID3"]) | ||
|
||
|
||
class CommandRunTests(unittest.TestCase): | ||
@staticmethod | ||
def run_command_with_options(*option_tuples): | ||
with mock.patch("safety.command.cli.check") as check_function: | ||
cmd = command.SafetyCommand(dist.Distribution()) | ||
cmd.initialize_options() | ||
for option_name, value in option_tuples: | ||
opt = option_name.replace("-", "_") | ||
setattr(cmd, opt, value) | ||
cmd.finalize_options() | ||
cmd.run() | ||
return check_function | ||
|
||
def test_that_default_command_is_empty(self): | ||
check_function = self.run_command_with_options() | ||
check_function.assert_called_once_with(args=[]) | ||
|
||
def test_boolean_options(self): | ||
for opt in command.SafetyCommand.boolean_options: | ||
check_function = self.run_command_with_options((opt, True)) | ||
check_function.assert_called_once_with(args=["--"+opt]) | ||
|
||
def test_simple_options(self): | ||
for opt in ("db", "key", "output"): | ||
check_function = self.run_command_with_options((opt, "value")) | ||
check_function.assert_called_once_with(args=["--"+opt, "value"]) | ||
|
||
def test_list_options(self): | ||
for opt in ("files", "ignore"): | ||
check_function = self.run_command_with_options((opt, "one two")) | ||
check_function.assert_called_once_with(args=[ | ||
"--"+opt, "one", "--"+opt, "two"]) | ||
|
||
def test_proxy_options(self): | ||
check_function = self.run_command_with_options(("proxy-host", "host")) | ||
check_function.assert_called_once_with(args=["--proxy-host", "host"]) | ||
|
||
check_function = self.run_command_with_options( | ||
("proxy-host", "host"), ("proxy-port", "1234")) | ||
check_function.assert_called_once_with(args=[ | ||
"--proxy-host", "host", "--proxy-port", "1234"]) | ||
|
||
check_function = self.run_command_with_options( | ||
("proxy-host", "host"), ("proxy-protocol", "https")) | ||
check_function.assert_called_once_with(args=[ | ||
"--proxy-host", "host", "--proxy-protocol", "https"]) | ||
|
||
check_function = self.run_command_with_options( | ||
("proxy-host", "host"), ("proxy-port", "1234"), | ||
("proxy-protocol", "https")) | ||
check_function.assert_called_once_with(args=[ | ||
"--proxy-host", "host", "--proxy-protocol", "https", | ||
"--proxy-port", "1234"]) | ||
|
||
check_function = self.run_command_with_options(("proxy-port", "1234")) | ||
check_function.assert_called_once_with(args=[]) | ||
|
||
check_function = self.run_command_with_options( | ||
("proxy-protocol", "http")) | ||
check_function.assert_called_once_with(args=[]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we could explain briefly how the users should configure their
setup.py
file, including adding safety tosetup_requires
.