Skip to content
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

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Copy link
Contributor

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 to setup_requires.


```
python setup.py safety --full-report
```

The command-line options can also be configured in *setup.cfg*:

```
[safety]
full-report yes
Copy link
Contributor

Choose a reason for hiding this comment

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

Just curious, don't we need an = here?

Copy link
Author

Choose a reason for hiding this comment

The 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

Expand Down
76 changes: 76 additions & 0 deletions safety/command.py
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
Copy link
Contributor

Choose a reason for hiding this comment

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

Shall we use safety.safety instead of safety.cli?

My understanding is that cli module serves as an interface for the console solely. Setup command would be another interface IMHO.


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)
5 changes: 4 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
]

test_requirements = [
# TODO: put package test requirements here
'mock==1.0.1',
rafaelpivato marked this conversation as resolved.
Show resolved Hide resolved
]

setup(
Expand All @@ -46,6 +46,9 @@
entry_points={
'console_scripts': [
'safety=safety.cli:cli'
],
'distutils.commands': [
'safety=safety.command:SafetyCommand'
]
},
include_package_data=True,
Expand Down
85 changes: 85 additions & 0 deletions tests/test_command.py
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=[])