提取一些公共方法

pull/1496/head
UnCLAS-Prommer 2026-02-15 00:18:16 +08:00
parent 6db889580d
commit 6dc33e9e86
No known key found for this signature in database
3 changed files with 96 additions and 87 deletions

View File

@ -54,7 +54,7 @@ def _install_stub_modules(monkeypatch):
file_name: str = ""
description: str | None = None
emotion: list[str] | None = None
emoji_hash: str | None = None
file_hash: str | None = None
is_deleted: bool = False
query_count: int = 0
register_time: object | None = None
@ -831,7 +831,7 @@ def test_delete_emoji_file_missing_and_db_record_missing(monkeypatch):
emoji = emoji_manager_new.MaiEmoji()
emoji.full_path = _DummyPath()
emoji.file_name = "missing.png"
emoji.emoji_hash = "hash-missing"
emoji.file_hash = "hash-missing"
result = manager.delete_emoji(emoji)
@ -859,7 +859,7 @@ def test_delete_emoji_file_delete_error(monkeypatch):
emoji = emoji_manager_new.MaiEmoji()
emoji.full_path = _DummyPath()
emoji.file_name = "boom.png"
emoji.emoji_hash = "hash-boom"
emoji.file_hash = "hash-boom"
result = manager.delete_emoji(emoji)
@ -915,7 +915,7 @@ def test_delete_emoji_db_error_file_still_exists(monkeypatch):
emoji = emoji_manager_new.MaiEmoji()
emoji.full_path = _DummyPath()
emoji.file_name = "keep.png"
emoji.emoji_hash = "hash-keep"
emoji.file_hash = "hash-keep"
result = manager.delete_emoji(emoji)
@ -988,7 +988,7 @@ def test_delete_emoji_success(monkeypatch):
emoji = emoji_manager_new.MaiEmoji()
emoji.full_path = _DummyPath()
emoji.file_name = "ok.png"
emoji.emoji_hash = "hash-ok"
emoji.file_hash = "hash-ok"
result = manager.delete_emoji(emoji)
@ -1043,7 +1043,7 @@ def test_update_emoji_usage_success(monkeypatch):
monkeypatch.setattr(emoji_manager_new, "get_db_session", _get_db_session)
emoji = emoji_manager_new.MaiEmoji()
emoji.emoji_hash = "hash-ok"
emoji.file_hash = "hash-ok"
result = manager.update_emoji_usage(emoji)
@ -1090,7 +1090,7 @@ def test_update_emoji_usage_missing_record(monkeypatch):
monkeypatch.setattr(emoji_manager_new, "get_db_session", _get_db_session)
emoji = emoji_manager_new.MaiEmoji()
emoji.emoji_hash = "hash-missing"
emoji.file_hash = "hash-missing"
result = manager.update_emoji_usage(emoji)
@ -1129,7 +1129,7 @@ def test_update_emoji_usage_execute_error(monkeypatch):
monkeypatch.setattr(emoji_manager_new, "get_db_session", _get_db_session)
emoji = emoji_manager_new.MaiEmoji()
emoji.emoji_hash = "hash-execute"
emoji.file_hash = "hash-execute"
result = manager.update_emoji_usage(emoji)
@ -1148,7 +1148,7 @@ def test_update_emoji_usage_get_db_session_error(monkeypatch):
monkeypatch.setattr(emoji_manager_new, "get_db_session", _get_db_session)
emoji = emoji_manager_new.MaiEmoji()
emoji.emoji_hash = "hash-session"
emoji.file_hash = "hash-session"
result = manager.update_emoji_usage(emoji)
@ -1264,7 +1264,7 @@ async def test_build_emoji_description_calls_hash_and_sets_description(monkeypat
)
emoji = emoji_manager_new.MaiEmoji()
emoji.emoji_hash = None
emoji.file_hash = None
emoji._format = "png"
emoji.full_path = Path("/tmp/a.png")
@ -1292,7 +1292,7 @@ async def test_build_emoji_description_gif_conversion_error(monkeypatch):
monkeypatch.setattr(emoji_manager_new.ImageUtils, "gif_2_static_image", staticmethod(_gif_to_static))
emoji = emoji_manager_new.MaiEmoji()
emoji.emoji_hash = "hash"
emoji.file_hash = "hash"
emoji._format = "gif"
emoji.full_path = Path("/tmp/a.gif")
@ -1330,7 +1330,7 @@ async def test_build_emoji_description_content_filtration_reject(monkeypatch):
)
emoji = emoji_manager_new.MaiEmoji()
emoji.emoji_hash = "hash"
emoji.file_hash = "hash"
emoji._format = "png"
emoji.full_path = Path("/tmp/a.png")
@ -1365,7 +1365,7 @@ async def test_build_emoji_description_content_filtration_pass(monkeypatch):
)
emoji = emoji_manager_new.MaiEmoji()
emoji.emoji_hash = "hash"
emoji.file_hash = "hash"
emoji._format = "png"
emoji.full_path = Path("/tmp/a.png")
@ -1394,7 +1394,7 @@ async def test_build_emoji_description_vlm_exception_propagates(monkeypatch):
)
emoji = emoji_manager_new.MaiEmoji()
emoji.emoji_hash = "hash"
emoji.file_hash = "hash"
emoji._format = "png"
emoji.full_path = Path("/tmp/a.png")
@ -1738,7 +1738,7 @@ async def test_register_emoji_by_filename_duplicate_hash(monkeypatch, tmp_path):
class _Emoji(emoji_manager_new.MaiEmoji):
async def calculate_hash_format(self):
self.emoji_hash = "hash-dup"
self.file_hash = "hash-dup"
self.full_path = file_path
return True
@ -1765,7 +1765,7 @@ async def test_register_emoji_by_filename_build_description_failed(monkeypatch,
class _Emoji(emoji_manager_new.MaiEmoji):
async def calculate_hash_format(self):
self.emoji_hash = "hash-desc"
self.file_hash = "hash-desc"
self.full_path = file_path
return True
@ -1793,7 +1793,7 @@ async def test_register_emoji_by_filename_build_emotion_failed(monkeypatch, tmp_
class _Emoji(emoji_manager_new.MaiEmoji):
async def calculate_hash_format(self):
self.emoji_hash = "hash-emo"
self.file_hash = "hash-emo"
self.full_path = file_path
return True
@ -1827,7 +1827,7 @@ async def test_register_emoji_by_filename_capacity_replace_failed(monkeypatch, t
class _Emoji(emoji_manager_new.MaiEmoji):
async def calculate_hash_format(self):
self.emoji_hash = "hash-full"
self.file_hash = "hash-full"
self.full_path = file_path
return True
@ -1866,7 +1866,7 @@ async def test_register_emoji_by_filename_capacity_replace_success(monkeypatch,
class _Emoji(emoji_manager_new.MaiEmoji):
async def calculate_hash_format(self):
self.emoji_hash = "hash-full-ok"
self.file_hash = "hash-full-ok"
self.full_path = file_path
return True
@ -1904,7 +1904,7 @@ async def test_register_emoji_by_filename_register_db_failed(monkeypatch, tmp_pa
class _Emoji(emoji_manager_new.MaiEmoji):
async def calculate_hash_format(self):
self.emoji_hash = "hash-db-fail"
self.file_hash = "hash-db-fail"
self.full_path = file_path
return True
@ -1939,7 +1939,7 @@ async def test_register_emoji_by_filename_register_db_success(monkeypatch, tmp_p
class _Emoji(emoji_manager_new.MaiEmoji):
async def calculate_hash_format(self):
self.emoji_hash = "hash-db-ok"
self.file_hash = "hash-db-ok"
self.full_path = file_path
self.file_name = "db-ok.png"
return True

View File

@ -144,12 +144,12 @@ class EmojiManager:
# 删除数据库记录
try:
with get_db_session() as session:
statement = select(Images).filter_by(image_hash=emoji.emoji_hash, image_type=ImageType.EMOJI).limit(1)
statement = select(Images).filter_by(image_hash=emoji.file_hash, image_type=ImageType.EMOJI).limit(1)
if image_record := session.exec(statement).first():
session.delete(image_record)
logger.info(f"[删除表情包] 成功删除数据库中的表情包记录: {emoji.emoji_hash}")
logger.info(f"[删除表情包] 成功删除数据库中的表情包记录: {emoji.file_hash}")
else:
logger.warning(f"[删除表情包] 数据库中未找到表情包记录: {emoji.emoji_hash}")
logger.warning(f"[删除表情包] 数据库中未找到表情包记录: {emoji.file_hash}")
except Exception as e:
logger.error(f"[删除表情包] 删除数据库记录时出错: {e}")
# 如果数据库记录删除失败,但文件可能已删除,记录一个警告
@ -170,14 +170,14 @@ class EmojiManager:
"""
try:
with get_db_session() as session:
statement = select(Images).filter_by(image_hash=emoji.emoji_hash, image_type=ImageType.EMOJI).limit(1)
statement = select(Images).filter_by(image_hash=emoji.file_hash, image_type=ImageType.EMOJI).limit(1)
if image_record := session.exec(statement).first():
image_record.query_count += 1
image_record.last_used_time = datetime.now()
session.add(image_record)
logger.info(f"[记录表情包使用] 成功记录表情包使用: {emoji.emoji_hash}")
logger.info(f"[记录表情包使用] 成功记录表情包使用: {emoji.file_hash}")
else:
logger.error(f"[记录表情包使用] 未找到表情包记录: {emoji.emoji_hash}")
logger.error(f"[记录表情包使用] 未找到表情包记录: {emoji.file_hash}")
return False
except Exception as e:
logger.error(f"[记录表情包使用] 记录使用时出错: {e}")
@ -194,7 +194,7 @@ class EmojiManager:
return (Optional[MaiEmoji]): 返回表情包对象如果未找到则返回 None
"""
for emoji in self.emojis:
if emoji.emoji_hash == emoji_hash and not emoji.is_deleted:
if emoji.file_hash == emoji_hash and not emoji.is_deleted:
return emoji
logger.info(f"[获取表情包] 未找到哈希值为 {emoji_hash} 的表情包")
return None
@ -210,8 +210,8 @@ class EmojiManager:
"""
try:
with get_db_session() as session:
statement = select(Images).filter_by(image_hash=emoji_hash, image_type=ImageType.EMOJI)
if image_record := session.execute(statement).scalars().first():
statement = select(Images).filter_by(image_hash=emoji_hash, image_type=ImageType.EMOJI).limit(1)
if image_record := session.exec(statement).first():
return MaiEmoji.from_db_instance(image_record)
logger.info(f"[数据库] 未找到哈希值为 {emoji_hash} 的表情包记录")
return None
@ -224,7 +224,7 @@ class EmojiManager:
根据文本情感标签获取合适的表情包
Args:
text_emotion (str): 文本的情感标签
emotion_label (str): 文本的情感标签
Returns:
return (Optional[MaiEmoji]): 返回表情包对象如果未找到则返回 None
"""
@ -320,7 +320,7 @@ class EmojiManager:
Returns:
return (Tuple[bool, MaiEmoji]): 返回是否成功构建描述及表情包对象
"""
if not target_emoji.emoji_hash:
if not target_emoji.file_hash:
# Should not happen, but just in case
await target_emoji.calculate_hash_format()
@ -502,7 +502,7 @@ class EmojiManager:
return False
file_full_path = target_emoji.full_path # 更新为可能修正后的路径
# 2. 检查是否已经存在过
if existing_emoji := self.get_emoji_by_hash(target_emoji.emoji_hash):
if existing_emoji := self.get_emoji_by_hash(target_emoji.file_hash):
logger.warning(f"[注册表情包] 表情包已存在,跳过注册: {existing_emoji.file_name}")
return False
# 3. 构建描述

View File

@ -20,6 +20,20 @@ logger = get_logger("emoji")
class BaseImageDataModel(BaseDatabaseDataModel[Images]):
def __init__(self, full_path: str | Path, image_bytes: Optional[bytes] = None):
if not full_path:
# 创建时候即检测文件路径合法性
raise ValueError("表情包路径不能为空")
if Path(full_path).is_dir() or not Path(full_path).exists():
raise FileNotFoundError(f"表情包路径无效: {full_path}")
resolved_path = Path(full_path).absolute().resolve()
self.full_path: Path = resolved_path
self.dir_path: Path = resolved_path.parent.resolve()
self.file_name: str = resolved_path.name
self.file_hash: str = None # type: ignore
self.image_bytes: Optional[bytes] = image_bytes
def read_image_bytes(self, path: Path) -> bytes:
"""
同步读取图片文件的字节内容
@ -65,55 +79,6 @@ class BaseImageDataModel(BaseDatabaseDataModel[Images]):
logger.error(f"[获取图片格式] 读取图片格式时发生错误: {e}")
raise e
class MaiEmoji(BaseImageDataModel):
def __init__(self, full_path: str | Path):
if not full_path:
# 创建时候即检测文件路径合法性
raise ValueError("表情包路径不能为空")
if Path(full_path).is_dir() or not Path(full_path).exists():
raise FileNotFoundError(f"表情包路径无效: {full_path}")
resolved_path = Path(full_path).absolute().resolve()
self.full_path: Path = resolved_path
self.dir_path: Path = resolved_path.parent.resolve()
self.file_name: str = resolved_path.name
# self.embedding = []
self.emoji_hash: str = None # type: ignore
self.description = ""
self.emotion: List[str] = []
self.query_count = 0
self.register_time: Optional[datetime] = None
self.last_used_time: Optional[datetime] = None
# 私有属性
self.is_deleted = False
self._format: str = "" # 图片格式
@classmethod
def from_db_instance(cls, db_record: Images):
obj = cls(db_record.full_path)
obj.emoji_hash = db_record.image_hash
obj.description = db_record.description
if db_record.emotion:
obj.emotion = db_record.emotion.split(",")
obj.query_count = db_record.query_count
obj.last_used_time = db_record.last_used_time
obj.register_time = db_record.register_time
return obj
def to_db_instance(self) -> Images:
emotion_str = ",".join(self.emotion) if self.emotion else None
return Images(
image_hash=self.emoji_hash,
description=self.description,
full_path=str(self.full_path),
image_type=ImageType.EMOJI,
emotion=emotion_str,
query_count=self.query_count,
last_used_time=self.last_used_time,
register_time=self.register_time,
)
async def calculate_hash_format(self) -> bool:
"""
异步计算表情包的哈希值和格式
@ -121,13 +86,17 @@ class MaiEmoji(BaseImageDataModel):
Returns:
return (bool): 如果成功计算哈希值和格式则返回True否则返回False
"""
logger.debug(f"[初始化] 正在读取文件: {self.full_path}")
try:
# 计算哈希值
logger.debug(f"[初始化] 计算 {self.file_name} 的哈希值...")
image_bytes = await asyncio.to_thread(self.read_image_bytes, self.full_path)
self.emoji_hash = hashlib.sha256(image_bytes).hexdigest()
logger.debug(f"[初始化] {self.file_name} 计算哈希值成功: {self.emoji_hash}")
if not self.image_bytes:
logger.debug(f"[初始化] 正在读取文件: {self.full_path}")
image_bytes = await asyncio.to_thread(self.read_image_bytes, self.full_path)
else:
image_bytes = self.image_bytes
self.file_hash = hashlib.sha256(image_bytes).hexdigest()
logger.debug(f"[初始化] {self.file_name} 计算哈希值成功: {self.file_hash}")
# 用PIL读取图片格式
logger.debug(f"[初始化] 读取 {self.file_name} 的图片格式...")
@ -146,7 +115,47 @@ class MaiEmoji(BaseImageDataModel):
return True
except Exception as e:
logger.error(f"[初始化] 初始化表情包时发生错误: {e}")
logger.error(f"[初始化] 初始化图片时发生错误: {e}")
logger.error(traceback.format_exc())
self.is_deleted = True
return False
class MaiEmoji(BaseImageDataModel):
def __init__(self, full_path: str | Path, image_bytes: Optional[bytes] = None):
# self.embedding = []
self.description = ""
self.emotion: List[str] = []
self.query_count = 0
self.register_time: Optional[datetime] = None
self.last_used_time: Optional[datetime] = None
# 私有属性
self.is_deleted = False
self._format: str = "" # 图片格式
super().__init__(full_path, image_bytes)
@classmethod
def from_db_instance(cls, db_record: Images):
obj = cls(db_record.full_path)
obj.file_hash = db_record.image_hash
obj.description = db_record.description
if db_record.emotion:
obj.emotion = db_record.emotion.split(",")
obj.query_count = db_record.query_count
obj.last_used_time = db_record.last_used_time
obj.register_time = db_record.register_time
return obj
def to_db_instance(self) -> Images:
emotion_str = ",".join(self.emotion) if self.emotion else None
return Images(
image_hash=self.file_hash,
description=self.description,
full_path=str(self.full_path),
image_type=ImageType.EMOJI,
emotion=emotion_str,
query_count=self.query_count,
last_used_time=self.last_used_time,
register_time=self.register_time,
)