fix:修复代码兼容性写法

This commit is contained in:
jxxghp
2025-03-23 12:10:21 +08:00
parent 79411a7350
commit 2f38b3040d
27 changed files with 242 additions and 145 deletions

2
.gitignore vendored
View File

@@ -1,5 +1,7 @@
.idea/ .idea/
*.c *.c
*.so
*.pyd
build/ build/
dist/ dist/
nginx/ nginx/

View File

@@ -1,5 +1,5 @@
from pathlib import Path from pathlib import Path
from typing import Any, List, Optional from typing import Any, List, Optional, Annotated
from fastapi import APIRouter, Depends from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
@@ -37,7 +37,7 @@ def statistic(name: str = None, _: schemas.TokenPayload = Depends(verify_token))
@router.get("/statistic2", summary="媒体数量统计API_TOKEN", response_model=schemas.Statistic) @router.get("/statistic2", summary="媒体数量统计API_TOKEN", response_model=schemas.Statistic)
def statistic2(_: str = Depends(verify_apitoken)) -> Any: def statistic2(_: Annotated[str, Depends(verify_apitoken)]) -> Any:
""" """
查询媒体数量统计信息 API_TOKEN认证?token=xxx 查询媒体数量统计信息 API_TOKEN认证?token=xxx
""" """
@@ -66,7 +66,7 @@ def storage(_: schemas.TokenPayload = Depends(verify_token)) -> Any:
@router.get("/storage2", summary="本地存储空间API_TOKEN", response_model=schemas.Storage) @router.get("/storage2", summary="本地存储空间API_TOKEN", response_model=schemas.Storage)
def storage2(_: str = Depends(verify_apitoken)) -> Any: def storage2(_: Annotated[str, Depends(verify_apitoken)]) -> Any:
""" """
查询本地存储空间信息 API_TOKEN认证?token=xxx 查询本地存储空间信息 API_TOKEN认证?token=xxx
""" """
@@ -103,7 +103,7 @@ def downloader(name: str = None, _: schemas.TokenPayload = Depends(verify_token)
@router.get("/downloader2", summary="下载器信息API_TOKEN", response_model=schemas.DownloaderInfo) @router.get("/downloader2", summary="下载器信息API_TOKEN", response_model=schemas.DownloaderInfo)
def downloader2(_: str = Depends(verify_apitoken)) -> Any: def downloader2(_: Annotated[str, Depends(verify_apitoken)]) -> Any:
""" """
查询下载器信息 API_TOKEN认证?token=xxx 查询下载器信息 API_TOKEN认证?token=xxx
""" """
@@ -119,7 +119,7 @@ def schedule(_: schemas.TokenPayload = Depends(verify_token)) -> Any:
@router.get("/schedule2", summary="后台服务API_TOKEN", response_model=List[schemas.ScheduleInfo]) @router.get("/schedule2", summary="后台服务API_TOKEN", response_model=List[schemas.ScheduleInfo])
def schedule2(_: str = Depends(verify_apitoken)) -> Any: def schedule2(_: Annotated[str, Depends(verify_apitoken)]) -> Any:
""" """
查询下载器信息 API_TOKEN认证?token=xxx 查询下载器信息 API_TOKEN认证?token=xxx
""" """
@@ -145,7 +145,7 @@ def cpu(_: schemas.TokenPayload = Depends(verify_token)) -> Any:
@router.get("/cpu2", summary="获取当前CPU使用率API_TOKEN", response_model=int) @router.get("/cpu2", summary="获取当前CPU使用率API_TOKEN", response_model=int)
def cpu2(_: str = Depends(verify_apitoken)) -> Any: def cpu2(_: Annotated[str, Depends(verify_apitoken)]) -> Any:
""" """
获取当前CPU使用率 API_TOKEN认证?token=xxx 获取当前CPU使用率 API_TOKEN认证?token=xxx
""" """
@@ -161,7 +161,7 @@ def memory(_: schemas.TokenPayload = Depends(verify_token)) -> Any:
@router.get("/memory2", summary="获取当前内存使用量和使用率API_TOKEN", response_model=List[int]) @router.get("/memory2", summary="获取当前内存使用量和使用率API_TOKEN", response_model=List[int])
def memory2(_: str = Depends(verify_apitoken)) -> Any: def memory2(_: Annotated[str, Depends(verify_apitoken)]) -> Any:
""" """
获取当前内存使用率 API_TOKEN认证?token=xxx 获取当前内存使用率 API_TOKEN认证?token=xxx
""" """

View File

@@ -1,4 +1,4 @@
from typing import Any, List from typing import Any, List, Annotated
from fastapi import APIRouter, Depends, Body from fastapi import APIRouter, Depends, Body
@@ -30,8 +30,8 @@ def current(
def download( def download(
media_in: schemas.MediaInfo, media_in: schemas.MediaInfo,
torrent_in: schemas.TorrentInfo, torrent_in: schemas.TorrentInfo,
downloader: str = Body(None), downloader: Annotated[str | None, Body()] = None,
save_path: str = Body(None), save_path: Annotated[str | None, Body()] = None,
current_user: User = Depends(get_current_active_user)) -> Any: current_user: User = Depends(get_current_active_user)) -> Any:
""" """
添加下载任务(含媒体信息) 添加下载任务(含媒体信息)
@@ -62,8 +62,8 @@ def download(
@router.post("/add", summary="添加下载(不含媒体信息)", response_model=schemas.Response) @router.post("/add", summary="添加下载(不含媒体信息)", response_model=schemas.Response)
def add( def add(
torrent_in: schemas.TorrentInfo, torrent_in: schemas.TorrentInfo,
downloader: str = Body(None), downloader: Annotated[str | None, Body()] = None,
save_path: str = Body(None), save_path: Annotated[str | None, Body()] = None,
current_user: User = Depends(get_current_active_user)) -> Any: current_user: User = Depends(get_current_active_user)) -> Any:
""" """
添加下载任务(不含媒体信息) 添加下载任务(不含媒体信息)

View File

@@ -1,5 +1,5 @@
from datetime import timedelta from datetime import timedelta
from typing import Any, List from typing import Any, List, Annotated
from fastapi import APIRouter, Depends, Form, HTTPException from fastapi import APIRouter, Depends, Form, HTTPException
from fastapi.security import OAuth2PasswordRequestForm from fastapi.security import OAuth2PasswordRequestForm
@@ -18,8 +18,8 @@ router = APIRouter()
@router.post("/access-token", summary="获取token", response_model=schemas.Token) @router.post("/access-token", summary="获取token", response_model=schemas.Token)
def login_access_token( def login_access_token(
form_data: OAuth2PasswordRequestForm = Depends(), form_data: Annotated[OAuth2PasswordRequestForm, Depends()],
otp_password: str = Form(None) otp_password: Annotated[str | None, Form()] = None
) -> Any: ) -> Any:
""" """
获取认证Token 获取认证Token

View File

@@ -1,5 +1,5 @@
from pathlib import Path from pathlib import Path
from typing import List, Any, Union from typing import List, Any, Union, Annotated
from fastapi import APIRouter, Depends from fastapi import APIRouter, Depends
@@ -33,9 +33,10 @@ def recognize(title: str,
@router.get("/recognize2", summary="识别种子媒体信息API_TOKEN", response_model=schemas.Context) @router.get("/recognize2", summary="识别种子媒体信息API_TOKEN", response_model=schemas.Context)
def recognize2(title: str, def recognize2(_: Annotated[str, Depends(verify_apitoken)],
subtitle: str = None, title: str,
_: str = Depends(verify_apitoken)) -> Any: subtitle: str = None
) -> Any:
""" """
根据标题、副标题识别媒体信息 API_TOKEN认证?token=xxx 根据标题、副标题识别媒体信息 API_TOKEN认证?token=xxx
""" """
@@ -58,7 +59,7 @@ def recognize_file(path: str,
@router.get("/recognize_file2", summary="识别文件媒体信息API_TOKEN", response_model=schemas.Context) @router.get("/recognize_file2", summary="识别文件媒体信息API_TOKEN", response_model=schemas.Context)
def recognize_file2(path: str, def recognize_file2(path: str,
_: str = Depends(verify_apitoken)) -> Any: _: Annotated[str, Depends(verify_apitoken)]) -> Any:
""" """
根据文件路径识别媒体信息 API_TOKEN认证?token=xxx 根据文件路径识别媒体信息 API_TOKEN认证?token=xxx
""" """

View File

@@ -44,7 +44,7 @@ def play_item(itemid: str, _: schemas.TokenPayload = Depends(verify_token)) -> s
@router.get("/exists", summary="查询本地是否存在(数据库)", response_model=schemas.Response) @router.get("/exists", summary="查询本地是否存在(数据库)", response_model=schemas.Response)
def exists_local(title: str = None, def exists_local(title: str = None,
year: int = None, year: str = None,
mtype: str = None, mtype: str = None,
tmdbid: int = None, tmdbid: int = None,
season: int = None, season: int = None,

View File

@@ -37,9 +37,13 @@ def search_by_id(mediaid: str,
根据TMDBID/豆瓣ID精确搜索站点资源 tmdb:/douban:/bangumi: 根据TMDBID/豆瓣ID精确搜索站点资源 tmdb:/douban:/bangumi:
""" """
if mtype: if mtype:
mtype = MediaType(mtype) media_type = MediaType(mtype)
else:
media_type = None
if season: if season:
season = int(season) media_season = int(season)
else:
media_season = None
if sites: if sites:
site_list = [int(site) for site in sites.split(",") if site] site_list = [int(site) for site in sites.split(",") if site]
else: else:
@@ -50,31 +54,31 @@ def search_by_id(mediaid: str,
tmdbid = int(mediaid.replace("tmdb:", "")) tmdbid = int(mediaid.replace("tmdb:", ""))
if settings.RECOGNIZE_SOURCE == "douban": if settings.RECOGNIZE_SOURCE == "douban":
# 通过TMDBID识别豆瓣ID # 通过TMDBID识别豆瓣ID
doubaninfo = MediaChain().get_doubaninfo_by_tmdbid(tmdbid=tmdbid, mtype=mtype) doubaninfo = MediaChain().get_doubaninfo_by_tmdbid(tmdbid=tmdbid, mtype=media_type)
if doubaninfo: if doubaninfo:
torrents = SearchChain().search_by_id(doubanid=doubaninfo.get("id"), torrents = SearchChain().search_by_id(doubanid=doubaninfo.get("id"),
mtype=mtype, area=area, season=season, mtype=media_type, area=area, season=media_season,
sites=site_list) sites=site_list)
else: else:
return schemas.Response(success=False, message="未识别到豆瓣媒体信息") return schemas.Response(success=False, message="未识别到豆瓣媒体信息")
else: else:
torrents = SearchChain().search_by_id(tmdbid=tmdbid, mtype=mtype, area=area, season=season, torrents = SearchChain().search_by_id(tmdbid=tmdbid, mtype=media_type, area=area, season=media_season,
sites=site_list) sites=site_list)
elif mediaid.startswith("douban:"): elif mediaid.startswith("douban:"):
doubanid = mediaid.replace("douban:", "") doubanid = mediaid.replace("douban:", "")
if settings.RECOGNIZE_SOURCE == "themoviedb": if settings.RECOGNIZE_SOURCE == "themoviedb":
# 通过豆瓣ID识别TMDBID # 通过豆瓣ID识别TMDBID
tmdbinfo = MediaChain().get_tmdbinfo_by_doubanid(doubanid=doubanid, mtype=mtype) tmdbinfo = MediaChain().get_tmdbinfo_by_doubanid(doubanid=doubanid, mtype=media_type)
if tmdbinfo: if tmdbinfo:
if tmdbinfo.get('season') and not season: if tmdbinfo.get('season') and not media_season:
season = tmdbinfo.get('season') media_season = tmdbinfo.get('season')
torrents = SearchChain().search_by_id(tmdbid=tmdbinfo.get("id"), torrents = SearchChain().search_by_id(tmdbid=tmdbinfo.get("id"),
mtype=mtype, area=area, season=season, mtype=media_type, area=area, season=media_season,
sites=site_list) sites=site_list)
else: else:
return schemas.Response(success=False, message="未识别到TMDB媒体信息") return schemas.Response(success=False, message="未识别到TMDB媒体信息")
else: else:
torrents = SearchChain().search_by_id(doubanid=doubanid, mtype=mtype, area=area, season=season, torrents = SearchChain().search_by_id(doubanid=doubanid, mtype=media_type, area=area, season=media_season,
sites=site_list) sites=site_list)
elif mediaid.startswith("bangumi:"): elif mediaid.startswith("bangumi:"):
bangumiid = int(mediaid.replace("bangumi:", "")) bangumiid = int(mediaid.replace("bangumi:", ""))
@@ -83,7 +87,7 @@ def search_by_id(mediaid: str,
tmdbinfo = MediaChain().get_tmdbinfo_by_bangumiid(bangumiid=bangumiid) tmdbinfo = MediaChain().get_tmdbinfo_by_bangumiid(bangumiid=bangumiid)
if tmdbinfo: if tmdbinfo:
torrents = SearchChain().search_by_id(tmdbid=tmdbinfo.get("id"), torrents = SearchChain().search_by_id(tmdbid=tmdbinfo.get("id"),
mtype=mtype, area=area, season=season, mtype=media_type, area=area, season=media_season,
sites=site_list) sites=site_list)
else: else:
return schemas.Response(success=False, message="未识别到TMDB媒体信息") return schemas.Response(success=False, message="未识别到TMDB媒体信息")
@@ -92,7 +96,7 @@ def search_by_id(mediaid: str,
doubaninfo = MediaChain().get_doubaninfo_by_bangumiid(bangumiid=bangumiid) doubaninfo = MediaChain().get_doubaninfo_by_bangumiid(bangumiid=bangumiid)
if doubaninfo: if doubaninfo:
torrents = SearchChain().search_by_id(doubanid=doubaninfo.get("id"), torrents = SearchChain().search_by_id(doubanid=doubaninfo.get("id"),
mtype=mtype, area=area, season=season, mtype=media_type, area=area, season=media_season,
sites=site_list) sites=site_list)
else: else:
return schemas.Response(success=False, message="未识别到豆瓣媒体信息") return schemas.Response(success=False, message="未识别到豆瓣媒体信息")
@@ -110,10 +114,10 @@ def search_by_id(mediaid: str,
search_id = event_data.media_dict.get("id") search_id = event_data.media_dict.get("id")
if event_data.convert_type == "themoviedb": if event_data.convert_type == "themoviedb":
torrents = SearchChain().search_by_id(tmdbid=search_id, torrents = SearchChain().search_by_id(tmdbid=search_id,
mtype=mtype, area=area, season=season) mtype=media_type, area=area, season=media_season)
elif event_data.convert_type == "douban": elif event_data.convert_type == "douban":
torrents = SearchChain().search_by_id(doubanid=search_id, torrents = SearchChain().search_by_id(doubanid=search_id,
mtype=mtype, area=area, season=season) mtype=media_type, area=area, season=media_season)
else: else:
if not title: if not title:
return schemas.Response(success=False, message="未知的媒体ID") return schemas.Response(success=False, message="未知的媒体ID")
@@ -121,19 +125,19 @@ def search_by_id(mediaid: str,
meta = MetaInfo(title) meta = MetaInfo(title)
if year: if year:
meta.year = year meta.year = year
if mtype: if media_type:
meta.type = mtype meta.type = media_type
if season: if media_season:
meta.type = MediaType.TV meta.type = MediaType.TV
meta.begin_season = season meta.begin_season = media_season
mediainfo = MediaChain().recognize_media(meta=meta) mediainfo = MediaChain().recognize_media(meta=meta)
if mediainfo: if mediainfo:
if settings.RECOGNIZE_SOURCE == "themoviedb": if settings.RECOGNIZE_SOURCE == "themoviedb":
torrents = SearchChain().search_by_id(tmdbid=mediainfo.tmdb_id, torrents = SearchChain().search_by_id(tmdbid=mediainfo.tmdb_id,
mtype=mtype, area=area, season=season) mtype=media_type, area=area, season=media_season)
else: else:
torrents = SearchChain().search_by_id(doubanid=mediainfo.douban_id, torrents = SearchChain().search_by_id(doubanid=mediainfo.douban_id,
mtype=mtype, area=area, season=season) mtype=media_type, area=area, season=media_season)
# 返回搜索结果 # 返回搜索结果
if not torrents: if not torrents:
return schemas.Response(success=False, message="未搜索到任何资源") return schemas.Response(success=False, message="未搜索到任何资源")

View File

@@ -1,4 +1,4 @@
from typing import List, Any from typing import List, Any, Annotated
import cn2an import cn2an
from fastapi import APIRouter, Request, BackgroundTasks, Depends, HTTPException, Header from fastapi import APIRouter, Request, BackgroundTasks, Depends, HTTPException, Header
@@ -44,7 +44,7 @@ def read_subscribes(
@router.get("/list", summary="查询所有订阅API_TOKEN", response_model=List[schemas.Subscribe]) @router.get("/list", summary="查询所有订阅API_TOKEN", response_model=List[schemas.Subscribe])
def list_subscribes(_: str = Depends(verify_apitoken)) -> Any: def list_subscribes(_: Annotated[str, Depends(verify_apitoken)]) -> Any:
""" """
查询所有订阅 API_TOKEN认证?token=xxx 查询所有订阅 API_TOKEN认证?token=xxx
""" """
@@ -331,7 +331,7 @@ def delete_subscribe_by_mediaid(
@router.post("/seerr", summary="OverSeerr/JellySeerr通知订阅", response_model=schemas.Response) @router.post("/seerr", summary="OverSeerr/JellySeerr通知订阅", response_model=schemas.Response)
async def seerr_subscribe(request: Request, background_tasks: BackgroundTasks, async def seerr_subscribe(request: Request, background_tasks: BackgroundTasks,
authorization: str = Header(None)) -> Any: authorization: Annotated[str | None, Header()] = None) -> Any:
""" """
Jellyseerr/Overseerr网络勾子通知订阅 Jellyseerr/Overseerr网络勾子通知订阅
""" """

View File

@@ -5,7 +5,7 @@ import tempfile
from collections import deque from collections import deque
from datetime import datetime from datetime import datetime
from pathlib import Path from pathlib import Path
from typing import Optional, Union from typing import Optional, Union, Annotated
import aiofiles import aiofiles
import pillow_avif # noqa 用于自动注册AVIF支持 import pillow_avif # noqa 用于自动注册AVIF支持
@@ -141,7 +141,7 @@ def fetch_image(
def proxy_img( def proxy_img(
imgurl: str, imgurl: str,
proxy: bool = False, proxy: bool = False,
if_none_match: Optional[str] = Header(None), if_none_match: Annotated[str | None, Header()] = None,
_: schemas.TokenPayload = Depends(verify_resource_token) _: schemas.TokenPayload = Depends(verify_resource_token)
) -> Response: ) -> Response:
""" """
@@ -158,7 +158,7 @@ def proxy_img(
@router.get("/cache/image", summary="图片缓存") @router.get("/cache/image", summary="图片缓存")
def cache_img( def cache_img(
url: str, url: str,
if_none_match: Optional[str] = Header(None), if_none_match: Annotated[str | None, Header()] = None,
_: schemas.TokenPayload = Depends(verify_resource_token) _: schemas.TokenPayload = Depends(verify_resource_token)
) -> Response: ) -> Response:
""" """
@@ -500,7 +500,7 @@ def run_scheduler(jobid: str,
@router.get("/runscheduler2", summary="运行服务API_TOKEN", response_model=schemas.Response) @router.get("/runscheduler2", summary="运行服务API_TOKEN", response_model=schemas.Response)
def run_scheduler2(jobid: str, def run_scheduler2(jobid: str,
_: str = Depends(verify_apitoken)): _: Annotated[str, Depends(verify_apitoken)]):
""" """
执行命令API_TOKEN认证 执行命令API_TOKEN认证
""" """

View File

@@ -1,5 +1,5 @@
from pathlib import Path from pathlib import Path
from typing import Any, List from typing import Any, List, Annotated
from fastapi import APIRouter, Depends from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
@@ -165,7 +165,7 @@ def manual_transfer(transer_item: ManualTransferItem,
@router.get("/now", summary="立即执行下载器文件整理", response_model=schemas.Response) @router.get("/now", summary="立即执行下载器文件整理", response_model=schemas.Response)
def now(_: str = Depends(verify_apitoken)) -> Any: def now(_: Annotated[str, Depends(verify_apitoken)]) -> Any:
""" """
立即执行下载器文件整理 API_TOKEN认证?token=xxx 立即执行下载器文件整理 API_TOKEN认证?token=xxx
""" """

View File

@@ -1,4 +1,4 @@
from typing import Any from typing import Any, Annotated
from fastapi import APIRouter, BackgroundTasks, Request, Depends from fastapi import APIRouter, BackgroundTasks, Request, Depends
@@ -19,7 +19,7 @@ def start_webhook_chain(body: Any, form: Any, args: Any):
@router.post("/", summary="Webhook消息响应", response_model=schemas.Response) @router.post("/", summary="Webhook消息响应", response_model=schemas.Response)
async def webhook_message(background_tasks: BackgroundTasks, async def webhook_message(background_tasks: BackgroundTasks,
request: Request, request: Request,
_: str = Depends(verify_apitoken) _: Annotated[str, Depends(verify_apitoken)]
) -> Any: ) -> Any:
""" """
Webhook响应配置请求中需要添加参数token=API_TOKEN&source=媒体服务器名 Webhook响应配置请求中需要添加参数token=API_TOKEN&source=媒体服务器名
@@ -33,7 +33,7 @@ async def webhook_message(background_tasks: BackgroundTasks,
@router.get("/", summary="Webhook消息响应", response_model=schemas.Response) @router.get("/", summary="Webhook消息响应", response_model=schemas.Response)
def webhook_message(background_tasks: BackgroundTasks, def webhook_message(background_tasks: BackgroundTasks,
request: Request, _: str = Depends(verify_apitoken)) -> Any: request: Request, _: Annotated[str, Depends(verify_apitoken)]) -> Any:
""" """
Webhook响应配置请求中需要添加参数token=API_TOKEN&source=媒体服务器名 Webhook响应配置请求中需要添加参数token=API_TOKEN&source=媒体服务器名
""" """

View File

@@ -1,4 +1,4 @@
from typing import Any, List from typing import Any, List, Annotated
from fastapi import APIRouter, HTTPException, Depends from fastapi import APIRouter, HTTPException, Depends
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
@@ -18,7 +18,7 @@ arr_router = APIRouter(tags=['servarr'])
@arr_router.get("/system/status", summary="系统状态") @arr_router.get("/system/status", summary="系统状态")
def arr_system_status(_: str = Depends(verify_apikey)) -> Any: def arr_system_status(_: Annotated[str, Depends(verify_apikey)]) -> Any:
""" """
模拟Radarr、Sonarr系统状态 模拟Radarr、Sonarr系统状态
""" """
@@ -72,7 +72,7 @@ def arr_system_status(_: str = Depends(verify_apikey)) -> Any:
@arr_router.get("/qualityProfile", summary="质量配置") @arr_router.get("/qualityProfile", summary="质量配置")
def arr_qualityProfile(_: str = Depends(verify_apikey)) -> Any: def arr_qualityProfile(_: Annotated[str, Depends(verify_apikey)]) -> Any:
""" """
模拟Radarr、Sonarr质量配置 模拟Radarr、Sonarr质量配置
""" """
@@ -113,7 +113,7 @@ def arr_qualityProfile(_: str = Depends(verify_apikey)) -> Any:
@arr_router.get("/rootfolder", summary="根目录") @arr_router.get("/rootfolder", summary="根目录")
def arr_rootfolder(_: str = Depends(verify_apikey)) -> Any: def arr_rootfolder(_: Annotated[str, Depends(verify_apikey)]) -> Any:
""" """
模拟Radarr、Sonarr根目录 模拟Radarr、Sonarr根目录
""" """
@@ -129,7 +129,7 @@ def arr_rootfolder(_: str = Depends(verify_apikey)) -> Any:
@arr_router.get("/tag", summary="标签") @arr_router.get("/tag", summary="标签")
def arr_tag(_: str = Depends(verify_apikey)) -> Any: def arr_tag(_: Annotated[str, Depends(verify_apikey)]) -> Any:
""" """
模拟Radarr、Sonarr标签 模拟Radarr、Sonarr标签
""" """
@@ -142,7 +142,7 @@ def arr_tag(_: str = Depends(verify_apikey)) -> Any:
@arr_router.get("/languageprofile", summary="语言") @arr_router.get("/languageprofile", summary="语言")
def arr_languageprofile(_: str = Depends(verify_apikey)) -> Any: def arr_languageprofile(_: Annotated[str, Depends(verify_apikey)]) -> Any:
""" """
模拟Radarr、Sonarr语言 模拟Radarr、Sonarr语言
""" """
@@ -168,7 +168,7 @@ def arr_languageprofile(_: str = Depends(verify_apikey)) -> Any:
@arr_router.get("/movie", summary="所有订阅电影", response_model=List[schemas.RadarrMovie]) @arr_router.get("/movie", summary="所有订阅电影", response_model=List[schemas.RadarrMovie])
def arr_movies(_: str = Depends(verify_apikey), db: Session = Depends(get_db)) -> Any: def arr_movies(_: Annotated[str, Depends(verify_apikey)], db: Session = Depends(get_db)) -> Any:
""" """
查询Rardar电影 查询Rardar电影
""" """
@@ -259,7 +259,7 @@ def arr_movies(_: str = Depends(verify_apikey), db: Session = Depends(get_db)) -
@arr_router.get("/movie/lookup", summary="查询电影", response_model=List[schemas.RadarrMovie]) @arr_router.get("/movie/lookup", summary="查询电影", response_model=List[schemas.RadarrMovie])
def arr_movie_lookup(term: str, db: Session = Depends(get_db), _: str = Depends(verify_apikey)) -> Any: def arr_movie_lookup(term: str, _: Annotated[str, Depends(verify_apikey)], db: Session = Depends(get_db)) -> Any:
""" """
查询Rardar电影 term: `tmdb:${id}` 查询Rardar电影 term: `tmdb:${id}`
存在和不存在均不能返回错误 存在和不存在均不能返回错误
@@ -305,7 +305,7 @@ def arr_movie_lookup(term: str, db: Session = Depends(get_db), _: str = Depends(
@arr_router.get("/movie/{mid}", summary="电影订阅详情", response_model=schemas.RadarrMovie) @arr_router.get("/movie/{mid}", summary="电影订阅详情", response_model=schemas.RadarrMovie)
def arr_movie(mid: int, db: Session = Depends(get_db), _: str = Depends(verify_apikey)) -> Any: def arr_movie(mid: int, _: Annotated[str, Depends(verify_apikey)], db: Session = Depends(get_db)) -> Any:
""" """
查询Rardar电影订阅 查询Rardar电影订阅
""" """
@@ -331,9 +331,9 @@ def arr_movie(mid: int, db: Session = Depends(get_db), _: str = Depends(verify_a
@arr_router.post("/movie", summary="新增电影订阅") @arr_router.post("/movie", summary="新增电影订阅")
def arr_add_movie(movie: RadarrMovie, def arr_add_movie(_: Annotated[str, Depends(verify_apikey)],
db: Session = Depends(get_db), movie: RadarrMovie,
_: str = Depends(verify_apikey) db: Session = Depends(get_db)
) -> Any: ) -> Any:
""" """
新增Rardar电影订阅 新增Rardar电影订阅
@@ -362,7 +362,7 @@ def arr_add_movie(movie: RadarrMovie,
@arr_router.delete("/movie/{mid}", summary="删除电影订阅", response_model=schemas.Response) @arr_router.delete("/movie/{mid}", summary="删除电影订阅", response_model=schemas.Response)
def arr_remove_movie(mid: int, db: Session = Depends(get_db), _: str = Depends(verify_apikey)) -> Any: def arr_remove_movie(mid: int, _: Annotated[str, Depends(verify_apikey)], db: Session = Depends(get_db)) -> Any:
""" """
删除Rardar电影订阅 删除Rardar电影订阅
""" """
@@ -378,7 +378,7 @@ def arr_remove_movie(mid: int, db: Session = Depends(get_db), _: str = Depends(v
@arr_router.get("/series", summary="所有剧集", response_model=List[schemas.SonarrSeries]) @arr_router.get("/series", summary="所有剧集", response_model=List[schemas.SonarrSeries])
def arr_series(_: str = Depends(verify_apikey), db: Session = Depends(get_db)) -> Any: def arr_series(_: Annotated[str, Depends(verify_apikey)], db: Session = Depends(get_db)) -> Any:
""" """
查询Sonarr剧集 查询Sonarr剧集
""" """
@@ -514,7 +514,7 @@ def arr_series(_: str = Depends(verify_apikey), db: Session = Depends(get_db)) -
@arr_router.get("/series/lookup", summary="查询剧集") @arr_router.get("/series/lookup", summary="查询剧集")
def arr_series_lookup(term: str, db: Session = Depends(get_db), _: str = Depends(verify_apikey)) -> Any: def arr_series_lookup(term: str, _: Annotated[str, Depends(verify_apikey)], db: Session = Depends(get_db)) -> Any:
""" """
查询Sonarr剧集 term: `tvdb:${id}` title 查询Sonarr剧集 term: `tvdb:${id}` title
""" """
@@ -603,7 +603,7 @@ def arr_series_lookup(term: str, db: Session = Depends(get_db), _: str = Depends
@arr_router.get("/series/{tid}", summary="剧集详情") @arr_router.get("/series/{tid}", summary="剧集详情")
def arr_serie(tid: int, db: Session = Depends(get_db), _: str = Depends(verify_apikey)) -> Any: def arr_serie(tid: int, _: Annotated[str, Depends(verify_apikey)], db: Session = Depends(get_db)) -> Any:
""" """
查询Sonarr剧集 查询Sonarr剧集
""" """
@@ -638,8 +638,8 @@ def arr_serie(tid: int, db: Session = Depends(get_db), _: str = Depends(verify_a
@arr_router.post("/series", summary="新增剧集订阅") @arr_router.post("/series", summary="新增剧集订阅")
def arr_add_series(tv: schemas.SonarrSeries, def arr_add_series(tv: schemas.SonarrSeries,
db: Session = Depends(get_db), _: Annotated[str, Depends(verify_apikey)],
_: str = Depends(verify_apikey)) -> Any: db: Session = Depends(get_db)) -> Any:
""" """
新增Sonarr剧集订阅 新增Sonarr剧集订阅
""" """
@@ -689,7 +689,7 @@ def arr_update_series(tv: schemas.SonarrSeries) -> Any:
@arr_router.delete("/series/{tid}", summary="删除剧集订阅") @arr_router.delete("/series/{tid}", summary="删除剧集订阅")
def arr_remove_series(tid: int, db: Session = Depends(get_db), _: str = Depends(verify_apikey)) -> Any: def arr_remove_series(tid: int, _: Annotated[str, Depends(verify_apikey)], db: Session = Depends(get_db)) -> Any:
""" """
删除Sonarr剧集订阅 删除Sonarr剧集订阅
""" """

View File

@@ -94,10 +94,10 @@ class ChainBase(metaclass=ABCMeta):
if isinstance(ret, tuple): if isinstance(ret, tuple):
return all(value is None for value in ret) return all(value is None for value in ret)
else: else:
return result is None return ret is None
logger.debug(f"请求模块执行:{method} ...")
result = None result = None
logger.debug(f"请求模块执行:{method} ...")
modules = self.modulemanager.get_running_modules(method) modules = self.modulemanager.get_running_modules(method)
# 按优先级排序 # 按优先级排序
modules = sorted(modules, key=lambda x: x.get_priority()) modules = sorted(modules, key=lambda x: x.get_priority())
@@ -110,6 +110,8 @@ class ChainBase(metaclass=ABCMeta):
module_name = module_id module_name = module_id
try: try:
func = getattr(module, method) func = getattr(module, method)
# 添加日志记录类型
logger.debug(f"调用方法类型: {type(func)}")
if is_result_empty(result): if is_result_empty(result):
# 返回None第一次执行或者需继续执行下一模块 # 返回None第一次执行或者需继续执行下一模块
result = func(*args, **kwargs) result = func(*args, **kwargs)

View File

@@ -172,7 +172,7 @@ class MetaVideo(MetaBase):
return None return None
@staticmethod @staticmethod
def __is_pinyin(name_str: str) -> bool: def __is_pinyin(name_str: Optional[str]) -> bool:
""" """
判断是否拼音 判断是否拼音
""" """
@@ -183,7 +183,7 @@ class MetaVideo(MetaBase):
return False return False
return True return True
def __fix_name(self, name: str): def __fix_name(self, name: Optional[str]):
""" """
去掉名字中不需要的干扰字符 去掉名字中不需要的干扰字符
""" """
@@ -207,7 +207,7 @@ class MetaVideo(MetaBase):
name = None name = None
return name return name
def __init_name(self, token: str): def __init_name(self, token: Optional[str]):
""" """
识别名称 识别名称
""" """

View File

@@ -12,7 +12,7 @@ import jwt
from Crypto.Cipher import AES from Crypto.Cipher import AES
from Crypto.Util.Padding import pad from Crypto.Util.Padding import pad
from cryptography.fernet import Fernet from cryptography.fernet import Fernet
from fastapi import HTTPException, status, Security, Request, Response from fastapi import HTTPException, status, Security, Request, Response, Depends
from fastapi.security import OAuth2PasswordBearer, APIKeyHeader, APIKeyQuery, APIKeyCookie from fastapi.security import OAuth2PasswordBearer, APIKeyHeader, APIKeyQuery, APIKeyCookie
from passlib.context import CryptContext from passlib.context import CryptContext
@@ -176,7 +176,7 @@ def __verify_token(token: str, purpose: str = "authentication") -> schemas.Token
def verify_token( def verify_token(
request: Request, request: Request,
response: Response, response: Response,
token: str = Security(oauth2_scheme) token: Annotated[str, Security(oauth2_scheme)]
) -> schemas.TokenPayload: ) -> schemas.TokenPayload:
""" """
验证 JWT 令牌并自动处理 resource_token 写入 验证 JWT 令牌并自动处理 resource_token 写入
@@ -196,7 +196,7 @@ def verify_token(
def verify_resource_token( def verify_resource_token(
resource_token: str = Security(resource_token_cookie) resource_token: Annotated[str, Security(resource_token_cookie)]
) -> schemas.TokenPayload: ) -> schemas.TokenPayload:
""" """
验证资源访问令牌(从 Cookie 中获取) 验证资源访问令牌(从 Cookie 中获取)
@@ -249,7 +249,7 @@ def __verify_key(key: str, expected_key: str, key_type: str) -> str:
return key return key
def verify_apitoken(token: str = Security(__get_api_token)) -> str: def verify_apitoken(token: Annotated[str, Security(__get_api_token)]) -> str:
""" """
使用 API Token 进行身份认证 使用 API Token 进行身份认证
:param token: API Token从 URL 查询参数中获取 :param token: API Token从 URL 查询参数中获取
@@ -258,7 +258,7 @@ def verify_apitoken(token: str = Security(__get_api_token)) -> str:
return __verify_key(token, settings.API_TOKEN, "API_TOKEN") return __verify_key(token, settings.API_TOKEN, "API_TOKEN")
def verify_apikey(apikey: str = Security(__get_api_key)) -> str: def verify_apikey(apikey: Annotated[str, Security(__get_api_key)]) -> str:
""" """
使用 API Key 进行身份认证 使用 API Key 进行身份认证
:param apikey: API Key从 URL 查询参数或请求头中获取 :param apikey: API Key从 URL 查询参数或请求头中获取

View File

@@ -3,18 +3,26 @@ import importlib
import pkgutil import pkgutil
import traceback import traceback
from pathlib import Path from pathlib import Path
from typing import List, Any from typing import List, Any, Callable
from app.log import logger from app.log import logger
FilterFuncType = Callable[[str, Any], bool]
def _default_filter(name: str, obj: Any) -> bool:
"""
默认过滤器
"""
return True
class ModuleHelper: class ModuleHelper:
""" """
模块动态加载 模块动态加载
""" """
@classmethod @classmethod
def load(cls, package_path: str, filter_func=lambda name, obj: True) -> List[Any]: def load(cls, package_path: str, filter_func: FilterFuncType = _default_filter) -> List[Any]:
""" """
导入模块 导入模块
:param package_path: 父包名 :param package_path: 父包名
@@ -46,7 +54,7 @@ class ModuleHelper:
return submodules return submodules
@classmethod @classmethod
def load_with_pre_filter(cls, package_path: str, filter_func=lambda name, obj: True) -> List[Any]: def load_with_pre_filter(cls, package_path: str, filter_func: FilterFuncType = _default_filter) -> List[Any]:
""" """
导入子模块 导入子模块
:param package_path: 父包名 :param package_path: 父包名

View File

@@ -40,7 +40,7 @@ class ProgressHelper(metaclass=Singleton):
"text": "正在处理..." "text": "正在处理..."
} }
def update(self, key: Union[ProgressKey, str], value: float = None, text: str = None): def update(self, key: Union[ProgressKey, str], value: Union[float, int] = None, text: str = None):
if isinstance(key, Enum): if isinstance(key, Enum):
key = key.value key = key.value
if not self._process_detail.get(key, {}).get('enable'): if not self._process_detail.get(key, {}).get('enable'):

View File

@@ -29,7 +29,6 @@ class _ModuleBase(metaclass=ABCMeta):
pass pass
@staticmethod @staticmethod
@abstractmethod
def get_name() -> str: def get_name() -> str:
""" """
获取模块名称 获取模块名称
@@ -37,7 +36,6 @@ class _ModuleBase(metaclass=ABCMeta):
pass pass
@staticmethod @staticmethod
@abstractmethod
def get_type() -> ModuleType: def get_type() -> ModuleType:
""" """
获取模块类型 获取模块类型
@@ -45,7 +43,6 @@ class _ModuleBase(metaclass=ABCMeta):
pass pass
@staticmethod @staticmethod
@abstractmethod
def get_subtype() -> Union[DownloaderType, MediaServerType, MessageChannel, StorageSchema, OtherModulesType]: def get_subtype() -> Union[DownloaderType, MediaServerType, MessageChannel, StorageSchema, OtherModulesType]:
""" """
获取模块子类型(下载器、媒体服务器、消息通道、存储类型、其他杂项模块类型) 获取模块子类型(下载器、媒体服务器、消息通道、存储类型、其他杂项模块类型)
@@ -53,7 +50,6 @@ class _ModuleBase(metaclass=ABCMeta):
pass pass
@staticmethod @staticmethod
@abstractmethod
def get_priority() -> int: def get_priority() -> int:
""" """
获取模块优先级,数字越小优先级越高,只有同一接口下优先级才生效 获取模块优先级,数字越小优先级越高,只有同一接口下优先级才生效

View File

@@ -148,12 +148,12 @@ class EmbyModule(_ModuleBase, _MediaServerBase[Emby]):
servers = [(server, self.get_instance(server))] servers = [(server, self.get_instance(server))]
else: else:
servers = self.get_instances().items() servers = self.get_instances().items()
for name, server in servers: for name, s in servers:
if not server: if not server:
continue continue
if mediainfo.type == MediaType.MOVIE: if mediainfo.type == MediaType.MOVIE:
if itemid: if itemid:
movie = server.get_iteminfo(itemid) movie = s.get_iteminfo(itemid)
if movie: if movie:
logger.info(f"媒体库 {name} 中找到了 {movie}") logger.info(f"媒体库 {name} 中找到了 {movie}")
return schemas.ExistMediaInfo( return schemas.ExistMediaInfo(
@@ -162,9 +162,9 @@ class EmbyModule(_ModuleBase, _MediaServerBase[Emby]):
server=name, server=name,
itemid=movie.item_id itemid=movie.item_id
) )
movies = server.get_movies(title=mediainfo.title, movies = s.get_movies(title=mediainfo.title,
year=mediainfo.year, year=mediainfo.year,
tmdb_id=mediainfo.tmdb_id) tmdb_id=mediainfo.tmdb_id)
if not movies: if not movies:
logger.info(f"{mediainfo.title_year} 没有在媒体库 {name}") logger.info(f"{mediainfo.title_year} 没有在媒体库 {name}")
continue continue
@@ -177,10 +177,10 @@ class EmbyModule(_ModuleBase, _MediaServerBase[Emby]):
itemid=movies[0].item_id itemid=movies[0].item_id
) )
else: else:
itemid, tvs = server.get_tv_episodes(title=mediainfo.title, itemid, tvs = s.get_tv_episodes(title=mediainfo.title,
year=mediainfo.year, year=mediainfo.year,
tmdb_id=mediainfo.tmdb_id, tmdb_id=mediainfo.tmdb_id,
item_id=itemid) item_id=itemid)
if not tvs: if not tvs:
logger.info(f"{mediainfo.title_year} 没有在媒体库 {name}") logger.info(f"{mediainfo.title_year} 没有在媒体库 {name}")
continue continue
@@ -207,11 +207,11 @@ class EmbyModule(_ModuleBase, _MediaServerBase[Emby]):
else: else:
servers = self.get_instances().values() servers = self.get_instances().values()
media_statistics = [] media_statistics = []
for server in servers: for s in servers:
media_statistic = server.get_medias_count() media_statistic = s.get_medias_count()
if not media_statistic: if not media_statistic:
continue continue
media_statistic.user_count = server.get_user_count() media_statistic.user_count = s.get_user_count()
media_statistics.append(media_statistic) media_statistics.append(media_statistic)
return media_statistics return media_statistics

View File

@@ -149,12 +149,12 @@ class JellyfinModule(_ModuleBase, _MediaServerBase[Jellyfin]):
servers = [(server, self.get_instance(server))] servers = [(server, self.get_instance(server))]
else: else:
servers = self.get_instances().items() servers = self.get_instances().items()
for name, server in servers: for name, s in servers:
if not server: if not server:
continue continue
if mediainfo.type == MediaType.MOVIE: if mediainfo.type == MediaType.MOVIE:
if itemid: if itemid:
movie = server.get_iteminfo(itemid) movie = s.get_iteminfo(itemid)
if movie: if movie:
logger.info(f"媒体库 {name} 中找到了 {movie}") logger.info(f"媒体库 {name} 中找到了 {movie}")
return schemas.ExistMediaInfo( return schemas.ExistMediaInfo(
@@ -163,7 +163,7 @@ class JellyfinModule(_ModuleBase, _MediaServerBase[Jellyfin]):
server=name, server=name,
itemid=movie.item_id itemid=movie.item_id
) )
movies = server.get_movies(title=mediainfo.title, year=mediainfo.year, tmdb_id=mediainfo.tmdb_id) movies = s.get_movies(title=mediainfo.title, year=mediainfo.year, tmdb_id=mediainfo.tmdb_id)
if not movies: if not movies:
logger.info(f"{mediainfo.title_year} 没有在媒体库 {name}") logger.info(f"{mediainfo.title_year} 没有在媒体库 {name}")
continue continue
@@ -176,10 +176,10 @@ class JellyfinModule(_ModuleBase, _MediaServerBase[Jellyfin]):
itemid=movies[0].item_id itemid=movies[0].item_id
) )
else: else:
itemid, tvs = server.get_tv_episodes(title=mediainfo.title, itemid, tvs = s.get_tv_episodes(title=mediainfo.title,
year=mediainfo.year, year=mediainfo.year,
tmdb_id=mediainfo.tmdb_id, tmdb_id=mediainfo.tmdb_id,
item_id=itemid) item_id=itemid)
if not tvs: if not tvs:
logger.info(f"{mediainfo.title_year} 没有在媒体库 {name}") logger.info(f"{mediainfo.title_year} 没有在媒体库 {name}")
continue continue
@@ -206,11 +206,11 @@ class JellyfinModule(_ModuleBase, _MediaServerBase[Jellyfin]):
else: else:
servers = self.get_instances().values() servers = self.get_instances().values()
media_statistics = [] media_statistics = []
for server in servers: for s in servers:
media_statistic = server.get_medias_count() media_statistic = s.get_medias_count()
if not media_statistic: if not media_statistic:
continue continue
media_statistic.user_count = server.get_user_count() media_statistic.user_count = s.get_user_count()
media_statistics.append(media_statistic) media_statistics.append(media_statistic)
return media_statistics return media_statistics

View File

@@ -152,12 +152,12 @@ class PlexModule(_ModuleBase, _MediaServerBase[Plex]):
servers = [(server, self.get_instance(server))] servers = [(server, self.get_instance(server))]
else: else:
servers = self.get_instances().items() servers = self.get_instances().items()
for name, server in servers: for name, s in servers:
if not server: if not server:
continue continue
if mediainfo.type == MediaType.MOVIE: if mediainfo.type == MediaType.MOVIE:
if itemid: if itemid:
movie = server.get_iteminfo(itemid) movie = s.get_iteminfo(itemid)
if movie: if movie:
logger.info(f"媒体库 {name} 中找到了 {movie}") logger.info(f"媒体库 {name} 中找到了 {movie}")
return schemas.ExistMediaInfo( return schemas.ExistMediaInfo(
@@ -166,10 +166,10 @@ class PlexModule(_ModuleBase, _MediaServerBase[Plex]):
server=name, server=name,
itemid=movie.item_id itemid=movie.item_id
) )
movies = server.get_movies(title=mediainfo.title, movies = s.get_movies(title=mediainfo.title,
original_title=mediainfo.original_title, original_title=mediainfo.original_title,
year=mediainfo.year, year=mediainfo.year,
tmdb_id=mediainfo.tmdb_id) tmdb_id=mediainfo.tmdb_id)
if not movies: if not movies:
logger.info(f"{mediainfo.title_year} 没有在媒体库 {name}") logger.info(f"{mediainfo.title_year} 没有在媒体库 {name}")
continue continue
@@ -182,11 +182,11 @@ class PlexModule(_ModuleBase, _MediaServerBase[Plex]):
itemid=movies[0].item_id itemid=movies[0].item_id
) )
else: else:
item_id, tvs = server.get_tv_episodes(title=mediainfo.title, item_id, tvs = s.get_tv_episodes(title=mediainfo.title,
original_title=mediainfo.original_title, original_title=mediainfo.original_title,
year=mediainfo.year, year=mediainfo.year,
tmdb_id=mediainfo.tmdb_id, tmdb_id=mediainfo.tmdb_id,
item_id=itemid) item_id=itemid)
if not tvs: if not tvs:
logger.info(f"{mediainfo.title_year} 没有在媒体库 {name}") logger.info(f"{mediainfo.title_year} 没有在媒体库 {name}")
continue continue
@@ -213,8 +213,8 @@ class PlexModule(_ModuleBase, _MediaServerBase[Plex]):
else: else:
servers = self.get_instances().values() servers = self.get_instances().values()
media_statistics = [] media_statistics = []
for server in servers: for s in servers:
media_statistic = server.get_medias_count() media_statistic = s.get_medias_count()
if not media_statistic: if not media_statistic:
continue continue
media_statistic.user_count = 1 media_statistic.user_count = 1

View File

@@ -1,7 +1,9 @@
import shutil import shutil
from pathlib import Path from pathlib import Path
from typing import Union
import ruamel.yaml import ruamel.yaml
from ruamel.yaml import CommentedMap
from app.core.config import settings from app.core.config import settings
from app.log import logger from app.log import logger
@@ -95,7 +97,7 @@ class CategoryHelper(metaclass=Singleton):
return self.get_category(self._tv_categorys, tmdb_info) return self.get_category(self._tv_categorys, tmdb_info)
@staticmethod @staticmethod
def get_category(categorys: dict, tmdb_info: dict) -> str: def get_category(categorys: Union[dict, CommentedMap], tmdb_info: dict) -> str:
""" """
根据 TMDB信息与分类配置文件进行比较确定所属分类 根据 TMDB信息与分类配置文件进行比较确定所属分类
:param categorys: 分类配置 :param categorys: 分类配置

View File

@@ -63,7 +63,6 @@ class _PluginBase(metaclass=ABCMeta):
pass pass
@staticmethod @staticmethod
@abstractmethod
def get_command() -> List[Dict[str, Any]]: def get_command() -> List[Dict[str, Any]]:
""" """
注册插件远程命令 注册插件远程命令

View File

@@ -1,6 +1,7 @@
import dis
import inspect import inspect
from types import FunctionType from types import FunctionType
from typing import Any, Callable from typing import Any, Callable, get_type_hints
class ObjectUtils: class ObjectUtils:
@@ -43,24 +44,45 @@ class ObjectUtils:
检查函数是否已实现 检查函数是否已实现
""" """
try: try:
# 尝试通过源代码分析
source = inspect.getsource(func) source = inspect.getsource(func)
in_comment = False in_comment = False
for line in source.split('\n'): for line in source.split('\n'):
line = line.strip() line = line.strip()
# 跳过空行
if not line: if not line:
continue continue
if line.startswith('"""') or line.startswith("'''"): # 处理多行注释
if line.startswith(('"""', "'''")):
in_comment = not in_comment in_comment = not in_comment
continue continue
if not in_comment and not (line.startswith('#') # 在注释中则跳过
or line == "pass" if in_comment:
or line.startswith('@') continue
or line.startswith('def ')): # 跳过注释、pass语句、装饰器、函数定义行
return True if line.startswith('#') or line == "pass" or line.startswith('@') or line.startswith('def '):
except Exception as err: continue
print(str(err)) # 发现有效代码行
return func.__code__.co_code not in [b'd\x01S\x00', b'\x97\x00d\x00S\x00'] return True
return False # 没有有效代码行
return False
except Exception:
# 源代码分析失败时,进行字节码分析
code_obj = func.__code__
instructions = list(dis.get_instructions(code_obj))
# 检查是否为仅返回None的简单结构
if len(instructions) == 2:
first, second = instructions
if (first.opname == 'LOAD_CONST' and
second.opname == 'RETURN_VALUE'):
# 验证加载的常量是否为None
const_index = first.arg
if (const_index < len(code_obj.co_consts) and
code_obj.co_consts[const_index] is None):
# 未实现的空函数
return False
# 其他情况认为已实现
return True
@staticmethod @staticmethod
def check_signature(func: FunctionType, *args) -> bool: def check_signature(func: FunctionType, *args) -> bool:
@@ -70,11 +92,35 @@ class ObjectUtils:
# 获取函数的参数信息 # 获取函数的参数信息
signature = inspect.signature(func) signature = inspect.signature(func)
parameters = signature.parameters parameters = signature.parameters
# 检查输入参数个数和类型是否一致
if len(args) != len(parameters): if len(args) != len(parameters):
return False return False
for arg, param in zip(args, parameters.values()): try:
if not isinstance(arg, param.annotation): # 获取解析后的类型提示
type_hints = get_type_hints(func)
except TypeError:
type_hints = {}
for arg, (param_name, param) in zip(args, parameters.items()):
# 优先使用解析后的类型提示
param_type = type_hints.get(param_name, None)
if param_type is None:
# 处理原始注解可能为字符串或Cython类型
param_annotation = param.annotation
if param_annotation is inspect.Parameter.empty:
continue
# 处理字符串类型的注解
if isinstance(param_annotation, str):
# 尝试解析字符串为实际类型
module = inspect.getmodule(func)
global_vars = module.__dict__ if module else globals()
try:
param_type = eval(param_annotation, global_vars)
except Exception as err:
print(str(err))
continue
else:
param_type = param_annotation
if param_type is None:
continue
if not isinstance(arg, param_type):
return False return False
return True return True

View File

@@ -207,7 +207,7 @@ class StringUtils:
return [StringUtils.clear(x) for x in text] return [StringUtils.clear(x) for x in text]
@staticmethod @staticmethod
def clear_upper(text: str) -> str: def clear_upper(text: Optional[str]) -> str:
""" """
去除特殊字符,同时大写 去除特殊字符,同时大写
""" """
@@ -596,7 +596,7 @@ class StringUtils:
return mtype, key_word, season_num, episode_num, year, content return mtype, key_word, season_num, episode_num, year, content
@staticmethod @staticmethod
def str_title(s: str) -> str: def str_title(s: Optional[str]) -> str:
""" """
大写首字母兼容None 大写首字母兼容None
""" """

View File

@@ -1,4 +1,4 @@
Cython~=3.0.2 Cython~=3.0.12
pydantic~=1.10.13 pydantic~=1.10.13
SQLAlchemy~=2.0.15 SQLAlchemy~=2.0.15
uvicorn~=0.22.0 uvicorn~=0.22.0

37
setup.py Normal file
View File

@@ -0,0 +1,37 @@
from setuptools import setup, Extension
from Cython.Build import cythonize
import glob
# 递归获取所有.py文件
sources = glob.glob("app/**/*.py", recursive=True)
# 移除不需要编译的文件
sources.remove("app/main.py")
# 配置编译参数(可选优化选项)
extensions = [
Extension(
name=path.replace("/", ".").replace(".py", ""),
sources=[path],
extra_compile_args=["-O3", "-ffast-math"],
)
for path in sources
]
setup(
name="MoviePilot",
author="jxxghp",
ext_modules=cythonize(
extensions,
build_dir="cython_cache",
compiler_directives={
"language_level": "3",
"auto_pickle": False,
"embedsignature": True,
"annotation_typing": True,
"infer_types": False
},
annotate=True
),
script_args=["build_ext", "-j8", "--inplace"],
)