diff --git a/app/agent/memory/__init__.py b/app/agent/memory/__init__.py index 5d596426..dbca172d 100644 --- a/app/agent/memory/__init__.py +++ b/app/agent/memory/__init__.py @@ -235,7 +235,7 @@ class ConversationMemoryManager: # 保存到Redis,设置TTL自动过期 if settings.CACHE_BACKEND_TYPE == "redis": 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}" ttl = int(timedelta(days=settings.LLM_REDIS_MEMORY_RETENTION_DAYS).total_seconds()) await self.redis_helper.set( diff --git a/app/agent/prompt/__init__.py b/app/agent/prompt/__init__.py index a5df868e..e7ba0662 100644 --- a/app/agent/prompt/__init__.py +++ b/app/agent/prompt/__init__.py @@ -21,7 +21,7 @@ class PromptManager: if prompt_name in self.prompts_cache: return self.prompts_cache[prompt_name] - prompt_file = self.prompts_dir / "prompt" / prompt_name + prompt_file = self.prompts_dir / prompt_name try: with open(prompt_file, 'r', encoding='utf-8') as f: diff --git a/app/agent/tools/impl/query_downloads.py b/app/agent/tools/impl/query_downloads.py index 8706b541..ee0cea66 100644 --- a/app/agent/tools/impl/query_downloads.py +++ b/app/agent/tools/impl/query_downloads.py @@ -27,7 +27,7 @@ class QueryDownloadsTool(MoviePilotTool): continue filtered_downloads.append(dl) 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 "未找到相关下载任务。" except Exception as e: logger.error(f"查询下载失败: {e}", exc_info=True) diff --git a/app/agent/tools/impl/query_media_library.py b/app/agent/tools/impl/query_media_library.py index 25a6e780..d78dc9e7 100644 --- a/app/agent/tools/impl/query_media_library.py +++ b/app/agent/tools/impl/query_media_library.py @@ -3,7 +3,7 @@ import json 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.agent.tools.base import MoviePilotTool @@ -16,8 +16,8 @@ class QueryMediaLibraryTool(MoviePilotTool): title: Optional[str] = None) -> str: logger.info(f"执行工具: {self.name}, 参数: media_type={media_type}, title={title}") try: - media_oper = MediaOper() - medias = media_oper.list() + media_server_oper = MediaServerOper() + medias = media_server_oper.list() filtered_medias = [] for media in medias: if media_type != "all" and media.type != media_type: diff --git a/app/api/endpoints/dashboard.py b/app/api/endpoints/dashboard.py index c0a00c96..3063d94e 100644 --- a/app/api/endpoints/dashboard.py +++ b/app/api/endpoints/dashboard.py @@ -137,7 +137,7 @@ async def transfer(days: Optional[int] = 7, 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: """ 获取当前CPU使用率 @@ -145,7 +145,7 @@ def cpu(_: schemas.TokenPayload = Depends(verify_token)) -> Any: 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: """ 获取当前CPU使用率 API_TOKEN认证(?token=xxx) diff --git a/app/api/endpoints/download.py b/app/api/endpoints/download.py index af091582..54558a24 100644 --- a/app/api/endpoints/download.py +++ b/app/api/endpoints/download.py @@ -40,10 +40,10 @@ def download( metainfo = MetaInfo(title=torrent_in.title, subtitle=torrent_in.description) # 媒体信息 mediainfo = MediaInfo() - mediainfo.from_dict(media_in.dict()) + mediainfo.from_dict(media_in.model_dump()) # 种子信息 torrentinfo = TorrentInfo() - torrentinfo.from_dict(torrent_in.dict()) + torrentinfo.from_dict(torrent_in.model_dump()) # 手动下载始终使用选择的下载器 torrentinfo.site_downloader = downloader # 上下文 @@ -81,7 +81,7 @@ def add( return schemas.Response(success=False, message="无法识别媒体信息") # 种子信息 torrentinfo = TorrentInfo() - torrentinfo.from_dict(torrent_in.dict()) + torrentinfo.from_dict(torrent_in.model_dump()) # 上下文 context = Context( meta_info=metainfo, diff --git a/app/api/endpoints/mediaserver.py b/app/api/endpoints/mediaserver.py index 92db747e..490e9d88 100644 --- a/app/api/endpoints/mediaserver.py +++ b/app/api/endpoints/mediaserver.py @@ -79,7 +79,7 @@ def exists(media_in: schemas.MediaInfo, """ # 转化为媒体信息对象 mediainfo = MediaInfo() - mediainfo.from_dict(media_in.dict()) + mediainfo.from_dict(media_in.model_dump()) existsinfo: schemas.ExistMediaInfo = MediaServerChain().media_exists(mediainfo=mediainfo) if not existsinfo: return [] @@ -108,7 +108,7 @@ def not_exists(media_in: schemas.MediaInfo, meta.year = media_in.year # 转化为媒体信息对象 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) mediakey = mediainfo.tmdb_id or mediainfo.douban_id if mediainfo.type == MediaType.MOVIE: diff --git a/app/api/endpoints/message.py b/app/api/endpoints/message.py index 3ebec2f6..9ed9d5e5 100644 --- a/app/api/endpoints/message.py +++ b/app/api/endpoints/message.py @@ -132,7 +132,7 @@ async def subscribe(subscription: schemas.Subscription, _: schemas.TokenPayload """ 客户端webpush通知订阅 """ - subinfo = subscription.dict() + subinfo = subscription.model_dump() if subinfo not in global_vars.get_subscriptions(): global_vars.push_subscription(subinfo) logger.debug(f"通知订阅成功: {subinfo}") @@ -148,7 +148,7 @@ def send_notification(payload: schemas.SubscriptionMessage, _: schemas.TokenPayl try: webpush( subscription_info=sub, - data=json.dumps(payload.dict()), + data=json.dumps(payload.model_dump()), vapid_private_key=settings.VAPID.get("privateKey"), vapid_claims={ "sub": settings.VAPID.get("subject") diff --git a/app/api/endpoints/site.py b/app/api/endpoints/site.py index e4f15766..477c7bd1 100644 --- a/app/api/endpoints/site.py +++ b/app/api/endpoints/site.py @@ -67,7 +67,7 @@ async def add_site( site_in.name = site_info.get("name") site_in.id = None 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) # 通知站点更新 await eventmanager.async_send_event(EventType.SiteUpdated, { @@ -92,7 +92,7 @@ async def update_site( # 校正地址格式 _scheme, _netloc = StringUtils.get_url_netloc(site_in.url) 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, { "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: return schemas.Response(success=False, message="请输入认证站点和认证参数") 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() Scheduler().init_plugin_jobs() diff --git a/app/api/endpoints/subscribe.py b/app/api/endpoints/subscribe.py index 1e17faee..7d31491c 100644 --- a/app/api/endpoints/subscribe.py +++ b/app/api/endpoints/subscribe.py @@ -79,7 +79,7 @@ async def create_subscribe( # 订阅用户 subscribe_in.username = current_user.name # 转化为字典 - subscribe_dict = subscribe_in.dict() + subscribe_dict = subscribe_in.model_dump() if subscribe_in.id: subscribe_dict.pop("id", None) sid, message = await SubscribeChain().async_add(mtype=mtype, @@ -106,7 +106,7 @@ async def update_subscribe( return schemas.Response(success=False, message="订阅不存在") # 避免更新缺失集数 old_subscribe_dict = subscribe.to_dict() - subscribe_dict = subscribe_in.dict() + subscribe_dict = subscribe_in.model_dump() if not subscribe_in.lack_episode: # 没有缺失集数时,缺失集数清空,避免更新为0 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") for key in list(sub_dict.keys()): if not hasattr(schemas.Subscribe(), key): diff --git a/app/api/endpoints/user.py b/app/api/endpoints/user.py index a4133aba..fe6aa1f9 100644 --- a/app/api/endpoints/user.py +++ b/app/api/endpoints/user.py @@ -41,7 +41,7 @@ async def create_user( user = await current_user.async_get_by_name(db, name=user_in.name) if user: return schemas.Response(success=False, message="用户已存在") - user_info = user_in.dict() + user_info = user_in.model_dump() if user_info.get("password"): user_info["hashed_password"] = get_password_hash(user_info["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"): # 正则表达式匹配密码包含字母、数字、特殊字符中的至少两项 pattern = r'^(?![a-zA-Z]+$)(?!\d+$)(?![^\da-zA-Z\s]+$).{6,50}$' diff --git a/app/api/endpoints/workflow.py b/app/api/endpoints/workflow.py index a6854b2a..123cfa77 100644 --- a/app/api/endpoints/workflow.py +++ b/app/api/endpoints/workflow.py @@ -47,7 +47,7 @@ async def create_workflow(workflow: schemas.Workflow, workflow.state = "P" if not workflow.trigger_type: workflow.trigger_type = "timer" - workflow_obj = Workflow(**workflow.dict()) + workflow_obj = Workflow(**workflow.model_dump()) await workflow_obj.async_create(db) return schemas.Response(success=True, message="创建工作流成功") @@ -277,7 +277,7 @@ def update_workflow(workflow: schemas.Workflow, return schemas.Response(success=False, message="工作流不存在") if not wf.trigger_type: workflow.trigger_type = "timer" - wf.update(db, workflow.dict()) + wf.update(db, workflow.model_dump()) # 更新后的工作流对象 updated_workflow = workflow_oper.get(workflow.id) # 更新定时任务 diff --git a/app/chain/__init__.py b/app/chain/__init__.py index eac918aa..be87f6e4 100644 --- a/app/chain/__init__.py +++ b/app/chain/__init__.py @@ -854,7 +854,7 @@ class ChainBase(metaclass=ABCMeta): torrentinfo=torrentinfo, transferinfo=transferinfo, **kwargs) # 保存消息 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: # 消息隔离设置 @@ -901,12 +901,12 @@ class ChainBase(metaclass=ABCMeta): break # 按设定发送 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) if not send_orignal: 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, immediately=True if message.userid else False) @@ -933,7 +933,7 @@ class ChainBase(metaclass=ABCMeta): torrentinfo=torrentinfo, transferinfo=transferinfo, **kwargs) # 保存消息 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: # 消息隔离设置 @@ -980,13 +980,13 @@ class ChainBase(metaclass=ABCMeta): break # 按设定发送 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) if not send_orignal: return # 发送消息事件 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, immediately=True if message.userid else False) @@ -1000,7 +1000,7 @@ class ChainBase(metaclass=ABCMeta): """ note_list = [media.to_dict() for media in medias] 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, 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] 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, immediately=True if message.userid else False) diff --git a/app/chain/download.py b/app/chain/download.py index a751b2f3..db2c4f25 100644 --- a/app/chain/download.py +++ b/app/chain/download.py @@ -994,7 +994,7 @@ class DownloadChain(ChainBase): # 发出下载任务删除事件,如需处理辅种,可监听该事件 self.eventmanager.send_event(EventType.DownloadDeleted, { "hash": hash_str, - "torrents": [torrent.dict() for torrent in torrents] + "torrents": [torrent.model_dump() for torrent in torrents] }) else: logger.info(f"没有在下载器中查询到 {hash_str} 对应的下载任务") diff --git a/app/chain/mediaserver.py b/app/chain/mediaserver.py index 7d415192..399cfee4 100644 --- a/app/chain/mediaserver.py +++ b/app/chain/mediaserver.py @@ -167,7 +167,7 @@ class MediaServerChain(ChainBase): for episode in espisodes_info: seasoninfo[episode.season] = episode.episodes # 插入数据 - item_dict = item.dict() + item_dict = item.model_dump() item_dict["seasoninfo"] = seasoninfo item_dict["item_type"] = item_type dboper.add(**item_dict) diff --git a/app/chain/site.py b/app/chain/site.py index d13182ff..7ebc09ae 100644 --- a/app/chain/site.py +++ b/app/chain/site.py @@ -56,7 +56,7 @@ class SiteChain(ChainBase): if userdata: SiteOper().update_userdata(domain=StringUtils.get_url_domain(site.get("domain")), name=site.get("name"), - payload=userdata.dict()) + payload=userdata.model_dump()) # 发送事件 eventmanager.send_event(EventType.SiteRefreshed, { "site_id": site.get("id") diff --git a/app/chain/workflow.py b/app/chain/workflow.py index 1248205f..638cff09 100644 --- a/app/chain/workflow.py +++ b/app/chain/workflow.py @@ -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): setattr(self.context, key, value) diff --git a/app/core/config.py b/app/core/config.py index 83b1ed66..52f43eee 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -11,7 +11,8 @@ from typing import Any, Dict, List, Optional, Tuple, Type from urllib.parse import urlparse 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.schemas import MediaType @@ -49,8 +50,7 @@ class ConfigModel(BaseModel): Pydantic 配置模型,描述所有配置项及其类型和默认值 """ - class Config: - extra = "ignore" # 忽略未定义的配置项 + model_config = ConfigDict(extra="ignore") # 忽略未定义的配置项 # ==================== 基础应用配置 ==================== # 项目名称 @@ -92,7 +92,7 @@ class ConfigModel(BaseModel): # 超级管理员初始用户名 SUPERUSER: str = "admin" # 超级管理员初始密码 - SUPERUSER_PASSWORD: str = None + SUPERUSER_PASSWORD: Optional[str] = None # 辅助认证,允许通过外部服务进行认证、单点登录以及自动创建用户 AUXILIARY_AUTH_ENABLE: bool = False # API密钥,需要更换 @@ -398,9 +398,9 @@ class ConfigModel(BaseModel): # ==================== 存储配置 ==================== # 对rclone进行快照对比时,是否检查文件夹的修改时间 - RCLONE_SNAPSHOT_CHECK_FOLDER_MODTIME = True + RCLONE_SNAPSHOT_CHECK_FOLDER_MODTIME: bool = True # 对OpenList进行快照对比时,是否检查文件夹的修改时间 - OPENLIST_SNAPSHOT_CHECK_FOLDER_MODTIME = True + OPENLIST_SNAPSHOT_CHECK_FOLDER_MODTIME: bool = True # ==================== Docker配置 ==================== # Docker Client API地址 @@ -438,10 +438,11 @@ class Settings(BaseSettings, ConfigModel, LogConfigModel): 系统配置类 """ - class Config: - case_sensitive = True - env_file = SystemUtils.get_env_path() - env_file_encoding = "utf-8" + model_config = ConfigDict( + case_sensitive=True, + env_file=SystemUtils.get_env_path(), + env_file_encoding="utf-8" + ) def __init__(self, **kwargs): super().__init__(**kwargs) @@ -538,19 +539,48 @@ class Settings(BaseSettings, ConfigModel, LogConfigModel): f"配置项 '{field_name}' 的值 '{value}' 无法转换成正确的类型,使用默认值 '{default}',错误信息: {e}") return default, True - @validator('*', pre=True, always=True) - def generic_type_validator(cls, value: Any, field): # noqa + @model_validator(mode='before') + @classmethod + def generic_type_validator(cls, data: Any): # noqa """ 通用校验器,尝试将配置值转换为期望的类型 """ - if field.name == "API_TOKEN": - converted_value, needs_update = cls.validate_api_token(value, value) - else: - converted_value, needs_update = cls.generic_type_converter(value, value, field.type_, field.default, - field.name) - if needs_update: - cls.update_env_config(field, value, converted_value) - return converted_value + if not isinstance(data, dict): + return data + + # 处理 API_TOKEN 特殊验证 + if 'API_TOKEN' in data: + converted_value, needs_update = cls.validate_api_token(data['API_TOKEN'], data['API_TOKEN']) + if needs_update: + 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 def update_env_config(field: Any, original_value: Any, converted_value: Any) -> Tuple[bool, str]: diff --git a/app/db/transferhistory_oper.py b/app/db/transferhistory_oper.py index 02e1a349..87c753a1 100644 --- a/app/db/transferhistory_oper.py +++ b/app/db/transferhistory_oper.py @@ -128,10 +128,10 @@ class TransferHistoryOper(DbOper): self.add_force( src=fileitem.path, 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_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, type=mediainfo.type.value, category=mediainfo.category, @@ -159,10 +159,10 @@ class TransferHistoryOper(DbOper): his = self.add_force( src=fileitem.path, 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_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, type=mediainfo.type.value, category=mediainfo.category, @@ -188,7 +188,7 @@ class TransferHistoryOper(DbOper): year=meta.year, src=fileitem.path, src_storage=fileitem.storage, - src_fileitem=fileitem.dict(), + src_fileitem=fileitem.model_dump(), mode=mode, seasons=meta.season, episodes=meta.episode, diff --git a/app/helper/storage.py b/app/helper/storage.py index ce6810f1..e9cff447 100644 --- a/app/helper/storage.py +++ b/app/helper/storage.py @@ -47,7 +47,7 @@ class StorageHelper: if s.type == storage: s.config = conf 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): """ @@ -68,7 +68,7 @@ class StorageHelper: name=name, 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): """ @@ -79,4 +79,4 @@ class StorageHelper: if s.type == storage: s.config = {} break - SystemConfigOper().set(SystemConfigKey.Storages, [s.dict() for s in storagies]) + SystemConfigOper().set(SystemConfigKey.Storages, [s.model_dump() for s in storagies]) diff --git a/app/log.py b/app/log.py index 1f212f44..c4aed1d2 100644 --- a/app/log.py +++ b/app/log.py @@ -11,7 +11,8 @@ from pathlib import Path from typing import Dict, Any, Optional import click -from pydantic import BaseSettings, BaseModel +from pydantic import BaseModel, ConfigDict +from pydantic_settings import BaseSettings from app.utils.system import SystemUtils @@ -21,8 +22,7 @@ class LogConfigModel(BaseModel): Pydantic 配置模型,描述所有配置项及其类型和默认值 """ - class Config: - extra = "ignore" # 忽略未定义的配置项 + model_config = ConfigDict(extra="ignore") # 忽略未定义的配置项 # 配置文件目录 CONFIG_DIR: Optional[str] = None @@ -71,10 +71,11 @@ class LogSettings(BaseSettings, LogConfigModel): """ return self.LOG_MAX_FILE_SIZE * 1024 * 1024 - class Config: - case_sensitive = True - env_file = SystemUtils.get_env_path() - env_file_encoding = "utf-8" + model_config = ConfigDict( + case_sensitive=True, + env_file=SystemUtils.get_env_path(), + env_file_encoding="utf-8" + ) # 实例化日志设置 diff --git a/app/modules/filter/__init__.py b/app/modules/filter/__init__.py index 0f9b43bd..406aefc3 100644 --- a/app/modules/filter/__init__.py +++ b/app/modules/filter/__init__.py @@ -154,7 +154,7 @@ class FilterModule(_ModuleBase): custom_rules = self.rulehelper.get_custom_rules() for rule in custom_rules: logger.info(f"加载自定义规则 {rule.id} - {rule.name}") - self.rule_set[rule.id] = rule.dict() + self.rule_set[rule.id] = rule.model_dump() @staticmethod def get_name() -> str: diff --git a/app/schemas/agent.py b/app/schemas/agent.py index 6fa52528..38d34e63 100644 --- a/app/schemas/agent.py +++ b/app/schemas/agent.py @@ -2,7 +2,7 @@ from datetime import datetime from typing import Dict, List, Optional, Any -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, ConfigDict, field_serializer class ConversationMemory(BaseModel): @@ -16,10 +16,11 @@ class ConversationMemory(BaseModel): created_at: datetime = Field(default_factory=datetime.now, description="创建时间") updated_at: datetime = Field(default_factory=datetime.now, description="更新时间") - class Config: - json_encoders = { - datetime: lambda v: v.isoformat() - } + model_config = ConfigDict() + + @field_serializer('created_at', 'updated_at', when_used='json') + def serialize_datetime(self, value: datetime) -> str: + return value.isoformat() class AgentState(BaseModel): @@ -30,10 +31,11 @@ class AgentState(BaseModel): is_thinking: bool = Field(default=False, description="是否正在思考") last_activity: datetime = Field(default_factory=datetime.now, description="最后活动时间") - class Config: - json_encoders = { - datetime: lambda v: v.isoformat() - } + model_config = ConfigDict() + + @field_serializer('last_activity', when_used='json') + def serialize_datetime(self, value: datetime) -> str: + return value.isoformat() class UserMessage(BaseModel): diff --git a/app/schemas/event.py b/app/schemas/event.py index 30fd0616..8c2fecab 100644 --- a/app/schemas/event.py +++ b/app/schemas/event.py @@ -1,7 +1,7 @@ from pathlib import Path 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.file import FileItem @@ -68,7 +68,8 @@ class AuthCredentials(ChainEventData): channel: 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 grant_type = values.get("grant_type") if not grant_type: diff --git a/app/schemas/history.py b/app/schemas/history.py index 193b13b8..f6d489ed 100644 --- a/app/schemas/history.py +++ b/app/schemas/history.py @@ -1,6 +1,6 @@ from typing import Optional, Any -from pydantic import BaseModel +from pydantic import BaseModel, ConfigDict class DownloadHistory(BaseModel): @@ -51,8 +51,7 @@ class DownloadHistory(BaseModel): # 自定义剧集组 episode_group: Optional[str] = None - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) class TransferHistory(BaseModel): @@ -97,5 +96,4 @@ class TransferHistory(BaseModel): # 日期 date: Optional[str] = None - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) diff --git a/app/schemas/mediaserver.py b/app/schemas/mediaserver.py index 985bcba7..a5427228 100644 --- a/app/schemas/mediaserver.py +++ b/app/schemas/mediaserver.py @@ -1,7 +1,7 @@ from pathlib import Path 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 @@ -125,8 +125,7 @@ class MediaServerItem(BaseModel): lst_mod_date: Optional[str] = None user_state: Optional[MediaServerItemUserState] = None - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) class MediaServerSeasonInfo(BaseModel): diff --git a/app/schemas/message.py b/app/schemas/message.py index cf200877..f3a66aec 100644 --- a/app/schemas/message.py +++ b/app/schemas/message.py @@ -40,7 +40,7 @@ class CommingMessage(BaseModel): """ 转换为字典 """ - items = self.dict() + items = self.model_dump() for k, v in items.items(): if isinstance(v, MessageChannel): items[k] = v.value @@ -88,7 +88,7 @@ class Notification(BaseModel): """ 转换为字典 """ - items = self.dict() + items = self.model_dump() for k, v in items.items(): if isinstance(v, MessageChannel) \ or isinstance(v, NotificationType): diff --git a/app/schemas/site.py b/app/schemas/site.py index aded2a62..4478eb65 100644 --- a/app/schemas/site.py +++ b/app/schemas/site.py @@ -1,6 +1,6 @@ from typing import Optional, Any, Union, Dict -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, ConfigDict class Site(BaseModel): @@ -47,8 +47,7 @@ class Site(BaseModel): # 下载器 downloader: Optional[str] = None - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) class SiteStatistic(BaseModel): @@ -67,8 +66,7 @@ class SiteStatistic(BaseModel): # 备注 note: Optional[Any] = None - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) class SiteUserData(BaseModel): diff --git a/app/schemas/subscribe.py b/app/schemas/subscribe.py index f482e9cc..5e66a24c 100644 --- a/app/schemas/subscribe.py +++ b/app/schemas/subscribe.py @@ -1,6 +1,6 @@ from typing import Optional, List, Dict, Any -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, ConfigDict class Subscribe(BaseModel): @@ -76,8 +76,7 @@ class Subscribe(BaseModel): # 剧集组 episode_group: Optional[str] = None - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) class SubscribeShare(BaseModel): diff --git a/app/schemas/transfer.py b/app/schemas/transfer.py index 85b35f89..b12f16c7 100644 --- a/app/schemas/transfer.py +++ b/app/schemas/transfer.py @@ -73,10 +73,10 @@ class TransferTask(BaseModel): 返回字典 """ dicts = vars(self).copy() - dicts["fileitem"] = self.fileitem.dict() if self.fileitem else None - dicts["meta"] = self.meta.dict() if self.meta else None - dicts["mediainfo"] = self.mediainfo.dict() if self.mediainfo else None - dicts["target_directory"] = self.target_directory.dict() if self.target_directory else None + dicts["fileitem"] = self.fileitem.model_dump() if self.fileitem else None + dicts["meta"] = self.meta.model_dump() if self.meta else None + dicts["mediainfo"] = self.mediainfo.model_dump() if self.mediainfo else None + dicts["target_directory"] = self.target_directory.model_dump() if self.target_directory else None return dicts @@ -144,8 +144,8 @@ class TransferInfo(BaseModel): 返回字典 """ dicts = vars(self).copy() - dicts["fileitem"] = self.fileitem.dict() if self.fileitem else None - dicts["target_item"] = self.target_item.dict() if self.target_item else None + dicts["fileitem"] = self.fileitem.model_dump() if self.fileitem else None + dicts["target_item"] = self.target_item.model_dump() if self.target_item else None return dicts diff --git a/app/schemas/user.py b/app/schemas/user.py index a42777c6..8125cb14 100644 --- a/app/schemas/user.py +++ b/app/schemas/user.py @@ -1,6 +1,6 @@ from typing import Optional -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, ConfigDict # Shared properties @@ -22,8 +22,7 @@ class UserBase(BaseModel): # 个性化设置 settings: Optional[dict] = Field(default_factory=dict) - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) # Properties to receive via API on creation @@ -48,8 +47,7 @@ class UserUpdate(UserBase): class UserInDBBase(UserBase): id: Optional[int] = None - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) # Additional properties to return via API diff --git a/app/schemas/workflow.py b/app/schemas/workflow.py index 57d5c3b5..52f7401b 100644 --- a/app/schemas/workflow.py +++ b/app/schemas/workflow.py @@ -1,6 +1,6 @@ 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.download import DownloadTask @@ -29,8 +29,7 @@ class Workflow(BaseModel): add_time: Optional[str] = Field(default=None, description="创建时间") last_time: Optional[str] = Field(default=None, description="最后执行时间") - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) class ActionParams(BaseModel): @@ -108,5 +107,4 @@ class WorkflowShare(BaseModel): date: Optional[str] = Field(default=None, description="分享时间") count: Optional[int] = Field(default=0, description="复用人次") - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) diff --git a/app/workflow/actions/add_download.py b/app/workflow/actions/add_download.py index 425875a7..73def935 100644 --- a/app/workflow/actions/add_download.py +++ b/app/workflow/actions/add_download.py @@ -44,7 +44,7 @@ class AddDownloadAction(BaseAction): @classmethod @property def data(cls) -> dict: # noqa - return AddDownloadParams().dict() + return AddDownloadParams().model_dump() @property def success(self) -> bool: diff --git a/app/workflow/actions/add_subscribe.py b/app/workflow/actions/add_subscribe.py index 2d022d76..77ed32c0 100644 --- a/app/workflow/actions/add_subscribe.py +++ b/app/workflow/actions/add_subscribe.py @@ -37,7 +37,7 @@ class AddSubscribeAction(BaseAction): @classmethod @property def data(cls) -> dict: # noqa - return AddSubscribeParams().dict() + return AddSubscribeParams().model_dump() @property def success(self) -> bool: @@ -57,7 +57,7 @@ class AddSubscribeAction(BaseAction): logger.info(f"{media.title} {media.year} 已添加过订阅,跳过") continue mediainfo = MediaInfo() - mediainfo.from_dict(media.dict()) + mediainfo.from_dict(media.model_dump()) subscribechain = SubscribeChain() if subscribechain.exists(mediainfo): logger.info(f"{media.title} 已存在订阅") diff --git a/app/workflow/actions/fetch_downloads.py b/app/workflow/actions/fetch_downloads.py index ab12a04f..9a380fdf 100644 --- a/app/workflow/actions/fetch_downloads.py +++ b/app/workflow/actions/fetch_downloads.py @@ -33,7 +33,7 @@ class FetchDownloadsAction(BaseAction): @classmethod @property def data(cls) -> dict: # noqa - return FetchDownloadsParams().dict() + return FetchDownloadsParams().model_dump() @property def success(self) -> bool: diff --git a/app/workflow/actions/fetch_medias.py b/app/workflow/actions/fetch_medias.py index 174e022a..ff6cc1f4 100644 --- a/app/workflow/actions/fetch_medias.py +++ b/app/workflow/actions/fetch_medias.py @@ -107,7 +107,7 @@ class FetchMediasAction(BaseAction): if event and event.event_data: event_data: RecommendSourceEventData = event.event_data 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 @property @@ -122,7 +122,7 @@ class FetchMediasAction(BaseAction): @classmethod @property def data(cls) -> dict: # noqa - return FetchMediasParams().dict() + return FetchMediasParams().model_dump() @property def success(self) -> bool: diff --git a/app/workflow/actions/fetch_rss.py b/app/workflow/actions/fetch_rss.py index a0b299fa..af6e6e3f 100644 --- a/app/workflow/actions/fetch_rss.py +++ b/app/workflow/actions/fetch_rss.py @@ -47,7 +47,7 @@ class FetchRssAction(BaseAction): @classmethod @property def data(cls) -> dict: # noqa - return FetchRssParams().dict() + return FetchRssParams().model_dump() @property def success(self) -> bool: diff --git a/app/workflow/actions/fetch_torrents.py b/app/workflow/actions/fetch_torrents.py index 64f1db53..e4d0806a 100644 --- a/app/workflow/actions/fetch_torrents.py +++ b/app/workflow/actions/fetch_torrents.py @@ -46,7 +46,7 @@ class FetchTorrentsAction(BaseAction): @classmethod @property def data(cls) -> dict: # noqa - return FetchTorrentsParams().dict() + return FetchTorrentsParams().model_dump() @property def success(self) -> bool: diff --git a/app/workflow/actions/filter_medias.py b/app/workflow/actions/filter_medias.py index 4f1ac90c..47c25868 100644 --- a/app/workflow/actions/filter_medias.py +++ b/app/workflow/actions/filter_medias.py @@ -39,7 +39,7 @@ class FilterMediasAction(BaseAction): @classmethod @property def data(cls) -> dict: # noqa - return FilterMediasParams().dict() + return FilterMediasParams().model_dump() @property def success(self) -> bool: diff --git a/app/workflow/actions/filter_torrents.py b/app/workflow/actions/filter_torrents.py index ed59443f..23eed7e4 100644 --- a/app/workflow/actions/filter_torrents.py +++ b/app/workflow/actions/filter_torrents.py @@ -44,7 +44,7 @@ class FilterTorrentsAction(BaseAction): @classmethod @property def data(cls) -> dict: # noqa - return FilterTorrentsParams().dict() + return FilterTorrentsParams().model_dump() @property def success(self) -> bool: diff --git a/app/workflow/actions/invoke_plugin.py b/app/workflow/actions/invoke_plugin.py index 63b26c7e..ce50a163 100644 --- a/app/workflow/actions/invoke_plugin.py +++ b/app/workflow/actions/invoke_plugin.py @@ -37,7 +37,7 @@ class InvokePluginAction(BaseAction): @classmethod @property def data(cls) -> dict: # noqa - return InvokePluginParams().dict() + return InvokePluginParams().model_dump() @property def success(self) -> bool: diff --git a/app/workflow/actions/scan_file.py b/app/workflow/actions/scan_file.py index 859fcb49..c170ed74 100644 --- a/app/workflow/actions/scan_file.py +++ b/app/workflow/actions/scan_file.py @@ -42,7 +42,7 @@ class ScanFileAction(BaseAction): @classmethod @property def data(cls) -> dict: # noqa - return ScanFileParams().dict() + return ScanFileParams().model_dump() @property def success(self) -> bool: diff --git a/app/workflow/actions/scrape_file.py b/app/workflow/actions/scrape_file.py index d0ed15ea..d06cddd4 100644 --- a/app/workflow/actions/scrape_file.py +++ b/app/workflow/actions/scrape_file.py @@ -39,7 +39,7 @@ class ScrapeFileAction(BaseAction): @classmethod @property def data(cls) -> dict: # noqa - return ScrapeFileParams().dict() + return ScrapeFileParams().model_dump() @property def success(self) -> bool: diff --git a/app/workflow/actions/send_event.py b/app/workflow/actions/send_event.py index d71a5b57..7d96e6f0 100644 --- a/app/workflow/actions/send_event.py +++ b/app/workflow/actions/send_event.py @@ -29,7 +29,7 @@ class SendEventAction(BaseAction): @classmethod @property def data(cls) -> dict: # noqa - return SendEventParams().dict() + return SendEventParams().model_dump() @property def success(self) -> bool: diff --git a/app/workflow/actions/send_message.py b/app/workflow/actions/send_message.py index 4c046eb5..2bf0bda1 100644 --- a/app/workflow/actions/send_message.py +++ b/app/workflow/actions/send_message.py @@ -36,7 +36,7 @@ class SendMessageAction(BaseAction): @classmethod @property def data(cls) -> dict: # noqa - return SendMessageParams().dict() + return SendMessageParams().model_dump() @property def success(self) -> bool: diff --git a/app/workflow/actions/transfer_file.py b/app/workflow/actions/transfer_file.py index e1cfaaa6..08e95991 100644 --- a/app/workflow/actions/transfer_file.py +++ b/app/workflow/actions/transfer_file.py @@ -44,7 +44,7 @@ class TransferFileAction(BaseAction): @classmethod @property def data(cls) -> dict: # noqa - return TransferFileParams().dict() + return TransferFileParams().model_dump() @property def success(self) -> bool: diff --git a/requirements.in b/requirements.in index 2f28ceba..dace6e0f 100644 --- a/requirements.in +++ b/requirements.in @@ -1,5 +1,6 @@ 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 uvicorn~=0.34.3 fastapi~=0.115.14