Files
Hotel-Booking/Backend/venv/lib/python3.12/site-packages/safety_schemas/models/scan.py
Iliyan Angelov 62c1fe5951 updates
2025-12-01 06:50:10 +02:00

106 lines
3.7 KiB
Python

import importlib
from dataclasses import field
from pathlib import Path
from typing import List, Union
from pydantic import ValidationError
from pydantic.dataclasses import dataclass
from typing_extensions import Self
from ..report.schemas.v3_0 import main as v3_0
from .base import ReportSchemaVersion, SafetyBaseModel, ScanType
from .file import FileModel
from .metadata import MetadataModel
from .project import ProjectModel
from .telemetry import TelemetryModel
from pydantic.version import VERSION as PYDANTIC_VERSION
def version_validator(func):
if PYDANTIC_VERSION.startswith("1."):
from pydantic import validator
return validator('verison', pre=True, always=True)
else:
from pydantic import field_validator
return field_validator('verison', mode='before')
@dataclass
class ReportModel(SafetyBaseModel):
"""
Used as an entrypoint to keep backwards compatibility with old formats.
Use this model if you want to generate a standard JSON report.
"""
DEFAULT_SCHEMA_VERSION = ReportSchemaVersion.v3_0
telemetry: TelemetryModel
metadata: MetadataModel
files: List[FileModel]
projects: List[ProjectModel] = field(default_factory=lambda: [])
version: ReportSchemaVersion = DEFAULT_SCHEMA_VERSION
@version_validator
def validate_version(cls, version: ReportSchemaVersion) -> ReportSchemaVersion:
versions = list(ReportSchemaVersion)
if version not in (versions):
raise ValueError(f"Invalid version, allowed versions are {versions}")
return version
def as_v30(self) -> v3_0.Report:
full = self.metadata.scan_type is ScanType.scan
projects_data = [p.as_v30(full=full) for p in self.projects]
if not all(isinstance(p, type(projects_data[0])) for p in projects_data):
raise ValueError("All objects in projects_data must be of the same type")
results = v3_0.ScanResults(
files=[f.as_v30() for f in self.files],
projects=projects_data, # type: ignore
)
meta_data = self.metadata.as_v30()
if not isinstance(meta_data, v3_0.Meta) or not isinstance(results, v3_0.ScanResults):
raise TypeError("Expected instance of v3_0.Meta and v3_0.ScanResults")
report = v3_0.Report(meta=meta_data, scan_results=results)
return report
@classmethod
def from_v30(cls, obj: v3_0.SchemaModelV30) -> 'ReportModel':
if not isinstance(obj, v3_0.Report):
raise TypeError('Expected instance of v3_0.Report')
return ReportModel(
version=ReportSchemaVersion(obj.meta.schema_version),
telemetry=TelemetryModel.from_v30(obj.meta.telemetry),
metadata=MetadataModel.from_v30(obj.meta),
projects=[ProjectModel.from_v30(p) for p in obj.scan_results.projects],
files=[FileModel.from_v30(f) for f in obj.scan_results.files],
)
@classmethod
def parse_report(
cls, raw_report: Union[str, Path], schema: ReportSchemaVersion
) -> Union['ReportModel', ValidationError]:
parse = "parse_raw"
if isinstance(raw_report, Path):
raw_report = raw_report.expanduser().resolve()
parse = "parse_file"
target_schema = schema.value.replace(".", "_")
module_name = "safety_schemas.report.schemas." f"v{target_schema}.main"
module = importlib.import_module(module_name)
report_model = module.Report
# This will raise a validation error if the content is wrong
validated_report = getattr(report_model, parse)(raw_report)
# TODO: Select the from from the version passed
return ReportModel.from_v30(obj=validated_report)