"""Base class for file uploaders.""" from abc import ABC, abstractmethod import asyncio from dataclasses import dataclass from datetime import datetime from typing import Any from crewai_files.core.types import FileInput @dataclass class UploadResult: """Result of a file upload operation. Attributes: file_id: Provider-specific file identifier. file_uri: Optional URI for accessing the file. content_type: MIME type of the uploaded file. expires_at: When the upload expires (if applicable). provider: Name of the provider. """ file_id: str provider: str content_type: str file_uri: str | None = None expires_at: datetime | None = None class FileUploader(ABC): """Abstract base class for provider file uploaders. Implementations handle uploading files to provider-specific File APIs. """ @property @abstractmethod def provider_name(self) -> str: """Return the provider name.""" @abstractmethod def upload(self, file: FileInput, purpose: str | None = None) -> UploadResult: """Upload a file to the provider. Args: file: The file to upload. purpose: Optional purpose/description for the upload. Returns: UploadResult with the file identifier and metadata. Raises: Exception: If upload fails. """ async def aupload( self, file: FileInput, purpose: str | None = None ) -> UploadResult: """Async upload a file to the provider. Default implementation runs sync upload in executor. Override in subclasses for native async support. Args: file: The file to upload. purpose: Optional purpose/description for the upload. Returns: UploadResult with the file identifier and metadata. """ loop = asyncio.get_running_loop() return await loop.run_in_executor(None, self.upload, file, purpose) @abstractmethod def delete(self, file_id: str) -> bool: """Delete an uploaded file. Args: file_id: The file identifier to delete. Returns: True if deletion was successful, False otherwise. """ async def adelete(self, file_id: str) -> bool: """Async delete an uploaded file. Default implementation runs sync delete in executor. Override in subclasses for native async support. Args: file_id: The file identifier to delete. Returns: True if deletion was successful, False otherwise. """ loop = asyncio.get_running_loop() return await loop.run_in_executor(None, self.delete, file_id) def get_file_info(self, file_id: str) -> dict[str, Any] | None: """Get information about an uploaded file. Args: file_id: The file identifier. Returns: Dictionary with file information, or None if not found. """ return None def list_files(self) -> list[dict[str, Any]]: """List all uploaded files. Returns: List of dictionaries with file information. """ return []