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,7 @@
from .file_finder import FileFinder
from .handlers import PythonFileHandler
__all__ = [
"FileFinder",
"PythonFileHandler"
]

View File

@@ -0,0 +1,167 @@
# type: ignore
import logging
import os
from pathlib import Path
from typing import Dict, List, Optional, Set, Tuple
from safety_schemas.models import Ecosystem, FileType
from .handlers import FileHandler, ECOSYSTEM_HANDLER_MAPPING
LOG = logging.getLogger(__name__)
def should_exclude(excludes: Set[Path], to_analyze: Path) -> bool:
"""
Determines whether a given path should be excluded based on the provided exclusion set.
Args:
excludes (Set[Path]): Set of paths to exclude.
to_analyze (Path): The path to analyze.
Returns:
bool: True if the path should be excluded, False otherwise.
"""
if not to_analyze.is_absolute():
to_analyze = to_analyze.resolve()
for exclude in excludes:
if not exclude.is_absolute():
exclude = exclude.resolve()
try:
if to_analyze == exclude or to_analyze.relative_to(exclude):
return True
except ValueError:
pass
return False
class FileFinder:
""" "
Defines a common interface to agree in what type of components Safety is trying to
find depending on the language type.
"""
def __init__(
self,
max_level: int,
ecosystems: List[Ecosystem],
target: Path,
live_status=None,
exclude: Optional[List[str]] = None,
include_files: Optional[Dict[FileType, List[Path]]] = None,
handlers: Optional[Set[FileHandler]] = None,
) -> None:
"""
Initializes the FileFinder with the specified parameters.
Args:
max_level (int): Maximum directory depth to search.
ecosystems (List[Ecosystem]): List of ecosystems to consider.
target (Path): Target directory to search.
console: Console object for output.
live_status: Live status object for updates.
exclude (Optional[List[str]]): List of patterns to exclude from the search.
include_files (Optional[Dict[FileType, List[Path]]]): Dictionary of files to include in the search.
handlers (Optional[Set[FileHandler]]): Set of file handlers.
"""
self.max_level = max_level
self.target = target
self.include_files = include_files
# If no handlers are provided, initialize them from the ecosystem mapping
if not handlers:
handlers = set(
ECOSYSTEM_HANDLER_MAPPING[ecosystem]() for ecosystem in ecosystems
)
self.handlers = handlers
self.file_count = 0
self.exclude_dirs: Set[Path] = set()
self.exclude_files: Set[Path] = set()
exclude = [] if not exclude else exclude
# Populate the exclude_dirs and exclude_files sets based on the provided patterns
for pattern in exclude:
for path in Path(target).glob(pattern):
if path.is_dir():
self.exclude_dirs.add(path)
else:
self.exclude_files.add(path)
self.live_status = live_status
def process_directory(
self, dir_path: str, max_deep: Optional[int] = None
) -> Tuple[str, Dict[str, Set[Path]]]:
"""
Processes the specified directory to find files matching the handlers' criteria.
Args:
dir_path (str): The directory path to process.
max_deep (Optional[int]): Maximum depth to search within the directory.
Returns:
Tuple[str, Dict[str, Set[Path]]]: The directory path and a dictionary of file types and their corresponding paths.
"""
files: Dict[str, Set[Path]] = {}
level: int = 0
initial_depth = len(Path(dir_path).parts) - 1
for root, dirs, filenames in os.walk(dir_path):
root_path = Path(root)
current_depth = len(root_path.parts) - initial_depth
# Filter directories based on exclusion criteria
dirs[:] = [
d
for d in dirs
if not should_exclude(
excludes=self.exclude_dirs, to_analyze=(root_path / Path(d))
)
]
if dirs:
LOG.info(f"Directories to inspect -> {', '.join(dirs)}")
LOG.info(f"Current -> {root}")
if self.live_status:
self.live_status.update(f":mag: Scanning {root}")
# Stop descending into directories if the maximum depth is reached
if max_deep is not None and current_depth > max_deep:
# Don't go deeper
del dirs[:]
# Filter filenames based on exclusion criteria
filenames[:] = [
f
for f in filenames
if not should_exclude(excludes=self.exclude_files, to_analyze=Path(f))
]
self.file_count += len(filenames)
for file_name in filenames:
for handler in self.handlers:
file_type = handler.can_handle(root, file_name, self.include_files)
if file_type:
inspectable_file: Path = Path(root, file_name)
if file_type.value not in files or not files[file_type.value]:
files[file_type.value] = set()
files[file_type.value].add(inspectable_file)
break
level += 1
return dir_path, files
def search(self) -> Tuple[str, Dict[str, Set[Path]]]:
"""
Initiates the search for files within the target directory.
Returns:
Tuple[str, Dict[str, Set[Path]]]: The target directory and a dictionary of file types and their corresponding paths.
"""
return self.process_directory(self.target, self.max_level)

View File

@@ -0,0 +1,122 @@
from abc import ABC, abstractmethod
import os
from pathlib import Path
from types import MappingProxyType
from typing import Dict, List, Optional, Optional, Tuple
from safety_schemas.models import Ecosystem, FileType
NOT_IMPLEMENTED = "You should implement this."
class FileHandler(ABC):
"""
Abstract base class for file handlers that define how to handle specific types of files
within an ecosystem.
"""
def __init__(self) -> None:
self.ecosystem: Optional[Ecosystem] = None
def can_handle(self, root: str, file_name: str, include_files: Dict[FileType, List[Path]]) -> Optional[FileType]:
"""
Determines if the handler can handle the given file based on its type and inclusion criteria.
Args:
root (str): The root directory of the file.
file_name (str): The name of the file.
include_files (Dict[FileType, List[Path]]): Dictionary of file types and their paths to include.
Returns:
Optional[FileType]: The type of the file if it can be handled, otherwise None.
"""
# Keeping it simple for now
if not self.ecosystem:
return None
for f_type in self.ecosystem.file_types:
if f_type in include_files:
current = Path(root, file_name).resolve()
paths = [p.resolve() if p.is_absolute() else (root / p).resolve() for p in include_files[f_type]]
if current in paths:
return f_type
# Let's compare by name only for now
# We can put heavier logic here, but for speed reasons,
# right now is very basic, we will improve this later.
# Custom matching per File Type
if file_name.lower().endswith(f_type.value.lower()):
return f_type
return None
@abstractmethod
def download_required_assets(self, session) -> Dict[str, str]:
"""
Abstract method to download required assets for handling files. Should be implemented
by subclasses.
Args:
session: The session object for making network requests.
Returns:
Dict[str, str]: A dictionary of downloaded assets.
"""
return NotImplementedError(NOT_IMPLEMENTED)
class PythonFileHandler(FileHandler):
"""
Handler for Python files within the Python ecosystem.
"""
# Example of a Python File Handler
def __init__(self) -> None:
super().__init__()
self.ecosystem = Ecosystem.PYTHON
def download_required_assets(self, session) -> None:
"""
Downloads the required assets for handling Python files, specifically the Safety database.
Args:
session: The session object for making network requests.
"""
from safety.safety import fetch_database
SAFETY_DB_DIR = os.getenv("SAFETY_DB_DIR")
db = False if SAFETY_DB_DIR is None else SAFETY_DB_DIR
# Fetch both the full and partial Safety databases
fetch_database(session=session, full=False, db=db, cached=True,
telemetry=True, ecosystem=Ecosystem.PYTHON,
from_cache=False)
fetch_database(session=session, full=True, db=db, cached=True,
telemetry=True, ecosystem=Ecosystem.PYTHON,
from_cache=False)
class SafetyProjectFileHandler(FileHandler):
"""
Handler for Safety project files within the Safety project ecosystem.
"""
# Example of a Python File Handler
def __init__(self) -> None:
super().__init__()
self.ecosystem = Ecosystem.SAFETY_PROJECT
def download_required_assets(self, session) -> None:
"""
No required assets to download for Safety project files.
"""
pass
# Mapping of ecosystems to their corresponding file handlers
ECOSYSTEM_HANDLER_MAPPING = MappingProxyType({
Ecosystem.PYTHON: PythonFileHandler,
Ecosystem.SAFETY_PROJECT: SafetyProjectFileHandler,
})