from dataclasses import asdict, field from pathlib import Path from typing import List from pydantic.dataclasses import dataclass from typing_extensions import Self from .util import dict_dump from ..report.schemas.v3_0 import main as v3_0 from .base import FileType, SafetyBaseModel from .package import PythonDependency, PythonSpecification from .result import DependencyResultModel from .vulnerability import ClosestSecureVersion, RemediationModel, Vulnerability @dataclass class FileModel(SafetyBaseModel): location: Path file_type: FileType results: DependencyResultModel = field( default_factory=lambda: DependencyResultModel(dependencies=[]) ) def as_v30(self) -> v3_0.File: dependencies_output = [] for dep in self.results.dependencies: specs: List[v3_0.AnalyzedSpecification] = [] for specification in dep.specifications: rem = None if specification.remediation: closest = None if specification.remediation.closest_secure: closest_kwargs = asdict( specification.remediation.closest_secure ) closest = v3_0.ClosestSecureSpecification(**closest_kwargs) rem = v3_0.Remediation( vulnerabilities_found=specification.remediation.vulnerabilities_found, closest_secure=closest, recommended=specification.remediation.recommended, other_recommended=specification.remediation.other_recommended, ) analyzed = v3_0.AnalyzedSpecification( raw=specification.raw, vulnerabilities=v3_0.SpecificationVulnerabilities( known_vulnerabilities=[ v3_0.Vulnerability(**vuln.to_model_dict()) for vuln in specification.vulnerabilities ], remediation=rem, ), ) specs.append(analyzed) p = v3_0.Package(name=dep.name, specifications=specs) dependencies_output.append(p) return v3_0.File( location=str(self.location), type=self.file_type.value, categories=[self.file_type.ecosystem.value], results=v3_0.Results(dependencies=dependencies_output), ) @classmethod def from_v30(cls, obj: v3_0.SchemaModelV30) -> 'FileModel': if not isinstance(obj, v3_0.File): raise TypeError('Expected instance of v3_0.File') location = Path(obj.location) dependencies: List[PythonDependency] = [] for dep in obj.results.dependencies: specs: List[PythonSpecification] = [] for specification in dep.specifications: remediation_obj = None remed = specification.vulnerabilities.remediation if remed: closest_sec = None if remed.closest_secure: closest_sec = ClosestSecureVersion( **dict_dump(remed.closest_secure) ) remediation_obj = RemediationModel( vulnerabilities_found=remed.vulnerabilities_found, more_info_url="", recommended=remed.recommended, closest_secure=closest_sec, other_recommended=remed.other_recommended, ) vulns: List[Vulnerability] = [] py_spec = PythonSpecification(specification.raw, found=location) for vuln in specification.vulnerabilities.known_vulnerabilities: ignored = False ignored_expires = None ignored_reason = None ignored_code = None if vuln.ignored: ignored = True ignored_expires = vuln.ignored.expires ignored_reason = vuln.ignored.reason ignored_code = vuln.ignored.code vulns.append( Vulnerability( vulnerability_id=vuln.id, package_name=py_spec.name, ignored=ignored, ignored_reason=ignored_reason, ignored_expires=ignored_expires, ignored_code=ignored_code, vulnerable_spec=vuln.vulnerable_spec, ) ) py_spec.remediation = remediation_obj py_spec.vulnerabilities = vulns specs.append(py_spec) version = PythonDependency.find_version(specifications=specs) dependencies.append( PythonDependency( name=dep.name, version=version, specifications=specs, found=location ) ) results = DependencyResultModel(dependencies=dependencies) return FileModel( location=location, file_type=FileType(obj.type), results=results )