This commit is contained in:
Iliyan Angelov
2025-12-01 06:50:10 +02:00
parent 91f51bc6fe
commit 62c1fe5951
4682 changed files with 544807 additions and 31208 deletions

View File

@@ -0,0 +1,137 @@
import abc
from dataclasses import field
from pathlib import Path
from typing import List, Optional
from pydantic.dataclasses import dataclass
from .specification import PythonSpecification
NOT_IMPLEMENTED_ERROR_MSG = (
"Needs implementation for the specific " "specification type."
)
@dataclass
class Dependency:
name: str
version: Optional[str]
specifications: List[PythonSpecification]
found: Optional[Path] = None
absolute_path: Optional[Path] = None
insecure_versions: List[str] = field(default_factory=lambda: [])
secure_versions: List[str] = field(default_factory=lambda: [])
latest_version_without_known_vulnerabilities: Optional[str] = None
latest_version: Optional[str] = None
more_info_url: Optional[str] = None
def has_unpinned_specification(self):
for specification in self.specifications:
if not specification.is_pinned():
return True
return False
def get_unpinned_specificaitons(self):
return filter(
lambda specification: not specification.is_pinned(), self.specifications
)
@abc.abstractmethod
def filter_by_supported_versions(self, versions: List[str]) -> List[str]:
raise NotImplementedError()
@abc.abstractmethod
def get_versions(self, db_full):
raise NotImplementedError()
@abc.abstractmethod
def refresh_from(self, db_full):
raise NotImplementedError()
def to_dict(self, **kwargs):
if kwargs.get("short_version", False):
return {
"name": self.name,
"version": self.version,
"requirements": self.specifications,
}
return {
"name": self.name,
"version": self.version,
"requirements": self.specifications,
"found": None,
"insecure_versions": self.insecure_versions,
"secure_versions": self.secure_versions,
"latest_version_without_known_vulnerabilities": self.latest_version_without_known_vulnerabilities, # noqa: E501
"latest_version": self.latest_version,
"more_info_url": self.more_info_url,
}
def update(self, new):
for key, value in new.items():
if hasattr(self, key):
setattr(self, key, value)
@dataclass
class PythonDependency(Dependency):
def filter_by_supported_versions(self, versions: List[str]) -> List[str]:
from packaging.version import parse as parse_version
allowed = []
for version in versions:
try:
parse_version(version)
allowed.append(version)
except Exception:
pass
return allowed
def get_versions(self, db_full):
pkg_meta = db_full.get("meta", {}).get("packages", {}).get(self.name, {})
versions = self.filter_by_supported_versions(
pkg_meta.get("insecure_versions", []) + pkg_meta.get("secure_versions", [])
)
return set(versions)
def refresh_from(self, db_full):
from packaging.utils import canonicalize_name
base_domain = db_full.get("meta", {}).get("base_domain")
pkg_meta = (
db_full.get("meta", {})
.get("packages", {})
.get(canonicalize_name(self.name), {})
)
kwargs = {
"insecure_versions": self.filter_by_supported_versions(
pkg_meta.get("insecure_versions", [])
),
"secure_versions": self.filter_by_supported_versions(
pkg_meta.get("secure_versions", [])
),
"latest_version_without_known_vulnerabilities": pkg_meta.get(
"latest_secure_version", None
),
"latest_version": pkg_meta.get("latest_version", None),
"more_info_url": f"{base_domain}{pkg_meta.get('more_info_path', '')}",
}
self.update(kwargs)
@staticmethod
def find_version(specifications: List[PythonSpecification]) -> Optional[str]:
ver = None
if len(specifications) != 1:
return ver
specification = specifications[0]
if specification.is_pinned():
ver = next(iter(specification.specifier)).version
return ver