from datetime import datetime from sqlalchemy import BigInteger, Boolean, DateTime, Enum, ForeignKey, Integer, String, Text, func from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship from .enums import ( BODY_TYPE_ENUM_VALUES, COUNTRY_ENUM_VALUES, CURRENCY_ENUM_VALUES, DRIVE_ENUM_VALUES, GEARBOX_ENUM_VALUES, ORIGIN_ENUM_VALUES, SELLING_TYPE_ENUM_VALUES, STEERING_WHEEL_ENUM_VALUES, ) class Base(DeclarativeBase): # База ORM. pass class Car(Base): # Автомобиль. __tablename__ = "cars" id: Mapped[int] = mapped_column(BigInteger().with_variant(Integer, "sqlite"), primary_key=True, autoincrement=True) parser_id: Mapped[str] = mapped_column(String(50), nullable=False, unique=True) brand: Mapped[str] = mapped_column(String(50), nullable=False) model: Mapped[str] = mapped_column(String(50), nullable=False) year: Mapped[int | None] = mapped_column(Integer, nullable=True) price: Mapped[int | None] = mapped_column(BigInteger, nullable=True) currency: Mapped[str] = mapped_column(Enum(*CURRENCY_ENUM_VALUES, name="currencyenum", native_enum=True, create_constraint=False), nullable=False, default="USD") mileage: Mapped[int] = mapped_column(Integer, nullable=False, default=0) country: Mapped[str] = mapped_column(Enum(*COUNTRY_ENUM_VALUES, name="countryenum", native_enum=True, create_constraint=False), nullable=False, default="NA") is_sold: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False) color: Mapped[str] = mapped_column(String(), nullable=False, default="other") drive: Mapped[str | None] = mapped_column(Enum(*DRIVE_ENUM_VALUES, name="driveenum", native_enum=True, create_constraint=False), nullable=True) gearbox: Mapped[str | None] = mapped_column(Enum(*GEARBOX_ENUM_VALUES, name="gearboxenum", native_enum=True, create_constraint=False), nullable=True) steering_wheel: Mapped[str | None] = mapped_column(Enum(*STEERING_WHEEL_ENUM_VALUES, name="steeringwheelenum", native_enum=True, create_constraint=False), nullable=True) body_type: Mapped[str] = mapped_column(Enum(*BODY_TYPE_ENUM_VALUES, name="bodytypeenum", native_enum=True, create_constraint=False), nullable=False, default="OTHER") engine_volume: Mapped[int | None] = mapped_column(Integer, nullable=True) selling_type: Mapped[str] = mapped_column(Enum(*SELLING_TYPE_ENUM_VALUES, name="sellingtypeenum", native_enum=True, create_constraint=False), nullable=False, default="NA") one_owner: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False) new_car: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False) is_hidden: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False) origin: Mapped[str] = mapped_column(Enum(*ORIGIN_ENUM_VALUES, name="originenum", native_enum=True, create_constraint=False), nullable=False, default="NA") origin_url: Mapped[str] = mapped_column(String(), nullable=False, index=True) origin_id: Mapped[str] = mapped_column(String(), nullable=False, unique=True, index=True) is_damaged: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False) evaluation: Mapped[str | None] = mapped_column(String(), nullable=True) non_smoking: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True) rental: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False) repair_history: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False) slug: Mapped[str] = mapped_column(String(), nullable=False) last_seen_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, default=func.now()) content_hash: Mapped[str] = mapped_column(String(64), nullable=False, default="", index=True) raw_attributes: Mapped[str | None] = mapped_column(Text, nullable=True) images: Mapped[list["Image"]] = relationship("Image", back_populates="car", cascade="all, delete-orphan") class Image(Base): # Картинки автомобиля. __tablename__ = "images" id: Mapped[int] = mapped_column(BigInteger().with_variant(Integer, "sqlite"), primary_key=True, autoincrement=True) fullres_image: Mapped[str] = mapped_column(String(), nullable=False) preview_image: Mapped[str] = mapped_column(String(), nullable=False) order_index: Mapped[int] = mapped_column(Integer, nullable=False) car_id: Mapped[int] = mapped_column(BigInteger, ForeignKey("cars.id", ondelete="CASCADE"), nullable=False) car: Mapped[Car] = relationship("Car", back_populates="images") class SyncRun(Base): # Статистика синхронизаций. __tablename__ = "sync_runs" id: Mapped[int] = mapped_column(BigInteger().with_variant(Integer, "sqlite"), primary_key=True, autoincrement=True) started_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, default=func.now()) finished_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) status: Mapped[str] = mapped_column(Text, nullable=False) lane: Mapped[str] = mapped_column(Text, nullable=False) ids_fetched: Mapped[int] = mapped_column(Integer, nullable=False, default=0) cars_upserted: Mapped[int] = mapped_column(Integer, nullable=False, default=0) cars_failed: Mapped[int] = mapped_column(Integer, nullable=False, default=0) images_upserted: Mapped[int] = mapped_column(Integer, nullable=False, default=0) error_summary: Mapped[str | None] = mapped_column(Text, nullable=True) class ScrapeTask(Base): # Задачи Celery. __tablename__ = "scrape_tasks" id: Mapped[int] = mapped_column(BigInteger().with_variant(Integer, "sqlite"), primary_key=True, autoincrement=True) celery_task_id: Mapped[str] = mapped_column(String(255), nullable=False, unique=True, index=True) task_type: Mapped[str] = mapped_column(String(50), nullable=False) # sync_vehicle/sync_listing status: Mapped[str] = mapped_column(String(20), nullable=False, default="pending") # pending/running/success/failed vehicle_url: Mapped[str | None] = mapped_column(Text, nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, default=func.now()) started_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) finished_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) result_summary: Mapped[str | None] = mapped_column(Text, nullable=True) error_message: Mapped[str | None] = mapped_column(Text, nullable=True)