fix(critical): 在session内部完成字典转换,彻底解决detached对象greenlet错误
This commit is contained in:
@@ -108,20 +108,18 @@ class CRUDBase:
|
|||||||
instance = result.scalar_one_or_none()
|
instance = result.scalar_one_or_none()
|
||||||
|
|
||||||
if instance is not None:
|
if instance is not None:
|
||||||
# 预加载所有字段
|
# ✅ 在 session 内部转换为字典,此时所有字段都可安全访问
|
||||||
for column in self.model.__table__.columns:
|
instance_dict = _model_to_dict(instance)
|
||||||
try:
|
|
||||||
getattr(instance, column.name)
|
# 写入缓存
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# 转换为字典并写入缓存
|
|
||||||
if use_cache:
|
if use_cache:
|
||||||
instance_dict = _model_to_dict(instance)
|
|
||||||
cache = await get_cache()
|
cache = await get_cache()
|
||||||
await cache.set(cache_key, instance_dict)
|
await cache.set(cache_key, instance_dict)
|
||||||
|
|
||||||
|
# 从字典重建对象返回(detached状态,所有字段已加载)
|
||||||
|
return _dict_to_model(self.model, instance_dict)
|
||||||
|
|
||||||
return instance
|
return None
|
||||||
|
|
||||||
async def get_by(
|
async def get_by(
|
||||||
self,
|
self,
|
||||||
@@ -159,21 +157,18 @@ class CRUDBase:
|
|||||||
instance = result.scalar_one_or_none()
|
instance = result.scalar_one_or_none()
|
||||||
|
|
||||||
if instance is not None:
|
if instance is not None:
|
||||||
# 触发所有列的加载,避免 detached 后的延迟加载问题
|
# ✅ 在 session 内部转换为字典,此时所有字段都可安全访问
|
||||||
# 遍历所有列属性以确保它们被加载到内存中
|
instance_dict = _model_to_dict(instance)
|
||||||
for column in self.model.__table__.columns:
|
|
||||||
try:
|
# 写入缓存
|
||||||
getattr(instance, column.name)
|
|
||||||
except Exception:
|
|
||||||
pass # 忽略访问错误
|
|
||||||
|
|
||||||
# 转换为字典并写入缓存
|
|
||||||
if use_cache:
|
if use_cache:
|
||||||
instance_dict = _model_to_dict(instance)
|
|
||||||
cache = await get_cache()
|
cache = await get_cache()
|
||||||
await cache.set(cache_key, instance_dict)
|
await cache.set(cache_key, instance_dict)
|
||||||
|
|
||||||
|
# 从字典重建对象返回(detached状态,所有字段已加载)
|
||||||
|
return _dict_to_model(self.model, instance_dict)
|
||||||
|
|
||||||
return instance
|
return None
|
||||||
|
|
||||||
async def get_multi(
|
async def get_multi(
|
||||||
self,
|
self,
|
||||||
@@ -222,21 +217,16 @@ class CRUDBase:
|
|||||||
result = await session.execute(stmt)
|
result = await session.execute(stmt)
|
||||||
instances = list(result.scalars().all())
|
instances = list(result.scalars().all())
|
||||||
|
|
||||||
# 触发所有实例的列加载,避免 detached 后的延迟加载问题
|
# ✅ 在 session 内部转换为字典列表,此时所有字段都可安全访问
|
||||||
for instance in instances:
|
instances_dicts = [_model_to_dict(inst) for inst in instances]
|
||||||
for column in self.model.__table__.columns:
|
|
||||||
try:
|
# 写入缓存
|
||||||
getattr(instance, column.name)
|
|
||||||
except Exception:
|
|
||||||
pass # 忽略访问错误
|
|
||||||
|
|
||||||
# 转换为字典列表并写入缓存
|
|
||||||
if use_cache:
|
if use_cache:
|
||||||
instances_dicts = [_model_to_dict(inst) for inst in instances]
|
|
||||||
cache = await get_cache()
|
cache = await get_cache()
|
||||||
await cache.set(cache_key, instances_dicts)
|
await cache.set(cache_key, instances_dicts)
|
||||||
|
|
||||||
return instances
|
# 从字典列表重建对象列表返回(detached状态,所有字段已加载)
|
||||||
|
return [_dict_to_model(self.model, d) for d in instances_dicts]
|
||||||
|
|
||||||
async def create(
|
async def create(
|
||||||
self,
|
self,
|
||||||
|
|||||||
@@ -205,21 +205,16 @@ class QueryBuilder(Generic[T]):
|
|||||||
result = await session.execute(self._stmt)
|
result = await session.execute(self._stmt)
|
||||||
instances = list(result.scalars().all())
|
instances = list(result.scalars().all())
|
||||||
|
|
||||||
# 预加载所有列以避免detached对象的lazy loading问题
|
# ✅ 在 session 内部转换为字典列表,此时所有字段都可安全访问
|
||||||
for instance in instances:
|
instances_dicts = [_model_to_dict(inst) for inst in instances]
|
||||||
for column in self.model.__table__.columns:
|
|
||||||
try:
|
# 写入缓存
|
||||||
getattr(instance, column.name)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# 转换为字典列表并写入缓存
|
|
||||||
if self._use_cache:
|
if self._use_cache:
|
||||||
instances_dicts = [_model_to_dict(inst) for inst in instances]
|
|
||||||
cache = await get_cache()
|
cache = await get_cache()
|
||||||
await cache.set(cache_key, instances_dicts)
|
await cache.set(cache_key, instances_dicts)
|
||||||
|
|
||||||
return instances
|
# 从字典列表重建对象列表返回(detached状态,所有字段已加载)
|
||||||
|
return [_dict_to_model(self.model, d) for d in instances_dicts]
|
||||||
|
|
||||||
async def first(self) -> T | None:
|
async def first(self) -> T | None:
|
||||||
"""获取第一个结果
|
"""获取第一个结果
|
||||||
@@ -243,21 +238,19 @@ class QueryBuilder(Generic[T]):
|
|||||||
result = await session.execute(self._stmt)
|
result = await session.execute(self._stmt)
|
||||||
instance = result.scalars().first()
|
instance = result.scalars().first()
|
||||||
|
|
||||||
# 预加载所有列以避免detached对象的lazy loading问题
|
|
||||||
if instance is not None:
|
if instance is not None:
|
||||||
for column in self.model.__table__.columns:
|
# ✅ 在 session 内部转换为字典,此时所有字段都可安全访问
|
||||||
try:
|
|
||||||
getattr(instance, column.name)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# 转换为字典并写入缓存
|
|
||||||
if instance is not None and self._use_cache:
|
|
||||||
instance_dict = _model_to_dict(instance)
|
instance_dict = _model_to_dict(instance)
|
||||||
cache = await get_cache()
|
|
||||||
await cache.set(cache_key, instance_dict)
|
# 写入缓存
|
||||||
|
if self._use_cache:
|
||||||
|
cache = await get_cache()
|
||||||
|
await cache.set(cache_key, instance_dict)
|
||||||
|
|
||||||
|
# 从字典重建对象返回(detached状态,所有字段已加载)
|
||||||
|
return _dict_to_model(self.model, instance_dict)
|
||||||
|
|
||||||
return instance
|
return None
|
||||||
|
|
||||||
async def count(self) -> int:
|
async def count(self) -> int:
|
||||||
"""统计数量
|
"""统计数量
|
||||||
|
|||||||
@@ -196,31 +196,18 @@ class RelationshipFetcher:
|
|||||||
|
|
||||||
if relationship:
|
if relationship:
|
||||||
# 将SQLAlchemy对象转换为字典以保持兼容性
|
# 将SQLAlchemy对象转换为字典以保持兼容性
|
||||||
# 使用 inspect 安全访问 detached 对象,避免触发 lazy loading
|
# 直接使用 __dict__ 访问,避免触发 SQLAlchemy 的描述符和 lazy loading
|
||||||
from sqlalchemy import inspect as sa_inspect
|
# 方案A已经确保所有字段在缓存前都已预加载,所以 __dict__ 中有完整数据
|
||||||
|
|
||||||
rel_data = {}
|
|
||||||
try:
|
try:
|
||||||
# 使用 inspect 获取对象状态,避免触发 ORM 机制
|
|
||||||
state = sa_inspect(relationship)
|
|
||||||
rel_data = {
|
rel_data = {
|
||||||
"user_aliases": state.attrs.user_aliases.value if hasattr(state.attrs, "user_aliases") else None,
|
"user_aliases": relationship.__dict__.get("user_aliases"),
|
||||||
"relationship_text": state.attrs.relationship_text.value if hasattr(state.attrs, "relationship_text") else None,
|
"relationship_text": relationship.__dict__.get("relationship_text"),
|
||||||
"preference_keywords": state.attrs.preference_keywords.value if hasattr(state.attrs, "preference_keywords") else None,
|
"preference_keywords": relationship.__dict__.get("preference_keywords"),
|
||||||
"relationship_score": state.attrs.relationship_score.value if hasattr(state.attrs, "relationship_score") else None,
|
"relationship_score": relationship.__dict__.get("relationship_score"),
|
||||||
}
|
}
|
||||||
except Exception as attr_error:
|
except Exception as attr_error:
|
||||||
logger.warning(f"访问relationship对象属性失败: {attr_error}")
|
logger.warning(f"访问relationship对象属性失败: {attr_error}")
|
||||||
# 如果 inspect 也失败,尝试使用 __dict__ 直接访问
|
rel_data = {}
|
||||||
try:
|
|
||||||
rel_data = {
|
|
||||||
"user_aliases": relationship.__dict__.get("user_aliases"),
|
|
||||||
"relationship_text": relationship.__dict__.get("relationship_text"),
|
|
||||||
"preference_keywords": relationship.__dict__.get("preference_keywords"),
|
|
||||||
"relationship_score": relationship.__dict__.get("relationship_score"),
|
|
||||||
}
|
|
||||||
except Exception:
|
|
||||||
rel_data = {}
|
|
||||||
|
|
||||||
# 5.1 用户别名
|
# 5.1 用户别名
|
||||||
if rel_data.get("user_aliases"):
|
if rel_data.get("user_aliases"):
|
||||||
@@ -284,26 +271,18 @@ class RelationshipFetcher:
|
|||||||
return ""
|
return ""
|
||||||
|
|
||||||
# 将SQLAlchemy对象转换为字典以保持兼容性
|
# 将SQLAlchemy对象转换为字典以保持兼容性
|
||||||
# 使用 inspect 安全访问 detached 对象,避免触发 lazy loading
|
# 直接使用 __dict__ 访问,避免触发 SQLAlchemy 的描述符和 lazy loading
|
||||||
from sqlalchemy import inspect as sa_inspect
|
# 方案A已经确保所有字段在缓存前都已预加载,所以 __dict__ 中有完整数据
|
||||||
|
|
||||||
try:
|
try:
|
||||||
state = sa_inspect(stream)
|
|
||||||
stream_data = {
|
|
||||||
"group_name": state.attrs.group_name.value if hasattr(state.attrs, "group_name") else None,
|
|
||||||
"stream_impression_text": state.attrs.stream_impression_text.value if hasattr(state.attrs, "stream_impression_text") else None,
|
|
||||||
"stream_chat_style": state.attrs.stream_chat_style.value if hasattr(state.attrs, "stream_chat_style") else None,
|
|
||||||
"stream_topic_keywords": state.attrs.stream_topic_keywords.value if hasattr(state.attrs, "stream_topic_keywords") else None,
|
|
||||||
}
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning(f"访问stream对象属性失败: {e}")
|
|
||||||
# 回退到 __dict__ 访问
|
|
||||||
stream_data = {
|
stream_data = {
|
||||||
"group_name": stream.__dict__.get("group_name"),
|
"group_name": stream.__dict__.get("group_name"),
|
||||||
"stream_impression_text": stream.__dict__.get("stream_impression_text"),
|
"stream_impression_text": stream.__dict__.get("stream_impression_text"),
|
||||||
"stream_chat_style": stream.__dict__.get("stream_chat_style"),
|
"stream_chat_style": stream.__dict__.get("stream_chat_style"),
|
||||||
"stream_topic_keywords": stream.__dict__.get("stream_topic_keywords"),
|
"stream_topic_keywords": stream.__dict__.get("stream_topic_keywords"),
|
||||||
}
|
}
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"访问stream对象属性失败: {e}")
|
||||||
|
stream_data = {}
|
||||||
|
|
||||||
impression_parts = []
|
impression_parts = []
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user