Skip to content

Commit

Permalink
safety/formatters
Browse files Browse the repository at this point in the history
  • Loading branch information
dylanpulver committed Jul 2, 2024
1 parent f349ba3 commit cacbfba
Show file tree
Hide file tree
Showing 8 changed files with 424 additions and 39 deletions.
43 changes: 38 additions & 5 deletions safety/formatters/bare.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,32 @@
from collections import namedtuple
from typing import List, Dict, Any, Optional, Tuple

from safety.formatter import FormatterAPI
from safety.util import get_basic_announcements


class BareReport(FormatterAPI):
"""Bare report, for command line tools"""
"""
Bare report, for command line tools.
"""

def render_vulnerabilities(self, announcements, vulnerabilities, remediations, full, packages, fixes=()):
parsed_announcements = []
def render_vulnerabilities(self, announcements: List[Dict[str, Any]], vulnerabilities: List[Any],
remediations: Any, full: bool, packages: List[Any], fixes: Tuple = ()) -> str:
"""
Renders vulnerabilities in a bare format.
Args:
announcements (List[Dict[str, Any]]): List of announcements.
vulnerabilities (List[Any]): List of vulnerabilities.
remediations (Any): Remediation data.
full (bool): Flag indicating full output.
packages (List[Any]): List of packages.
fixes (Tuple, optional): Tuple of fixes.
Returns:
str: Rendered vulnerabilities.
"""
parsed_announcements = []
Announcement = namedtuple("Announcement", ["name"])

for announcement in get_basic_announcements(announcements, include_local=False):
Expand All @@ -21,7 +38,17 @@ def render_vulnerabilities(self, announcements, vulnerabilities, remediations, f

return " ".join(announcements_to_render + affected_packages)

def render_licenses(self, announcements, packages_licenses):
def render_licenses(self, announcements: List[Dict[str, Any]], packages_licenses: List[Dict[str, Any]]) -> str:
"""
Renders licenses in a bare format.
Args:
announcements (List[Dict[str, Any]]): List of announcements.
packages_licenses (List[Dict[str, Any]]): List of package licenses.
Returns:
str: Rendered licenses.
"""
parsed_announcements = []

for announcement in get_basic_announcements(announcements):
Expand All @@ -34,5 +61,11 @@ def render_licenses(self, announcements, packages_licenses):
sorted_licenses = sorted(licenses)
return " ".join(announcements_to_render + sorted_licenses)

def render_announcements(self, announcements):
def render_announcements(self, announcements: List[Dict[str, Any]]) -> None:
"""
Renders announcements in a bare format.
Args:
announcements (List[Dict[str, Any]]): List of announcements.
"""
print('render_announcements bare')
42 changes: 37 additions & 5 deletions safety/formatters/html.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import logging
from typing import List, Dict, Tuple, Optional


from safety.formatter import FormatterAPI
from safety.formatters.json import build_json_report
Expand All @@ -9,18 +11,48 @@


class HTMLReport(FormatterAPI):
"""HTML report, for when the output is input for something else"""

def render_vulnerabilities(self, announcements, vulnerabilities, remediations, full, packages, fixes=()):
"""
HTML report formatter for when the output is input for something else.
"""

def render_vulnerabilities(self, announcements: List[Dict], vulnerabilities: List[Dict], remediations: Dict,
full: bool, packages: List[Dict], fixes: Tuple = ()) -> Optional[str]:
"""
Renders vulnerabilities in HTML format.
Args:
announcements (List[Dict]): List of announcements.
vulnerabilities (List[Dict]): List of vulnerabilities.
remediations (Dict): Remediation data.
full (bool): Flag indicating full output.
packages (List[Dict]): List of packages.
fixes (Tuple, optional): Tuple of fixes.
Returns:
str: Rendered HTML vulnerabilities report.
"""
LOG.debug(
f'HTML Output, Rendering {len(vulnerabilities)} vulnerabilities, {len(remediations)} package '
f'remediations with full_report: {full}')
report = build_json_report(announcements, vulnerabilities, remediations, packages)

return parse_html(kwargs={"json_data": report})

def render_licenses(self, announcements, licenses):
def render_licenses(self, announcements: List[Dict], licenses: List[Dict]) -> None:
"""
Renders licenses in HTML format.
Args:
announcements (List[Dict]): List of announcements.
licenses (List[Dict]): List of licenses.
"""
pass

def render_announcements(self, announcements):
def render_announcements(self, announcements: List[Dict]) -> None:
"""
Renders announcements in HTML format.
Args:
announcements (List[Dict]): List of announcements.
"""
pass
78 changes: 69 additions & 9 deletions safety/formatters/json.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import logging

import json as json_parser
from collections import defaultdict
from typing import Iterable

from requests.models import PreparedRequest
from typing import Iterable, List, Dict, Any

from safety.formatter import FormatterAPI
from safety.formatters.schemas import VulnerabilitySchemaV05
Expand All @@ -16,7 +13,20 @@
LOG = logging.getLogger(__name__)


def build_json_report(announcements, vulnerabilities, remediations, packages):
def build_json_report(announcements: List[Dict], vulnerabilities: List[Dict], remediations: Dict[str, Any],
packages: List[Any]) -> Dict[str, Any]:
"""
Build a JSON report for vulnerabilities, remediations, and packages.
Args:
announcements (List[Dict]): List of announcements.
vulnerabilities (List[Dict]): List of vulnerabilities.
remediations (Dict[str, Any]): Remediation data.
packages (List[Any]): List of packages.
Returns:
Dict[str, Any]: JSON report.
"""
vulns_ignored = [vuln.to_dict() for vuln in vulnerabilities if vuln.ignored]
vulns = [vuln.to_dict() for vuln in vulnerabilities if not vuln.ignored]

Expand Down Expand Up @@ -56,10 +66,31 @@ class JsonReport(FormatterAPI):
VERSIONS = ("0.5", "1.1")

def __init__(self, version="1.1", **kwargs):
"""
Initialize JsonReport with the specified version.
Args:
version (str): Report version.
"""
super().__init__(**kwargs)
self.version: str = version if version in self.VERSIONS else "1.1"

def render_vulnerabilities(self, announcements, vulnerabilities, remediations, full, packages, fixes=()):
def render_vulnerabilities(self, announcements: List[Dict], vulnerabilities: List[Dict],
remediations: Dict[str, Any], full: bool, packages: List[Any], fixes: Iterable = ()) -> str:
"""
Render vulnerabilities in JSON format.
Args:
announcements (List[Dict]): List of announcements.
vulnerabilities (List[Dict]): List of vulnerabilities.
remediations (Dict[str, Any]): Remediation data.
full (bool): Flag indicating full output.
packages (List[Any]): List of packages.
fixes (Iterable, optional): Iterable of fixes.
Returns:
str: Rendered JSON vulnerabilities report.
"""
if self.version == '0.5':
return json_parser.dumps(VulnerabilitySchemaV05().dump(obj=vulnerabilities, many=True), indent=4)

Expand All @@ -72,7 +103,17 @@ def render_vulnerabilities(self, announcements, vulnerabilities, remediations, f

return json_parser.dumps(template, indent=4, cls=SafetyEncoder)

def render_licenses(self, announcements, licenses):
def render_licenses(self, announcements: List[Dict], licenses: List[Dict]) -> str:
"""
Render licenses in JSON format.
Args:
announcements (List[Dict]): List of announcements.
licenses (List[Dict]): List of licenses.
Returns:
str: Rendered JSON licenses report.
"""
unique_license_types = set([lic['license'] for lic in licenses])
report = get_report_brief_info(as_dict=True, report_type=2, licenses_found=len(unique_license_types))

Expand All @@ -84,10 +125,29 @@ def render_licenses(self, announcements, licenses):

return json_parser.dumps(template, indent=4)

def render_announcements(self, announcements):
def render_announcements(self, announcements: List[Dict]) -> str:
"""
Render announcements in JSON format.
Args:
announcements (List[Dict]): List of announcements.
Returns:
str: Rendered JSON announcements.
"""
return json_parser.dumps({"announcements": get_basic_announcements(announcements)}, indent=4)

def __render_fixes(self, scan_template, fixes: Iterable):
def __render_fixes(self, scan_template: Dict[str, Any], fixes: Iterable) -> Dict[str, Any]:
"""
Render fixes and update the scan template with remediations information.
Args:
scan_template (Dict[str, Any]): Initial scan template.
fixes (Iterable): Iterable of fixes.
Returns:
Dict[str, Any]: Updated scan template with remediations.
"""

applied = defaultdict(lambda: defaultdict(lambda: defaultdict(dict)))
skipped = defaultdict(lambda: defaultdict(lambda: defaultdict(dict)))
Expand Down
38 changes: 31 additions & 7 deletions safety/formatters/schemas/common.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Dict, Generic, TypeVar
from typing import Dict, Generic, TypeVar, Generator

from pydantic import BaseModel as PydanticBaseModel
from pydantic import Extra
Expand All @@ -9,10 +9,13 @@


class BaseModel(PydanticBaseModel):
"""
Base model that extends Pydantic's BaseModel with additional configurations.
"""
class Config:
arbitrary_types_allowed = True
max_anystr_length = 50
validate_assignment = True
arbitrary_types_allowed: bool = True
max_anystr_length: int = 50
validate_assignment: bool = True
extra = Extra.forbid


Expand All @@ -21,15 +24,36 @@ class Config:


class ConstrainedDict(Generic[KeyType, ValueType]):
def __init__(self, v: Dict[KeyType, ValueType]):
"""
A constrained dictionary that validates its length based on a specified limit.
"""
def __init__(self, v: Dict[KeyType, ValueType]) -> None:
"""
Initialize the ConstrainedDict.
Args:
v (Dict[KeyType, ValueType]): The dictionary to constrain.
"""
super().__init__()

@classmethod
def __get_validators__(cls):
def __get_validators__(cls) -> Generator:
yield cls.dict_length_validator

@classmethod
def dict_length_validator(cls, v):
def dict_length_validator(cls, v: Dict[KeyType, ValueType]) -> Dict[KeyType, ValueType]:
"""
Validate the length of the dictionary.
Args:
v (Dict[KeyType, ValueType]): The dictionary to validate.
Returns:
Dict[KeyType, ValueType]: The validated dictionary.
Raises:
DictMaxLengthError: If the dictionary exceeds the allowed length.
"""
v = dict_validator(v)
if len(v) > SCHEMA_DICT_ITEMS_COUNT_LIMIT:
raise DictMaxLengthError(limit_value=SCHEMA_DICT_ITEMS_COUNT_LIMIT)
Expand Down
Loading

0 comments on commit cacbfba

Please sign in to comment.