""" Asset handling utilities for packaging operations. Provides utilities for discovering, processing, and managing assets within packages. """ import hashlib import mimetypes from pathlib import Path from typing import List, Set, Dict, Optional from .metadata import AssetMetadata from .errors import AssetError class AssetUtils: """Utilities for asset handling in packages.""" @staticmethod def discover_assets(source_path: Path, asset_extensions: Optional[Set[str]] = None) -> List[Path]: """ Discover assets in a source directory. Args: source_path: Path to search for assets asset_extensions: Set of file extensions to consider as assets If None, uses default set Returns: List of asset file paths """ if asset_extensions is None: asset_extensions = { '.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp', # Images '.pdf', '.doc', '.docx', '.txt', # Documents '.mp3', '.wav', '.ogg', # Audio '.mp4', '.webm', '.avi', # Video '.css', '.js', # Web assets '.json', '.yaml', '.yml' # Data files } assets = [] if source_path.is_file(): # Single file source if source_path.suffix.lower() in asset_extensions: assets.append(source_path) else: # Directory source for file_path in source_path.rglob('*'): if (file_path.is_file() and file_path.suffix.lower() in asset_extensions): assets.append(file_path) return assets @staticmethod def create_asset_metadata(file_path: Path, package_path: str, original_path: str = None) -> AssetMetadata: """ Create metadata for an asset file. Args: file_path: Path to the asset file package_path: Path within the package original_path: Original path before processing Returns: AssetMetadata object """ if not file_path.exists(): raise AssetError(f"Asset file not found: {file_path}") # Calculate file size size = file_path.stat().st_size # Calculate checksum checksum = AssetUtils.calculate_checksum(file_path) # Determine MIME type mime_type, _ = mimetypes.guess_type(str(file_path)) return AssetMetadata( path=package_path, original_path=original_path or str(file_path), size=size, checksum=checksum, mime_type=mime_type ) @staticmethod def calculate_checksum(file_path: Path) -> str: """ Calculate SHA-256 checksum of a file. Args: file_path: Path to the file Returns: Hexadecimal checksum string """ sha256_hash = hashlib.sha256() try: with open(file_path, "rb") as f: for chunk in iter(lambda: f.read(4096), b""): sha256_hash.update(chunk) except IOError as e: raise AssetError(f"Failed to read file for checksum: {e}") return sha256_hash.hexdigest() @staticmethod def validate_asset_integrity(file_path: Path, expected_checksum: str) -> bool: """ Validate asset integrity using checksum. Args: file_path: Path to the asset file expected_checksum: Expected checksum Returns: True if checksums match, False otherwise """ try: actual_checksum = AssetUtils.calculate_checksum(file_path) return actual_checksum == expected_checksum except AssetError: return False # Standalone utility functions for convenience def discover_assets(source_path: Path, asset_extensions: Optional[Set[str]] = None) -> List[Path]: """ Standalone wrapper for AssetUtils.discover_assets. Args: source_path: Path to search for assets asset_extensions: Set of file extensions to consider as assets Returns: List of asset file paths """ return AssetUtils.discover_assets(source_path, asset_extensions) def resolve_asset_path(base_path: Path, asset_path: str) -> Path: """ Resolve asset path relative to base path. Args: base_path: Base directory path asset_path: Asset path (relative or absolute) Returns: Resolved asset path """ if Path(asset_path).is_absolute(): return Path(asset_path) return base_path / asset_path def detect_mime_type(file_path: Path) -> Optional[str]: """ Detect MIME type of a file. Args: file_path: Path to the file Returns: MIME type string or None """ mime_type, _ = mimetypes.guess_type(str(file_path)) return mime_type