fix pydantic

This commit is contained in:
jxxghp
2025-11-01 09:51:23 +08:00
parent 09a19e94d5
commit d523c7c916
47 changed files with 160 additions and 135 deletions
+1 -1
View File
@@ -235,7 +235,7 @@ class ConversationMemoryManager:
# 保存到Redis,设置TTL自动过期 # 保存到Redis,设置TTL自动过期
if settings.CACHE_BACKEND_TYPE == "redis": if settings.CACHE_BACKEND_TYPE == "redis":
try: try:
memory_dict = memory.dict() memory_dict = memory.model_dump()
redis_key = f"agent_memory:{memory.user_id}:{memory.session_id}" if memory.user_id else f"agent_memory:{memory.session_id}" redis_key = f"agent_memory:{memory.user_id}:{memory.session_id}" if memory.user_id else f"agent_memory:{memory.session_id}"
ttl = int(timedelta(days=settings.LLM_REDIS_MEMORY_RETENTION_DAYS).total_seconds()) ttl = int(timedelta(days=settings.LLM_REDIS_MEMORY_RETENTION_DAYS).total_seconds())
await self.redis_helper.set( await self.redis_helper.set(
+1 -1
View File
@@ -21,7 +21,7 @@ class PromptManager:
if prompt_name in self.prompts_cache: if prompt_name in self.prompts_cache:
return self.prompts_cache[prompt_name] return self.prompts_cache[prompt_name]
prompt_file = self.prompts_dir / "prompt" / prompt_name prompt_file = self.prompts_dir / prompt_name
try: try:
with open(prompt_file, 'r', encoding='utf-8') as f: with open(prompt_file, 'r', encoding='utf-8') as f:
+1 -1
View File
@@ -27,7 +27,7 @@ class QueryDownloadsTool(MoviePilotTool):
continue continue
filtered_downloads.append(dl) filtered_downloads.append(dl)
if filtered_downloads: if filtered_downloads:
return json.dumps([d.dict() for d in filtered_downloads]) return json.dumps([d.model_dump() for d in filtered_downloads])
return "未找到相关下载任务。" return "未找到相关下载任务。"
except Exception as e: except Exception as e:
logger.error(f"查询下载失败: {e}", exc_info=True) logger.error(f"查询下载失败: {e}", exc_info=True)
+3 -3
View File
@@ -3,7 +3,7 @@
import json import json
from typing import Optional from typing import Optional
from app.db.media_oper import MediaOper from app.db.mediaserver_oper import MediaServerOper
from app.log import logger from app.log import logger
from app.agent.tools.base import MoviePilotTool from app.agent.tools.base import MoviePilotTool
@@ -16,8 +16,8 @@ class QueryMediaLibraryTool(MoviePilotTool):
title: Optional[str] = None) -> str: title: Optional[str] = None) -> str:
logger.info(f"执行工具: {self.name}, 参数: media_type={media_type}, title={title}") logger.info(f"执行工具: {self.name}, 参数: media_type={media_type}, title={title}")
try: try:
media_oper = MediaOper() media_server_oper = MediaServerOper()
medias = media_oper.list() medias = media_server_oper.list()
filtered_medias = [] filtered_medias = []
for media in medias: for media in medias:
if media_type != "all" and media.type != media_type: if media_type != "all" and media.type != media_type:
+2 -2
View File
@@ -137,7 +137,7 @@ async def transfer(days: Optional[int] = 7,
return [stat[1] for stat in transfer_stat] return [stat[1] for stat in transfer_stat]
@router.get("/cpu", summary="获取当前CPU使用率", response_model=int) @router.get("/cpu", summary="获取当前CPU使用率", response_model=float)
def cpu(_: schemas.TokenPayload = Depends(verify_token)) -> Any: def cpu(_: schemas.TokenPayload = Depends(verify_token)) -> Any:
""" """
获取当前CPU使用率 获取当前CPU使用率
@@ -145,7 +145,7 @@ def cpu(_: schemas.TokenPayload = Depends(verify_token)) -> Any:
return SystemUtils.cpu_usage() return SystemUtils.cpu_usage()
@router.get("/cpu2", summary="获取当前CPU使用率(API_TOKEN", response_model=int) @router.get("/cpu2", summary="获取当前CPU使用率(API_TOKEN", response_model=float)
def cpu2(_: Annotated[str, Depends(verify_apitoken)]) -> Any: def cpu2(_: Annotated[str, Depends(verify_apitoken)]) -> Any:
""" """
获取当前CPU使用率 API_TOKEN认证(?token=xxx 获取当前CPU使用率 API_TOKEN认证(?token=xxx
+3 -3
View File
@@ -40,10 +40,10 @@ def download(
metainfo = MetaInfo(title=torrent_in.title, subtitle=torrent_in.description) metainfo = MetaInfo(title=torrent_in.title, subtitle=torrent_in.description)
# 媒体信息 # 媒体信息
mediainfo = MediaInfo() mediainfo = MediaInfo()
mediainfo.from_dict(media_in.dict()) mediainfo.from_dict(media_in.model_dump())
# 种子信息 # 种子信息
torrentinfo = TorrentInfo() torrentinfo = TorrentInfo()
torrentinfo.from_dict(torrent_in.dict()) torrentinfo.from_dict(torrent_in.model_dump())
# 手动下载始终使用选择的下载器 # 手动下载始终使用选择的下载器
torrentinfo.site_downloader = downloader torrentinfo.site_downloader = downloader
# 上下文 # 上下文
@@ -81,7 +81,7 @@ def add(
return schemas.Response(success=False, message="无法识别媒体信息") return schemas.Response(success=False, message="无法识别媒体信息")
# 种子信息 # 种子信息
torrentinfo = TorrentInfo() torrentinfo = TorrentInfo()
torrentinfo.from_dict(torrent_in.dict()) torrentinfo.from_dict(torrent_in.model_dump())
# 上下文 # 上下文
context = Context( context = Context(
meta_info=metainfo, meta_info=metainfo,
+2 -2
View File
@@ -79,7 +79,7 @@ def exists(media_in: schemas.MediaInfo,
""" """
# 转化为媒体信息对象 # 转化为媒体信息对象
mediainfo = MediaInfo() mediainfo = MediaInfo()
mediainfo.from_dict(media_in.dict()) mediainfo.from_dict(media_in.model_dump())
existsinfo: schemas.ExistMediaInfo = MediaServerChain().media_exists(mediainfo=mediainfo) existsinfo: schemas.ExistMediaInfo = MediaServerChain().media_exists(mediainfo=mediainfo)
if not existsinfo: if not existsinfo:
return [] return []
@@ -108,7 +108,7 @@ def not_exists(media_in: schemas.MediaInfo,
meta.year = media_in.year meta.year = media_in.year
# 转化为媒体信息对象 # 转化为媒体信息对象
mediainfo = MediaInfo() mediainfo = MediaInfo()
mediainfo.from_dict(media_in.dict()) mediainfo.from_dict(media_in.model_dump())
exist_flag, no_exists = DownloadChain().get_no_exists_info(meta=meta, mediainfo=mediainfo) exist_flag, no_exists = DownloadChain().get_no_exists_info(meta=meta, mediainfo=mediainfo)
mediakey = mediainfo.tmdb_id or mediainfo.douban_id mediakey = mediainfo.tmdb_id or mediainfo.douban_id
if mediainfo.type == MediaType.MOVIE: if mediainfo.type == MediaType.MOVIE:
+2 -2
View File
@@ -132,7 +132,7 @@ async def subscribe(subscription: schemas.Subscription, _: schemas.TokenPayload
""" """
客户端webpush通知订阅 客户端webpush通知订阅
""" """
subinfo = subscription.dict() subinfo = subscription.model_dump()
if subinfo not in global_vars.get_subscriptions(): if subinfo not in global_vars.get_subscriptions():
global_vars.push_subscription(subinfo) global_vars.push_subscription(subinfo)
logger.debug(f"通知订阅成功: {subinfo}") logger.debug(f"通知订阅成功: {subinfo}")
@@ -148,7 +148,7 @@ def send_notification(payload: schemas.SubscriptionMessage, _: schemas.TokenPayl
try: try:
webpush( webpush(
subscription_info=sub, subscription_info=sub,
data=json.dumps(payload.dict()), data=json.dumps(payload.model_dump()),
vapid_private_key=settings.VAPID.get("privateKey"), vapid_private_key=settings.VAPID.get("privateKey"),
vapid_claims={ vapid_claims={
"sub": settings.VAPID.get("subject") "sub": settings.VAPID.get("subject")
+3 -3
View File
@@ -67,7 +67,7 @@ async def add_site(
site_in.name = site_info.get("name") site_in.name = site_info.get("name")
site_in.id = None site_in.id = None
site_in.public = 1 if site_info.get("public") else 0 site_in.public = 1 if site_info.get("public") else 0
site = Site(**site_in.dict()) site = Site(**site_in.model_dump())
site.create(db) site.create(db)
# 通知站点更新 # 通知站点更新
await eventmanager.async_send_event(EventType.SiteUpdated, { await eventmanager.async_send_event(EventType.SiteUpdated, {
@@ -92,7 +92,7 @@ async def update_site(
# 校正地址格式 # 校正地址格式
_scheme, _netloc = StringUtils.get_url_netloc(site_in.url) _scheme, _netloc = StringUtils.get_url_netloc(site_in.url)
site_in.url = f"{_scheme}://{_netloc}/" site_in.url = f"{_scheme}://{_netloc}/"
await site.async_update(db, site_in.dict()) await site.async_update(db, site_in.model_dump())
# 通知站点更新 # 通知站点更新
await eventmanager.async_send_event(EventType.SiteUpdated, { await eventmanager.async_send_event(EventType.SiteUpdated, {
"domain": site_in.domain "domain": site_in.domain
@@ -399,7 +399,7 @@ def auth_site(
if not auth_info or not auth_info.site or not auth_info.params: if not auth_info or not auth_info.site or not auth_info.params:
return schemas.Response(success=False, message="请输入认证站点和认证参数") return schemas.Response(success=False, message="请输入认证站点和认证参数")
status, msg = SitesHelper().check_user(auth_info.site, auth_info.params) status, msg = SitesHelper().check_user(auth_info.site, auth_info.params)
SystemConfigOper().set(SystemConfigKey.UserSiteAuthParams, auth_info.dict()) SystemConfigOper().set(SystemConfigKey.UserSiteAuthParams, auth_info.model_dump())
# 认证成功后,重新初始化插件 # 认证成功后,重新初始化插件
PluginManager().init_config() PluginManager().init_config()
Scheduler().init_plugin_jobs() Scheduler().init_plugin_jobs()
+3 -3
View File
@@ -79,7 +79,7 @@ async def create_subscribe(
# 订阅用户 # 订阅用户
subscribe_in.username = current_user.name subscribe_in.username = current_user.name
# 转化为字典 # 转化为字典
subscribe_dict = subscribe_in.dict() subscribe_dict = subscribe_in.model_dump()
if subscribe_in.id: if subscribe_in.id:
subscribe_dict.pop("id", None) subscribe_dict.pop("id", None)
sid, message = await SubscribeChain().async_add(mtype=mtype, sid, message = await SubscribeChain().async_add(mtype=mtype,
@@ -106,7 +106,7 @@ async def update_subscribe(
return schemas.Response(success=False, message="订阅不存在") return schemas.Response(success=False, message="订阅不存在")
# 避免更新缺失集数 # 避免更新缺失集数
old_subscribe_dict = subscribe.to_dict() old_subscribe_dict = subscribe.to_dict()
subscribe_dict = subscribe_in.dict() subscribe_dict = subscribe_in.model_dump()
if not subscribe_in.lack_episode: if not subscribe_in.lack_episode:
# 没有缺失集数时,缺失集数清空,避免更新为0 # 没有缺失集数时,缺失集数清空,避免更新为0
subscribe_dict.pop("lack_episode") subscribe_dict.pop("lack_episode")
@@ -529,7 +529,7 @@ async def subscribe_fork(
""" """
复用订阅 复用订阅
""" """
sub_dict = sub.dict() sub_dict = sub.model_dump()
sub_dict.pop("id") sub_dict.pop("id")
for key in list(sub_dict.keys()): for key in list(sub_dict.keys()):
if not hasattr(schemas.Subscribe(), key): if not hasattr(schemas.Subscribe(), key):
+2 -2
View File
@@ -41,7 +41,7 @@ async def create_user(
user = await current_user.async_get_by_name(db, name=user_in.name) user = await current_user.async_get_by_name(db, name=user_in.name)
if user: if user:
return schemas.Response(success=False, message="用户已存在") return schemas.Response(success=False, message="用户已存在")
user_info = user_in.dict() user_info = user_in.model_dump()
if user_info.get("password"): if user_info.get("password"):
user_info["hashed_password"] = get_password_hash(user_info["password"]) user_info["hashed_password"] = get_password_hash(user_info["password"])
user_info.pop("password") user_info.pop("password")
@@ -59,7 +59,7 @@ async def update_user(
""" """
更新用户 更新用户
""" """
user_info = user_in.dict() user_info = user_in.model_dump()
if user_info.get("password"): if user_info.get("password"):
# 正则表达式匹配密码包含字母、数字、特殊字符中的至少两项 # 正则表达式匹配密码包含字母、数字、特殊字符中的至少两项
pattern = r'^(?![a-zA-Z]+$)(?!\d+$)(?![^\da-zA-Z\s]+$).{6,50}$' pattern = r'^(?![a-zA-Z]+$)(?!\d+$)(?![^\da-zA-Z\s]+$).{6,50}$'
+2 -2
View File
@@ -47,7 +47,7 @@ async def create_workflow(workflow: schemas.Workflow,
workflow.state = "P" workflow.state = "P"
if not workflow.trigger_type: if not workflow.trigger_type:
workflow.trigger_type = "timer" workflow.trigger_type = "timer"
workflow_obj = Workflow(**workflow.dict()) workflow_obj = Workflow(**workflow.model_dump())
await workflow_obj.async_create(db) await workflow_obj.async_create(db)
return schemas.Response(success=True, message="创建工作流成功") return schemas.Response(success=True, message="创建工作流成功")
@@ -277,7 +277,7 @@ def update_workflow(workflow: schemas.Workflow,
return schemas.Response(success=False, message="工作流不存在") return schemas.Response(success=False, message="工作流不存在")
if not wf.trigger_type: if not wf.trigger_type:
workflow.trigger_type = "timer" workflow.trigger_type = "timer"
wf.update(db, workflow.dict()) wf.update(db, workflow.model_dump())
# 更新后的工作流对象 # 更新后的工作流对象
updated_workflow = workflow_oper.get(workflow.id) updated_workflow = workflow_oper.get(workflow.id)
# 更新定时任务 # 更新定时任务
+8 -8
View File
@@ -854,7 +854,7 @@ class ChainBase(metaclass=ABCMeta):
torrentinfo=torrentinfo, transferinfo=transferinfo, **kwargs) torrentinfo=torrentinfo, transferinfo=transferinfo, **kwargs)
# 保存消息 # 保存消息
self.messagehelper.put(message, role="user", title=message.title) self.messagehelper.put(message, role="user", title=message.title)
self.messageoper.add(**message.dict()) self.messageoper.add(**message.model_dump())
# 发送消息按设置隔离 # 发送消息按设置隔离
if not message.userid and message.mtype: if not message.userid and message.mtype:
# 消息隔离设置 # 消息隔离设置
@@ -901,12 +901,12 @@ class ChainBase(metaclass=ABCMeta):
break break
# 按设定发送 # 按设定发送
self.eventmanager.send_event(etype=EventType.NoticeMessage, self.eventmanager.send_event(etype=EventType.NoticeMessage,
data={**send_message.dict(), "type": send_message.mtype}) data={**send_message.model_dump(), "type": send_message.mtype})
self.messagequeue.send_message("post_message", message=send_message) self.messagequeue.send_message("post_message", message=send_message)
if not send_orignal: if not send_orignal:
return return
# 发送消息事件 # 发送消息事件
self.eventmanager.send_event(etype=EventType.NoticeMessage, data={**message.dict(), "type": message.mtype}) self.eventmanager.send_event(etype=EventType.NoticeMessage, data={**message.model_dump(), "type": message.mtype})
# 按原消息发送 # 按原消息发送
self.messagequeue.send_message("post_message", message=message, self.messagequeue.send_message("post_message", message=message,
immediately=True if message.userid else False) immediately=True if message.userid else False)
@@ -933,7 +933,7 @@ class ChainBase(metaclass=ABCMeta):
torrentinfo=torrentinfo, transferinfo=transferinfo, **kwargs) torrentinfo=torrentinfo, transferinfo=transferinfo, **kwargs)
# 保存消息 # 保存消息
self.messagehelper.put(message, role="user", title=message.title) self.messagehelper.put(message, role="user", title=message.title)
await self.messageoper.async_add(**message.dict()) await self.messageoper.async_add(**message.model_dump())
# 发送消息按设置隔离 # 发送消息按设置隔离
if not message.userid and message.mtype: if not message.userid and message.mtype:
# 消息隔离设置 # 消息隔离设置
@@ -980,13 +980,13 @@ class ChainBase(metaclass=ABCMeta):
break break
# 按设定发送 # 按设定发送
await self.eventmanager.async_send_event(etype=EventType.NoticeMessage, await self.eventmanager.async_send_event(etype=EventType.NoticeMessage,
data={**send_message.dict(), "type": send_message.mtype}) data={**send_message.model_dump(), "type": send_message.mtype})
await self.messagequeue.async_send_message("post_message", message=send_message) await self.messagequeue.async_send_message("post_message", message=send_message)
if not send_orignal: if not send_orignal:
return return
# 发送消息事件 # 发送消息事件
await self.eventmanager.async_send_event(etype=EventType.NoticeMessage, await self.eventmanager.async_send_event(etype=EventType.NoticeMessage,
data={**message.dict(), "type": message.mtype}) data={**message.model_dump(), "type": message.mtype})
# 按原消息发送 # 按原消息发送
await self.messagequeue.async_send_message("post_message", message=message, await self.messagequeue.async_send_message("post_message", message=message,
immediately=True if message.userid else False) immediately=True if message.userid else False)
@@ -1000,7 +1000,7 @@ class ChainBase(metaclass=ABCMeta):
""" """
note_list = [media.to_dict() for media in medias] note_list = [media.to_dict() for media in medias]
self.messagehelper.put(message, role="user", note=note_list, title=message.title) self.messagehelper.put(message, role="user", note=note_list, title=message.title)
self.messageoper.add(**message.dict(), note=note_list) self.messageoper.add(**message.model_dump(), note=note_list)
return self.messagequeue.send_message("post_medias_message", message=message, medias=medias, return self.messagequeue.send_message("post_medias_message", message=message, medias=medias,
immediately=True if message.userid else False) immediately=True if message.userid else False)
@@ -1013,7 +1013,7 @@ class ChainBase(metaclass=ABCMeta):
""" """
note_list = [torrent.torrent_info.to_dict() for torrent in torrents] note_list = [torrent.torrent_info.to_dict() for torrent in torrents]
self.messagehelper.put(message, role="user", note=note_list, title=message.title) self.messagehelper.put(message, role="user", note=note_list, title=message.title)
self.messageoper.add(**message.dict(), note=note_list) self.messageoper.add(**message.model_dump(), note=note_list)
return self.messagequeue.send_message("post_torrents_message", message=message, torrents=torrents, return self.messagequeue.send_message("post_torrents_message", message=message, torrents=torrents,
immediately=True if message.userid else False) immediately=True if message.userid else False)
+1 -1
View File
@@ -994,7 +994,7 @@ class DownloadChain(ChainBase):
# 发出下载任务删除事件,如需处理辅种,可监听该事件 # 发出下载任务删除事件,如需处理辅种,可监听该事件
self.eventmanager.send_event(EventType.DownloadDeleted, { self.eventmanager.send_event(EventType.DownloadDeleted, {
"hash": hash_str, "hash": hash_str,
"torrents": [torrent.dict() for torrent in torrents] "torrents": [torrent.model_dump() for torrent in torrents]
}) })
else: else:
logger.info(f"没有在下载器中查询到 {hash_str} 对应的下载任务") logger.info(f"没有在下载器中查询到 {hash_str} 对应的下载任务")
+1 -1
View File
@@ -167,7 +167,7 @@ class MediaServerChain(ChainBase):
for episode in espisodes_info: for episode in espisodes_info:
seasoninfo[episode.season] = episode.episodes seasoninfo[episode.season] = episode.episodes
# 插入数据 # 插入数据
item_dict = item.dict() item_dict = item.model_dump()
item_dict["seasoninfo"] = seasoninfo item_dict["seasoninfo"] = seasoninfo
item_dict["item_type"] = item_type item_dict["item_type"] = item_type
dboper.add(**item_dict) dboper.add(**item_dict)
+1 -1
View File
@@ -56,7 +56,7 @@ class SiteChain(ChainBase):
if userdata: if userdata:
SiteOper().update_userdata(domain=StringUtils.get_url_domain(site.get("domain")), SiteOper().update_userdata(domain=StringUtils.get_url_domain(site.get("domain")),
name=site.get("name"), name=site.get("name"),
payload=userdata.dict()) payload=userdata.model_dump())
# 发送事件 # 发送事件
eventmanager.send_event(EventType.SiteRefreshed, { eventmanager.send_event(EventType.SiteRefreshed, {
"site_id": site.get("id") "site_id": site.get("id")
+1 -1
View File
@@ -180,7 +180,7 @@ class WorkflowExecutor:
""" """
合并上下文 合并上下文
""" """
for key, value in context.dict().items(): for key, value in context.model_dump().items():
if not getattr(self.context, key, None): if not getattr(self.context, key, None):
setattr(self.context, key, value) setattr(self.context, key, value)
+50 -20
View File
@@ -11,7 +11,8 @@ from typing import Any, Dict, List, Optional, Tuple, Type
from urllib.parse import urlparse from urllib.parse import urlparse
from dotenv import set_key from dotenv import set_key
from pydantic import BaseModel, BaseSettings, validator, Field from pydantic import BaseModel, Field, ConfigDict, model_validator
from pydantic_settings import BaseSettings
from app.log import logger, log_settings, LogConfigModel from app.log import logger, log_settings, LogConfigModel
from app.schemas import MediaType from app.schemas import MediaType
@@ -49,8 +50,7 @@ class ConfigModel(BaseModel):
Pydantic 配置模型描述所有配置项及其类型和默认值 Pydantic 配置模型描述所有配置项及其类型和默认值
""" """
class Config: model_config = ConfigDict(extra="ignore") # 忽略未定义的配置项
extra = "ignore" # 忽略未定义的配置项
# ==================== 基础应用配置 ==================== # ==================== 基础应用配置 ====================
# 项目名称 # 项目名称
@@ -92,7 +92,7 @@ class ConfigModel(BaseModel):
# 超级管理员初始用户名 # 超级管理员初始用户名
SUPERUSER: str = "admin" SUPERUSER: str = "admin"
# 超级管理员初始密码 # 超级管理员初始密码
SUPERUSER_PASSWORD: str = None SUPERUSER_PASSWORD: Optional[str] = None
# 辅助认证,允许通过外部服务进行认证、单点登录以及自动创建用户 # 辅助认证,允许通过外部服务进行认证、单点登录以及自动创建用户
AUXILIARY_AUTH_ENABLE: bool = False AUXILIARY_AUTH_ENABLE: bool = False
# API密钥,需要更换 # API密钥,需要更换
@@ -398,9 +398,9 @@ class ConfigModel(BaseModel):
# ==================== 存储配置 ==================== # ==================== 存储配置 ====================
# 对rclone进行快照对比时,是否检查文件夹的修改时间 # 对rclone进行快照对比时,是否检查文件夹的修改时间
RCLONE_SNAPSHOT_CHECK_FOLDER_MODTIME = True RCLONE_SNAPSHOT_CHECK_FOLDER_MODTIME: bool = True
# 对OpenList进行快照对比时,是否检查文件夹的修改时间 # 对OpenList进行快照对比时,是否检查文件夹的修改时间
OPENLIST_SNAPSHOT_CHECK_FOLDER_MODTIME = True OPENLIST_SNAPSHOT_CHECK_FOLDER_MODTIME: bool = True
# ==================== Docker配置 ==================== # ==================== Docker配置 ====================
# Docker Client API地址 # Docker Client API地址
@@ -438,10 +438,11 @@ class Settings(BaseSettings, ConfigModel, LogConfigModel):
系统配置类 系统配置类
""" """
class Config: model_config = ConfigDict(
case_sensitive = True case_sensitive=True,
env_file = SystemUtils.get_env_path() env_file=SystemUtils.get_env_path(),
env_file_encoding = "utf-8" env_file_encoding="utf-8"
)
def __init__(self, **kwargs): def __init__(self, **kwargs):
super().__init__(**kwargs) super().__init__(**kwargs)
@@ -538,19 +539,48 @@ class Settings(BaseSettings, ConfigModel, LogConfigModel):
f"配置项 '{field_name}' 的值 '{value}' 无法转换成正确的类型,使用默认值 '{default}',错误信息: {e}") f"配置项 '{field_name}' 的值 '{value}' 无法转换成正确的类型,使用默认值 '{default}',错误信息: {e}")
return default, True return default, True
@validator('*', pre=True, always=True) @model_validator(mode='before')
def generic_type_validator(cls, value: Any, field): # noqa @classmethod
def generic_type_validator(cls, data: Any): # noqa
""" """
通用校验器尝试将配置值转换为期望的类型 通用校验器尝试将配置值转换为期望的类型
""" """
if field.name == "API_TOKEN": if not isinstance(data, dict):
converted_value, needs_update = cls.validate_api_token(value, value) return data
else:
converted_value, needs_update = cls.generic_type_converter(value, value, field.type_, field.default, # 处理 API_TOKEN 特殊验证
field.name) if 'API_TOKEN' in data:
if needs_update: converted_value, needs_update = cls.validate_api_token(data['API_TOKEN'], data['API_TOKEN'])
cls.update_env_config(field, value, converted_value) if needs_update:
return converted_value cls.update_env_config(
type('Field', (), {'name': 'API_TOKEN'})(),
data['API_TOKEN'],
converted_value
)
data['API_TOKEN'] = converted_value
# 对其他字段进行类型转换
for field_name, field_info in cls.model_fields.items():
if field_name not in data:
continue
value = data[field_name]
if value is None:
continue
field = cls.model_fields.get(field_name)
if field:
converted_value, needs_update = cls.generic_type_converter(
value, value, field.annotation, field.default, field_name
)
if needs_update:
cls.update_env_config(
type('Field', (), {'name': field_name})(),
value,
converted_value
)
data[field_name] = converted_value
return data
@staticmethod @staticmethod
def update_env_config(field: Any, original_value: Any, converted_value: Any) -> Tuple[bool, str]: def update_env_config(field: Any, original_value: Any, converted_value: Any) -> Tuple[bool, str]:
+5 -5
View File
@@ -128,10 +128,10 @@ class TransferHistoryOper(DbOper):
self.add_force( self.add_force(
src=fileitem.path, src=fileitem.path,
src_storage=fileitem.storage, src_storage=fileitem.storage,
src_fileitem=fileitem.dict(), src_fileitem=fileitem.model_dump(),
dest=transferinfo.target_item.path if transferinfo.target_item else None, dest=transferinfo.target_item.path if transferinfo.target_item else None,
dest_storage=transferinfo.target_item.storage if transferinfo.target_item else None, dest_storage=transferinfo.target_item.storage if transferinfo.target_item else None,
dest_fileitem=transferinfo.target_item.dict() if transferinfo.target_item else None, dest_fileitem=transferinfo.target_item.model_dump() if transferinfo.target_item else None,
mode=mode, mode=mode,
type=mediainfo.type.value, type=mediainfo.type.value,
category=mediainfo.category, category=mediainfo.category,
@@ -159,10 +159,10 @@ class TransferHistoryOper(DbOper):
his = self.add_force( his = self.add_force(
src=fileitem.path, src=fileitem.path,
src_storage=fileitem.storage, src_storage=fileitem.storage,
src_fileitem=fileitem.dict(), src_fileitem=fileitem.model_dump(),
dest=transferinfo.target_item.path if transferinfo.target_item else None, dest=transferinfo.target_item.path if transferinfo.target_item else None,
dest_storage=transferinfo.target_item.storage if transferinfo.target_item else None, dest_storage=transferinfo.target_item.storage if transferinfo.target_item else None,
dest_fileitem=transferinfo.target_item.dict() if transferinfo.target_item else None, dest_fileitem=transferinfo.target_item.model_dump() if transferinfo.target_item else None,
mode=mode, mode=mode,
type=mediainfo.type.value, type=mediainfo.type.value,
category=mediainfo.category, category=mediainfo.category,
@@ -188,7 +188,7 @@ class TransferHistoryOper(DbOper):
year=meta.year, year=meta.year,
src=fileitem.path, src=fileitem.path,
src_storage=fileitem.storage, src_storage=fileitem.storage,
src_fileitem=fileitem.dict(), src_fileitem=fileitem.model_dump(),
mode=mode, mode=mode,
seasons=meta.season, seasons=meta.season,
episodes=meta.episode, episodes=meta.episode,
+3 -3
View File
@@ -47,7 +47,7 @@ class StorageHelper:
if s.type == storage: if s.type == storage:
s.config = conf s.config = conf
break break
SystemConfigOper().set(SystemConfigKey.Storages, [s.dict() for s in storagies]) SystemConfigOper().set(SystemConfigKey.Storages, [s.model_dump() for s in storagies])
def add_storage(self, storage: str, name: str, conf: dict): def add_storage(self, storage: str, name: str, conf: dict):
""" """
@@ -68,7 +68,7 @@ class StorageHelper:
name=name, name=name,
config=conf config=conf
)) ))
SystemConfigOper().set(SystemConfigKey.Storages, [s.dict() for s in storagies]) SystemConfigOper().set(SystemConfigKey.Storages, [s.model_dump() for s in storagies])
def reset_storage(self, storage: str): def reset_storage(self, storage: str):
""" """
@@ -79,4 +79,4 @@ class StorageHelper:
if s.type == storage: if s.type == storage:
s.config = {} s.config = {}
break break
SystemConfigOper().set(SystemConfigKey.Storages, [s.dict() for s in storagies]) SystemConfigOper().set(SystemConfigKey.Storages, [s.model_dump() for s in storagies])
+8 -7
View File
@@ -11,7 +11,8 @@ from pathlib import Path
from typing import Dict, Any, Optional from typing import Dict, Any, Optional
import click import click
from pydantic import BaseSettings, BaseModel from pydantic import BaseModel, ConfigDict
from pydantic_settings import BaseSettings
from app.utils.system import SystemUtils from app.utils.system import SystemUtils
@@ -21,8 +22,7 @@ class LogConfigModel(BaseModel):
Pydantic 配置模型描述所有配置项及其类型和默认值 Pydantic 配置模型描述所有配置项及其类型和默认值
""" """
class Config: model_config = ConfigDict(extra="ignore") # 忽略未定义的配置项
extra = "ignore" # 忽略未定义的配置项
# 配置文件目录 # 配置文件目录
CONFIG_DIR: Optional[str] = None CONFIG_DIR: Optional[str] = None
@@ -71,10 +71,11 @@ class LogSettings(BaseSettings, LogConfigModel):
""" """
return self.LOG_MAX_FILE_SIZE * 1024 * 1024 return self.LOG_MAX_FILE_SIZE * 1024 * 1024
class Config: model_config = ConfigDict(
case_sensitive = True case_sensitive=True,
env_file = SystemUtils.get_env_path() env_file=SystemUtils.get_env_path(),
env_file_encoding = "utf-8" env_file_encoding="utf-8"
)
# 实例化日志设置 # 实例化日志设置
+1 -1
View File
@@ -154,7 +154,7 @@ class FilterModule(_ModuleBase):
custom_rules = self.rulehelper.get_custom_rules() custom_rules = self.rulehelper.get_custom_rules()
for rule in custom_rules: for rule in custom_rules:
logger.info(f"加载自定义规则 {rule.id} - {rule.name}") logger.info(f"加载自定义规则 {rule.id} - {rule.name}")
self.rule_set[rule.id] = rule.dict() self.rule_set[rule.id] = rule.model_dump()
@staticmethod @staticmethod
def get_name() -> str: def get_name() -> str:
+11 -9
View File
@@ -2,7 +2,7 @@
from datetime import datetime from datetime import datetime
from typing import Dict, List, Optional, Any from typing import Dict, List, Optional, Any
from pydantic import BaseModel, Field from pydantic import BaseModel, Field, ConfigDict, field_serializer
class ConversationMemory(BaseModel): class ConversationMemory(BaseModel):
@@ -16,10 +16,11 @@ class ConversationMemory(BaseModel):
created_at: datetime = Field(default_factory=datetime.now, description="创建时间") created_at: datetime = Field(default_factory=datetime.now, description="创建时间")
updated_at: datetime = Field(default_factory=datetime.now, description="更新时间") updated_at: datetime = Field(default_factory=datetime.now, description="更新时间")
class Config: model_config = ConfigDict()
json_encoders = {
datetime: lambda v: v.isoformat() @field_serializer('created_at', 'updated_at', when_used='json')
} def serialize_datetime(self, value: datetime) -> str:
return value.isoformat()
class AgentState(BaseModel): class AgentState(BaseModel):
@@ -30,10 +31,11 @@ class AgentState(BaseModel):
is_thinking: bool = Field(default=False, description="是否正在思考") is_thinking: bool = Field(default=False, description="是否正在思考")
last_activity: datetime = Field(default_factory=datetime.now, description="最后活动时间") last_activity: datetime = Field(default_factory=datetime.now, description="最后活动时间")
class Config: model_config = ConfigDict()
json_encoders = {
datetime: lambda v: v.isoformat() @field_serializer('last_activity', when_used='json')
} def serialize_datetime(self, value: datetime) -> str:
return value.isoformat()
class UserMessage(BaseModel): class UserMessage(BaseModel):
+3 -2
View File
@@ -1,7 +1,7 @@
from pathlib import Path from pathlib import Path
from typing import Optional, Dict, Any, List, Set, Callable from typing import Optional, Dict, Any, List, Set, Callable
from pydantic import BaseModel, Field, root_validator from pydantic import BaseModel, Field, model_validator
from app.schemas.message import MessageChannel from app.schemas.message import MessageChannel
from app.schemas.file import FileItem from app.schemas.file import FileItem
@@ -68,7 +68,8 @@ class AuthCredentials(ChainEventData):
channel: Optional[str] = Field(default=None, description="认证渠道") channel: Optional[str] = Field(default=None, description="认证渠道")
service: Optional[str] = Field(default=None, description="服务名称") service: Optional[str] = Field(default=None, description="服务名称")
@root_validator(pre=True) @model_validator(mode='before')
@classmethod
def check_fields_based_on_grant_type(cls, values): # noqa def check_fields_based_on_grant_type(cls, values): # noqa
grant_type = values.get("grant_type") grant_type = values.get("grant_type")
if not grant_type: if not grant_type:
+3 -5
View File
@@ -1,6 +1,6 @@
from typing import Optional, Any from typing import Optional, Any
from pydantic import BaseModel from pydantic import BaseModel, ConfigDict
class DownloadHistory(BaseModel): class DownloadHistory(BaseModel):
@@ -51,8 +51,7 @@ class DownloadHistory(BaseModel):
# 自定义剧集组 # 自定义剧集组
episode_group: Optional[str] = None episode_group: Optional[str] = None
class Config: model_config = ConfigDict(from_attributes=True)
orm_mode = True
class TransferHistory(BaseModel): class TransferHistory(BaseModel):
@@ -97,5 +96,4 @@ class TransferHistory(BaseModel):
# 日期 # 日期
date: Optional[str] = None date: Optional[str] = None
class Config: model_config = ConfigDict(from_attributes=True)
orm_mode = True
+2 -3
View File
@@ -1,7 +1,7 @@
from pathlib import Path from pathlib import Path
from typing import Optional, Dict, Union, List, Any from typing import Optional, Dict, Union, List, Any
from pydantic import BaseModel, Field from pydantic import BaseModel, Field, ConfigDict
from app.schemas.types import MediaType from app.schemas.types import MediaType
@@ -125,8 +125,7 @@ class MediaServerItem(BaseModel):
lst_mod_date: Optional[str] = None lst_mod_date: Optional[str] = None
user_state: Optional[MediaServerItemUserState] = None user_state: Optional[MediaServerItemUserState] = None
class Config: model_config = ConfigDict(from_attributes=True)
orm_mode = True
class MediaServerSeasonInfo(BaseModel): class MediaServerSeasonInfo(BaseModel):
+2 -2
View File
@@ -40,7 +40,7 @@ class CommingMessage(BaseModel):
""" """
转换为字典 转换为字典
""" """
items = self.dict() items = self.model_dump()
for k, v in items.items(): for k, v in items.items():
if isinstance(v, MessageChannel): if isinstance(v, MessageChannel):
items[k] = v.value items[k] = v.value
@@ -88,7 +88,7 @@ class Notification(BaseModel):
""" """
转换为字典 转换为字典
""" """
items = self.dict() items = self.model_dump()
for k, v in items.items(): for k, v in items.items():
if isinstance(v, MessageChannel) \ if isinstance(v, MessageChannel) \
or isinstance(v, NotificationType): or isinstance(v, NotificationType):
+3 -5
View File
@@ -1,6 +1,6 @@
from typing import Optional, Any, Union, Dict from typing import Optional, Any, Union, Dict
from pydantic import BaseModel, Field from pydantic import BaseModel, Field, ConfigDict
class Site(BaseModel): class Site(BaseModel):
@@ -47,8 +47,7 @@ class Site(BaseModel):
# 下载器 # 下载器
downloader: Optional[str] = None downloader: Optional[str] = None
class Config: model_config = ConfigDict(from_attributes=True)
orm_mode = True
class SiteStatistic(BaseModel): class SiteStatistic(BaseModel):
@@ -67,8 +66,7 @@ class SiteStatistic(BaseModel):
# 备注 # 备注
note: Optional[Any] = None note: Optional[Any] = None
class Config: model_config = ConfigDict(from_attributes=True)
orm_mode = True
class SiteUserData(BaseModel): class SiteUserData(BaseModel):
+2 -3
View File
@@ -1,6 +1,6 @@
from typing import Optional, List, Dict, Any from typing import Optional, List, Dict, Any
from pydantic import BaseModel, Field from pydantic import BaseModel, Field, ConfigDict
class Subscribe(BaseModel): class Subscribe(BaseModel):
@@ -76,8 +76,7 @@ class Subscribe(BaseModel):
# 剧集组 # 剧集组
episode_group: Optional[str] = None episode_group: Optional[str] = None
class Config: model_config = ConfigDict(from_attributes=True)
orm_mode = True
class SubscribeShare(BaseModel): class SubscribeShare(BaseModel):
+6 -6
View File
@@ -73,10 +73,10 @@ class TransferTask(BaseModel):
返回字典 返回字典
""" """
dicts = vars(self).copy() dicts = vars(self).copy()
dicts["fileitem"] = self.fileitem.dict() if self.fileitem else None dicts["fileitem"] = self.fileitem.model_dump() if self.fileitem else None
dicts["meta"] = self.meta.dict() if self.meta else None dicts["meta"] = self.meta.model_dump() if self.meta else None
dicts["mediainfo"] = self.mediainfo.dict() if self.mediainfo else None dicts["mediainfo"] = self.mediainfo.model_dump() if self.mediainfo else None
dicts["target_directory"] = self.target_directory.dict() if self.target_directory else None dicts["target_directory"] = self.target_directory.model_dump() if self.target_directory else None
return dicts return dicts
@@ -144,8 +144,8 @@ class TransferInfo(BaseModel):
返回字典 返回字典
""" """
dicts = vars(self).copy() dicts = vars(self).copy()
dicts["fileitem"] = self.fileitem.dict() if self.fileitem else None dicts["fileitem"] = self.fileitem.model_dump() if self.fileitem else None
dicts["target_item"] = self.target_item.dict() if self.target_item else None dicts["target_item"] = self.target_item.model_dump() if self.target_item else None
return dicts return dicts
+3 -5
View File
@@ -1,6 +1,6 @@
from typing import Optional from typing import Optional
from pydantic import BaseModel, Field from pydantic import BaseModel, Field, ConfigDict
# Shared properties # Shared properties
@@ -22,8 +22,7 @@ class UserBase(BaseModel):
# 个性化设置 # 个性化设置
settings: Optional[dict] = Field(default_factory=dict) settings: Optional[dict] = Field(default_factory=dict)
class Config: model_config = ConfigDict(from_attributes=True)
orm_mode = True
# Properties to receive via API on creation # Properties to receive via API on creation
@@ -48,8 +47,7 @@ class UserUpdate(UserBase):
class UserInDBBase(UserBase): class UserInDBBase(UserBase):
id: Optional[int] = None id: Optional[int] = None
class Config: model_config = ConfigDict(from_attributes=True)
orm_mode = True
# Additional properties to return via API # Additional properties to return via API
+3 -5
View File
@@ -1,6 +1,6 @@
from typing import Optional, List from typing import Optional, List
from pydantic import BaseModel, Field from pydantic import BaseModel, Field, ConfigDict
from app.schemas.context import Context, MediaInfo from app.schemas.context import Context, MediaInfo
from app.schemas.download import DownloadTask from app.schemas.download import DownloadTask
@@ -29,8 +29,7 @@ class Workflow(BaseModel):
add_time: Optional[str] = Field(default=None, description="创建时间") add_time: Optional[str] = Field(default=None, description="创建时间")
last_time: Optional[str] = Field(default=None, description="最后执行时间") last_time: Optional[str] = Field(default=None, description="最后执行时间")
class Config: model_config = ConfigDict(from_attributes=True)
orm_mode = True
class ActionParams(BaseModel): class ActionParams(BaseModel):
@@ -108,5 +107,4 @@ class WorkflowShare(BaseModel):
date: Optional[str] = Field(default=None, description="分享时间") date: Optional[str] = Field(default=None, description="分享时间")
count: Optional[int] = Field(default=0, description="复用人次") count: Optional[int] = Field(default=0, description="复用人次")
class Config: model_config = ConfigDict(from_attributes=True)
orm_mode = True
+1 -1
View File
@@ -44,7 +44,7 @@ class AddDownloadAction(BaseAction):
@classmethod @classmethod
@property @property
def data(cls) -> dict: # noqa def data(cls) -> dict: # noqa
return AddDownloadParams().dict() return AddDownloadParams().model_dump()
@property @property
def success(self) -> bool: def success(self) -> bool:
+2 -2
View File
@@ -37,7 +37,7 @@ class AddSubscribeAction(BaseAction):
@classmethod @classmethod
@property @property
def data(cls) -> dict: # noqa def data(cls) -> dict: # noqa
return AddSubscribeParams().dict() return AddSubscribeParams().model_dump()
@property @property
def success(self) -> bool: def success(self) -> bool:
@@ -57,7 +57,7 @@ class AddSubscribeAction(BaseAction):
logger.info(f"{media.title} {media.year} 已添加过订阅,跳过") logger.info(f"{media.title} {media.year} 已添加过订阅,跳过")
continue continue
mediainfo = MediaInfo() mediainfo = MediaInfo()
mediainfo.from_dict(media.dict()) mediainfo.from_dict(media.model_dump())
subscribechain = SubscribeChain() subscribechain = SubscribeChain()
if subscribechain.exists(mediainfo): if subscribechain.exists(mediainfo):
logger.info(f"{media.title} 已存在订阅") logger.info(f"{media.title} 已存在订阅")
+1 -1
View File
@@ -33,7 +33,7 @@ class FetchDownloadsAction(BaseAction):
@classmethod @classmethod
@property @property
def data(cls) -> dict: # noqa def data(cls) -> dict: # noqa
return FetchDownloadsParams().dict() return FetchDownloadsParams().model_dump()
@property @property
def success(self) -> bool: def success(self) -> bool:
+2 -2
View File
@@ -107,7 +107,7 @@ class FetchMediasAction(BaseAction):
if event and event.event_data: if event and event.event_data:
event_data: RecommendSourceEventData = event.event_data event_data: RecommendSourceEventData = event.event_data
if event_data.extra_sources: if event_data.extra_sources:
self.__inner_sources.extend([s.dict() for s in event_data.extra_sources]) self.__inner_sources.extend([s.model_dump() for s in event_data.extra_sources])
@classmethod @classmethod
@property @property
@@ -122,7 +122,7 @@ class FetchMediasAction(BaseAction):
@classmethod @classmethod
@property @property
def data(cls) -> dict: # noqa def data(cls) -> dict: # noqa
return FetchMediasParams().dict() return FetchMediasParams().model_dump()
@property @property
def success(self) -> bool: def success(self) -> bool:
+1 -1
View File
@@ -47,7 +47,7 @@ class FetchRssAction(BaseAction):
@classmethod @classmethod
@property @property
def data(cls) -> dict: # noqa def data(cls) -> dict: # noqa
return FetchRssParams().dict() return FetchRssParams().model_dump()
@property @property
def success(self) -> bool: def success(self) -> bool:
+1 -1
View File
@@ -46,7 +46,7 @@ class FetchTorrentsAction(BaseAction):
@classmethod @classmethod
@property @property
def data(cls) -> dict: # noqa def data(cls) -> dict: # noqa
return FetchTorrentsParams().dict() return FetchTorrentsParams().model_dump()
@property @property
def success(self) -> bool: def success(self) -> bool:
+1 -1
View File
@@ -39,7 +39,7 @@ class FilterMediasAction(BaseAction):
@classmethod @classmethod
@property @property
def data(cls) -> dict: # noqa def data(cls) -> dict: # noqa
return FilterMediasParams().dict() return FilterMediasParams().model_dump()
@property @property
def success(self) -> bool: def success(self) -> bool:
+1 -1
View File
@@ -44,7 +44,7 @@ class FilterTorrentsAction(BaseAction):
@classmethod @classmethod
@property @property
def data(cls) -> dict: # noqa def data(cls) -> dict: # noqa
return FilterTorrentsParams().dict() return FilterTorrentsParams().model_dump()
@property @property
def success(self) -> bool: def success(self) -> bool:
+1 -1
View File
@@ -37,7 +37,7 @@ class InvokePluginAction(BaseAction):
@classmethod @classmethod
@property @property
def data(cls) -> dict: # noqa def data(cls) -> dict: # noqa
return InvokePluginParams().dict() return InvokePluginParams().model_dump()
@property @property
def success(self) -> bool: def success(self) -> bool:
+1 -1
View File
@@ -42,7 +42,7 @@ class ScanFileAction(BaseAction):
@classmethod @classmethod
@property @property
def data(cls) -> dict: # noqa def data(cls) -> dict: # noqa
return ScanFileParams().dict() return ScanFileParams().model_dump()
@property @property
def success(self) -> bool: def success(self) -> bool:
+1 -1
View File
@@ -39,7 +39,7 @@ class ScrapeFileAction(BaseAction):
@classmethod @classmethod
@property @property
def data(cls) -> dict: # noqa def data(cls) -> dict: # noqa
return ScrapeFileParams().dict() return ScrapeFileParams().model_dump()
@property @property
def success(self) -> bool: def success(self) -> bool:
+1 -1
View File
@@ -29,7 +29,7 @@ class SendEventAction(BaseAction):
@classmethod @classmethod
@property @property
def data(cls) -> dict: # noqa def data(cls) -> dict: # noqa
return SendEventParams().dict() return SendEventParams().model_dump()
@property @property
def success(self) -> bool: def success(self) -> bool:
+1 -1
View File
@@ -36,7 +36,7 @@ class SendMessageAction(BaseAction):
@classmethod @classmethod
@property @property
def data(cls) -> dict: # noqa def data(cls) -> dict: # noqa
return SendMessageParams().dict() return SendMessageParams().model_dump()
@property @property
def success(self) -> bool: def success(self) -> bool:
+1 -1
View File
@@ -44,7 +44,7 @@ class TransferFileAction(BaseAction):
@classmethod @classmethod
@property @property
def data(cls) -> dict: # noqa def data(cls) -> dict: # noqa
return TransferFileParams().dict() return TransferFileParams().model_dump()
@property @property
def success(self) -> bool: def success(self) -> bool:
+2 -1
View File
@@ -1,5 +1,6 @@
Cython~=3.1.2 Cython~=3.1.2
pydantic~=1.10.22 pydantic>=2.0.0,<3.0.0
pydantic-settings>=2.0.0,<3.0.0
SQLAlchemy~=2.0.41 SQLAlchemy~=2.0.41
uvicorn~=0.34.3 uvicorn~=0.34.3
fastapi~=0.115.14 fastapi~=0.115.14