"""FileUploadConfig Model.""" from typing import TYPE_CHECKING, Optional from abacus_common_logic.connectors.database import db from abacus_common_logic.models import BaseSoftDeleteModel from sqlalchemy import JSON, BigInteger, Column, Enum, Integer, String from sqlalchemy.orm import Mapped from sqlalchemy.sql.elements import UnaryExpression from abacus_file_upload.constants import UPLOAD_TYPES if TYPE_CHECKING: from abacus_file_upload.models.file_upload import FileUpload class FileUploadConfig(BaseSoftDeleteModel): """FileUploadConfig Model.""" __tablename__ = 'file_upload_config' file_upload_config_id: Mapped[int] = Column( Integer, primary_key=True, autoincrement=True, comment='Primary key.' ) upload_type: Mapped[str] = Column( Enum(*UPLOAD_TYPES, name='upload_type', create_type=False), comment='Type identifier for the upload. Defines which upload configuration to use. Must be unique.', ) s3_key_template: Mapped[str] = Column( String(255), default='{file_key}.{ext}', server_default='{file_key}.{ext}', comment='S3 path/key template', ) allowed_file_types: Mapped[Optional[dict]] = Column( JSON, comment='Array of allowed file extensions, without leading dot and lowercase (e.g., ["csv", "pdf", "xlsx"]). NULL = allow all extensions.', ) max_file_size_bytes: Mapped[int] = Column( BigInteger, default=10485760, server_default='10485760', comment='Maximum file size in bytes. Defaults to 10MB, S3 maximum is 5TB.', ) multipart_threshold_bytes: Mapped[Optional[int]] = Column( BigInteger, default=104857600, server_default='104857600', comment='File size threshold for multipart upload. Defaults to 100MB, S3 maximum is 5GB.', ) min_multipart_chunk_size_bytes: Mapped[Optional[int]] = Column( BigInteger, default=10485760, server_default='10485760', comment='Minimum chunk size for multipart uploads. Defaults to 10MB, S3 range is [5MB, 5GB]. Calculated chunk size should be >= this value and stay within the S3 part limit (10,000).', ) description: Mapped[Optional[str]] = Column( String(255), comment='Description of what this upload config is for.' ) event_name: Mapped[Optional[str]] = Column( String(32), nullable=True, comment='An event that can be configured to trigger a lambda or an Airflow DAG', ) # Relationship to file_upload file_uploads: Mapped[list['FileUpload']] = db.relationship( 'FileUpload', back_populates='file_upload_config', lazy='dynamic' ) @classmethod def default_order(cls) -> UnaryExpression: """Override to customize default ordering.""" return cls.upload_type.asc() @classmethod def find_by_upload_type(cls, upload_type: str) -> Optional['FileUploadConfig']: """Find active config by upload_type.""" return cls.query.filter( cls.upload_type == upload_type, cls.deleted_at.is_(None) ).first()