mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-04-28 20:52:18 +08:00
fix pydantic
This commit is contained in:
@@ -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(
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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}$'
|
||||
|
||||
@@ -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)
|
||||
# 更新定时任务
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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} 对应的下载任务")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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]:
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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])
|
||||
|
||||
15
app/log.py
15
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"
|
||||
)
|
||||
|
||||
|
||||
# 实例化日志设置
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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} 已存在订阅")
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user