Files
MoviePilot/app/db/models/message.py
freeman f3ad81b39e style: apply safe ruff auto-fixes and exempt startup late imports
Run `ruff check app tests scripts --fix` with the configured rule set
(E4/E7/E9/F/I): import sorting (I001), f-strings without placeholders
(F541), lambda/variable/unused-import cleanups that ruff marks safe.
Nine files whose only ruff debt sits next to pre-existing pylint
import-resolution errors (lazy exports, sys.path script imports) or
whose "unused" imports are reached through attribute access by tests are
left untouched so the changed-file pylint gate and the benchmark tests
stay green; their diagnostics remain in the baseline.

app/startup/lifecycle/__init__.py and app/main.py import business
modules after cache composition, import-path and stdio setup on purpose
(the source comments document the contract), so declare E402
per-file-ignores for them instead of adding per-line noqa.

Ruff ratchet watermark drops from 498 to 100; sync the baseline fixture
and the optimization checklist figure.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-09 13:29:09 +08:00

158 lines
4.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from typing import Any, List, Optional
from sqlalchemy import JSON, Index, Integer, String, and_, delete, or_, select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import Mapped, Session, mapped_column
from app.db.base import Base, execute_dml, get_id_column
class Message(Base):
"""
消息表
"""
id = get_id_column()
# 消息渠道
channel: Mapped[Optional[str]] = mapped_column(String)
# 消息来源
source: Mapped[Optional[str]] = mapped_column(String)
# 消息类型
mtype: Mapped[Optional[str]] = mapped_column(String)
# 标题
title: Mapped[Optional[str]] = mapped_column(String)
# 文本内容
text: Mapped[Optional[str]] = mapped_column(String)
# 图片
image: Mapped[Optional[str]] = mapped_column(String)
# 链接
link: Mapped[Optional[str]] = mapped_column(String)
# 用户ID
userid: Mapped[Optional[str]] = mapped_column(String)
# 登记时间
reg_time: Mapped[Optional[str]] = mapped_column(String)
# 消息方向:0-接收息,1-发送消息
action: Mapped[Optional[int]] = mapped_column(Integer)
# 附件json
note: Mapped[Optional[Any]] = mapped_column(JSON)
__table_args__ = (
Index('ix_message_reg_time_id', 'reg_time', 'id'),
)
def create_and_to_dict(self, db: Session) -> dict:
"""
创建消息记录并返回写入后的字段字典。
"""
db.add(self)
db.flush()
return self.to_dict()
@classmethod
def list_by_page(
cls,
db: Session,
page: int = 1,
count: int = 30,
) -> List["Message"]:
"""在调用方同步会话中分页获取消息记录。"""
return list(db.execute(
select(cls)
.order_by(cls.reg_time.desc(), cls.id.desc())
.offset((page - 1) * count)
.limit(count)
).scalars().all())
@classmethod
def exists_by_source(
cls,
db: Session,
source: str,
) -> bool:
"""
判断指定来源标识的消息记录是否存在。
:param db: 数据库会话
:param source: 消息来源唯一标识
:return: 是否存在匹配记录
"""
return db.execute(
select(cls.id).where(cls.source == source).limit(1)
).scalars().first() is not None
@classmethod
async def async_list_by_page(
cls, db: AsyncSession, page: int = 1, count: int = 30
) -> List["Message"]:
"""
异步分页获取消息记录。
"""
result = await db.execute(
select(cls)
.order_by(cls.reg_time.desc(), cls.id.desc())
.offset((page - 1) * count)
.limit(count)
)
return list(result.scalars().all())
@classmethod
async def async_list_sent_by_page(
cls,
db: AsyncSession,
page: int = 1,
count: int = 30,
all_clear_before: Optional[str] = None,
system_clear_before: Optional[str] = None,
media_clear_before: Optional[str] = None,
) -> List["Message"]:
"""
分页获取系统发送的通知消息。
"""
statement = select(cls).where(cls.action == 1)
if all_clear_before:
statement = statement.where(cls.reg_time > all_clear_before)
if system_clear_before:
statement = statement.where(
or_(
and_(cls.image.isnot(None), cls.image != ""),
cls.reg_time > system_clear_before,
)
)
if media_clear_before:
statement = statement.where(
or_(
cls.image.is_(None),
cls.image == "",
cls.reg_time > media_clear_before,
)
)
result = await db.execute(
statement
.order_by(cls.reg_time.desc(), cls.id.desc())
.offset((page - 1) * count)
.limit(count)
)
return list(result.scalars().all())
@classmethod
def delete_before(
cls,
db: Session,
before_time: str,
limit: Optional[int] = 500,
) -> int:
"""
分批删除指定时间之前的消息记录。
"""
ids = db.execute(
select(cls.id)
.where(cls.reg_time < before_time)
.order_by(cls.id.asc())
.limit(limit)
).scalars().all()
if not ids:
return 0
return execute_dml(
db, delete(cls).where(cls.id.in_(ids)),
execution_options={"synchronize_session": False},
)