提取一些公共方法

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 = "" file_name: str = ""
description: str | None = None description: str | None = None
emotion: list[str] | None = None emotion: list[str] | None = None
emoji_hash: str | None = None file_hash: str | None = None
is_deleted: bool = False is_deleted: bool = False
query_count: int = 0 query_count: int = 0
register_time: object | None = None 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 = emoji_manager_new.MaiEmoji()
emoji.full_path = _DummyPath() emoji.full_path = _DummyPath()
emoji.file_name = "missing.png" emoji.file_name = "missing.png"
emoji.emoji_hash = "hash-missing" emoji.file_hash = "hash-missing"
result = manager.delete_emoji(emoji) result = manager.delete_emoji(emoji)
@ -859,7 +859,7 @@ def test_delete_emoji_file_delete_error(monkeypatch):
emoji = emoji_manager_new.MaiEmoji() emoji = emoji_manager_new.MaiEmoji()
emoji.full_path = _DummyPath() emoji.full_path = _DummyPath()
emoji.file_name = "boom.png" emoji.file_name = "boom.png"
emoji.emoji_hash = "hash-boom" emoji.file_hash = "hash-boom"
result = manager.delete_emoji(emoji) 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 = emoji_manager_new.MaiEmoji()
emoji.full_path = _DummyPath() emoji.full_path = _DummyPath()
emoji.file_name = "keep.png" emoji.file_name = "keep.png"
emoji.emoji_hash = "hash-keep" emoji.file_hash = "hash-keep"
result = manager.delete_emoji(emoji) result = manager.delete_emoji(emoji)
@ -988,7 +988,7 @@ def test_delete_emoji_success(monkeypatch):
emoji = emoji_manager_new.MaiEmoji() emoji = emoji_manager_new.MaiEmoji()
emoji.full_path = _DummyPath() emoji.full_path = _DummyPath()
emoji.file_name = "ok.png" emoji.file_name = "ok.png"
emoji.emoji_hash = "hash-ok" emoji.file_hash = "hash-ok"
result = manager.delete_emoji(emoji) 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) monkeypatch.setattr(emoji_manager_new, "get_db_session", _get_db_session)
emoji = emoji_manager_new.MaiEmoji() emoji = emoji_manager_new.MaiEmoji()
emoji.emoji_hash = "hash-ok" emoji.file_hash = "hash-ok"
result = manager.update_emoji_usage(emoji) 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) monkeypatch.setattr(emoji_manager_new, "get_db_session", _get_db_session)
emoji = emoji_manager_new.MaiEmoji() emoji = emoji_manager_new.MaiEmoji()
emoji.emoji_hash = "hash-missing" emoji.file_hash = "hash-missing"
result = manager.update_emoji_usage(emoji) 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) monkeypatch.setattr(emoji_manager_new, "get_db_session", _get_db_session)
emoji = emoji_manager_new.MaiEmoji() emoji = emoji_manager_new.MaiEmoji()
emoji.emoji_hash = "hash-execute" emoji.file_hash = "hash-execute"
result = manager.update_emoji_usage(emoji) 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) monkeypatch.setattr(emoji_manager_new, "get_db_session", _get_db_session)
emoji = emoji_manager_new.MaiEmoji() emoji = emoji_manager_new.MaiEmoji()
emoji.emoji_hash = "hash-session" emoji.file_hash = "hash-session"
result = manager.update_emoji_usage(emoji) 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_manager_new.MaiEmoji()
emoji.emoji_hash = None emoji.file_hash = None
emoji._format = "png" emoji._format = "png"
emoji.full_path = Path("/tmp/a.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)) monkeypatch.setattr(emoji_manager_new.ImageUtils, "gif_2_static_image", staticmethod(_gif_to_static))
emoji = emoji_manager_new.MaiEmoji() emoji = emoji_manager_new.MaiEmoji()
emoji.emoji_hash = "hash" emoji.file_hash = "hash"
emoji._format = "gif" emoji._format = "gif"
emoji.full_path = Path("/tmp/a.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_manager_new.MaiEmoji()
emoji.emoji_hash = "hash" emoji.file_hash = "hash"
emoji._format = "png" emoji._format = "png"
emoji.full_path = Path("/tmp/a.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_manager_new.MaiEmoji()
emoji.emoji_hash = "hash" emoji.file_hash = "hash"
emoji._format = "png" emoji._format = "png"
emoji.full_path = Path("/tmp/a.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_manager_new.MaiEmoji()
emoji.emoji_hash = "hash" emoji.file_hash = "hash"
emoji._format = "png" emoji._format = "png"
emoji.full_path = Path("/tmp/a.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): class _Emoji(emoji_manager_new.MaiEmoji):
async def calculate_hash_format(self): async def calculate_hash_format(self):
self.emoji_hash = "hash-dup" self.file_hash = "hash-dup"
self.full_path = file_path self.full_path = file_path
return True return True
@ -1765,7 +1765,7 @@ async def test_register_emoji_by_filename_build_description_failed(monkeypatch,
class _Emoji(emoji_manager_new.MaiEmoji): class _Emoji(emoji_manager_new.MaiEmoji):
async def calculate_hash_format(self): async def calculate_hash_format(self):
self.emoji_hash = "hash-desc" self.file_hash = "hash-desc"
self.full_path = file_path self.full_path = file_path
return True return True
@ -1793,7 +1793,7 @@ async def test_register_emoji_by_filename_build_emotion_failed(monkeypatch, tmp_
class _Emoji(emoji_manager_new.MaiEmoji): class _Emoji(emoji_manager_new.MaiEmoji):
async def calculate_hash_format(self): async def calculate_hash_format(self):
self.emoji_hash = "hash-emo" self.file_hash = "hash-emo"
self.full_path = file_path self.full_path = file_path
return True return True
@ -1827,7 +1827,7 @@ async def test_register_emoji_by_filename_capacity_replace_failed(monkeypatch, t
class _Emoji(emoji_manager_new.MaiEmoji): class _Emoji(emoji_manager_new.MaiEmoji):
async def calculate_hash_format(self): async def calculate_hash_format(self):
self.emoji_hash = "hash-full" self.file_hash = "hash-full"
self.full_path = file_path self.full_path = file_path
return True return True
@ -1866,7 +1866,7 @@ async def test_register_emoji_by_filename_capacity_replace_success(monkeypatch,
class _Emoji(emoji_manager_new.MaiEmoji): class _Emoji(emoji_manager_new.MaiEmoji):
async def calculate_hash_format(self): async def calculate_hash_format(self):
self.emoji_hash = "hash-full-ok" self.file_hash = "hash-full-ok"
self.full_path = file_path self.full_path = file_path
return True 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): class _Emoji(emoji_manager_new.MaiEmoji):
async def calculate_hash_format(self): async def calculate_hash_format(self):
self.emoji_hash = "hash-db-fail" self.file_hash = "hash-db-fail"
self.full_path = file_path self.full_path = file_path
return True 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): class _Emoji(emoji_manager_new.MaiEmoji):
async def calculate_hash_format(self): async def calculate_hash_format(self):
self.emoji_hash = "hash-db-ok" self.file_hash = "hash-db-ok"
self.full_path = file_path self.full_path = file_path
self.file_name = "db-ok.png" self.file_name = "db-ok.png"
return True return True

View File

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

View File

@ -20,6 +20,20 @@ logger = get_logger("emoji")
class BaseImageDataModel(BaseDatabaseDataModel[Images]): 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: def read_image_bytes(self, path: Path) -> bytes:
""" """
同步读取图片文件的字节内容 同步读取图片文件的字节内容
@ -65,55 +79,6 @@ class BaseImageDataModel(BaseDatabaseDataModel[Images]):
logger.error(f"[获取图片格式] 读取图片格式时发生错误: {e}") logger.error(f"[获取图片格式] 读取图片格式时发生错误: {e}")
raise 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: async def calculate_hash_format(self) -> bool:
""" """
异步计算表情包的哈希值和格式 异步计算表情包的哈希值和格式
@ -121,13 +86,17 @@ class MaiEmoji(BaseImageDataModel):
Returns: Returns:
return (bool): 如果成功计算哈希值和格式则返回True否则返回False return (bool): 如果成功计算哈希值和格式则返回True否则返回False
""" """
logger.debug(f"[初始化] 正在读取文件: {self.full_path}")
try: try:
# 计算哈希值 # 计算哈希值
logger.debug(f"[初始化] 计算 {self.file_name} 的哈希值...") logger.debug(f"[初始化] 计算 {self.file_name} 的哈希值...")
image_bytes = await asyncio.to_thread(self.read_image_bytes, self.full_path) if not self.image_bytes:
self.emoji_hash = hashlib.sha256(image_bytes).hexdigest() logger.debug(f"[初始化] 正在读取文件: {self.full_path}")
logger.debug(f"[初始化] {self.file_name} 计算哈希值成功: {self.emoji_hash}") 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读取图片格式 # 用PIL读取图片格式
logger.debug(f"[初始化] 读取 {self.file_name} 的图片格式...") logger.debug(f"[初始化] 读取 {self.file_name} 的图片格式...")
@ -146,7 +115,47 @@ class MaiEmoji(BaseImageDataModel):
return True return True
except Exception as e: except Exception as e:
logger.error(f"[初始化] 初始化表情包时发生错误: {e}") logger.error(f"[初始化] 初始化图片时发生错误: {e}")
logger.error(traceback.format_exc()) logger.error(traceback.format_exc())
self.is_deleted = True self.is_deleted = True
return False 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,
)