mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-08-29 03:56:43 +08:00
feat: 升级 V3 默认运行时至 Python 3.14 与 Debian Trixie (#6382)
This commit is contained in:
@@ -8,8 +8,7 @@ on:
|
||||
- 'pyproject.toml'
|
||||
- 'uv.lock'
|
||||
- 'docker/Dockerfile'
|
||||
- 'docker/entrypoint.sh'
|
||||
- 'docker/update.sh'
|
||||
- 'docker/**'
|
||||
- '.github/workflows/dependency-compat.yml'
|
||||
push:
|
||||
branches:
|
||||
@@ -18,8 +17,7 @@ on:
|
||||
- 'pyproject.toml'
|
||||
- 'uv.lock'
|
||||
- 'docker/Dockerfile'
|
||||
- 'docker/entrypoint.sh'
|
||||
- 'docker/update.sh'
|
||||
- 'docker/**'
|
||||
- '.github/workflows/dependency-compat.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
@@ -41,39 +39,29 @@ jobs:
|
||||
include:
|
||||
- name: Linux x64
|
||||
runner: ubuntu-24.04
|
||||
python-version: '3.12'
|
||||
expected-system: Linux
|
||||
expected-machine: x86_64
|
||||
- name: Linux x64
|
||||
runner: ubuntu-24.04
|
||||
python-version: '3.13'
|
||||
python-version: '3.14'
|
||||
expected-system: Linux
|
||||
expected-machine: x86_64
|
||||
- name: Linux ARM64
|
||||
runner: ubuntu-24.04-arm
|
||||
python-version: '3.12'
|
||||
python-version: '3.14'
|
||||
expected-system: Linux
|
||||
expected-machine: aarch64
|
||||
- name: macOS Intel
|
||||
runner: macos-15-intel
|
||||
python-version: '3.12'
|
||||
python-version: '3.14'
|
||||
expected-system: Darwin
|
||||
expected-machine: x86_64
|
||||
- name: macOS ARM
|
||||
runner: macos-15
|
||||
python-version: '3.12'
|
||||
python-version: '3.14'
|
||||
expected-system: Darwin
|
||||
expected-machine: arm64
|
||||
- name: Windows x64
|
||||
runner: windows-2025
|
||||
python-version: '3.12'
|
||||
python-version: '3.14'
|
||||
expected-system: Windows
|
||||
expected-machine: AMD64
|
||||
- name: Linux x64
|
||||
runner: ubuntu-24.04
|
||||
python-version: '3.14'
|
||||
expected-system: Linux
|
||||
expected-machine: x86_64
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
|
||||
with:
|
||||
version: '0.12.5'
|
||||
python-version: '3.12'
|
||||
python-version: '3.14'
|
||||
enable-cache: true
|
||||
cache-dependency-glob: |
|
||||
pyproject.toml
|
||||
|
||||
@@ -49,7 +49,7 @@ jobs:
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.12'
|
||||
python-version: '3.14'
|
||||
|
||||
- name: Set up uv
|
||||
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
|
||||
|
||||
@@ -40,7 +40,7 @@ jobs:
|
||||
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
|
||||
with:
|
||||
version: '0.12.5'
|
||||
python-version: '3.12'
|
||||
python-version: '3.14'
|
||||
enable-cache: true
|
||||
cache-dependency-glob: |
|
||||
pyproject.toml
|
||||
@@ -67,7 +67,7 @@ jobs:
|
||||
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
|
||||
with:
|
||||
version: '0.12.5'
|
||||
python-version: '3.12'
|
||||
python-version: '3.14'
|
||||
enable-cache: true
|
||||
cache-dependency-glob: |
|
||||
pyproject.toml
|
||||
|
||||
@@ -7,6 +7,12 @@ def _filter_third_party_startup_warnings() -> None:
|
||||
"""
|
||||
过滤第三方库在新版 Python 下产生的已知无害启动警告。
|
||||
"""
|
||||
warnings.filterwarnings(
|
||||
"ignore",
|
||||
message=r"'_UnionGenericAlias' is deprecated and slated for removal in Python 3\.17",
|
||||
category=DeprecationWarning,
|
||||
module=r"google\.genai\.types",
|
||||
)
|
||||
warnings.filterwarnings(
|
||||
"ignore",
|
||||
message=r"invalid escape sequence '\\&'",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Any, Union
|
||||
from typing import Any, ClassVar, Union
|
||||
|
||||
from app.chain import ChainBase
|
||||
from app.application.configuration import get_configured_system_config as SystemConfigOper
|
||||
@@ -17,6 +17,11 @@ class BaseAction(ABC):
|
||||
工作流动作基类
|
||||
"""
|
||||
|
||||
# 工作流编辑器和运行管理器读取的动作级静态元数据。
|
||||
name: ClassVar[str]
|
||||
description: ClassVar[str]
|
||||
data: ClassVar[dict[str, Any]]
|
||||
|
||||
# 动作ID
|
||||
_action_id = None
|
||||
# 完成标志
|
||||
@@ -34,24 +39,6 @@ class BaseAction(ABC):
|
||||
self._message = ""
|
||||
self.systemconfigoper = SystemConfigOper()
|
||||
|
||||
@classmethod
|
||||
@property
|
||||
@abstractmethod
|
||||
def name(cls) -> str: # noqa
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
@property
|
||||
@abstractmethod
|
||||
def description(cls) -> str: # noqa
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
@property
|
||||
@abstractmethod
|
||||
def data(cls) -> dict: # noqa
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def get_contract(cls) -> dict:
|
||||
"""
|
||||
|
||||
@@ -40,20 +40,9 @@ class AddDownloadAction(BaseAction):
|
||||
self._added_downloads = []
|
||||
self._has_error = False
|
||||
|
||||
@classmethod
|
||||
@property
|
||||
def name(cls) -> str: # noqa
|
||||
return "添加下载"
|
||||
|
||||
@classmethod
|
||||
@property
|
||||
def description(cls) -> str: # noqa
|
||||
return "根据资源列表添加下载任务"
|
||||
|
||||
@classmethod
|
||||
@property
|
||||
def data(cls) -> dict: # noqa
|
||||
return AddDownloadParams().model_dump()
|
||||
name = "添加下载"
|
||||
description = "根据资源列表添加下载任务"
|
||||
data = AddDownloadParams().model_dump()
|
||||
|
||||
@property
|
||||
def success(self) -> bool:
|
||||
|
||||
@@ -30,20 +30,9 @@ class AddSubscribeAction(BaseAction):
|
||||
self._added_subscribes = []
|
||||
self._has_error = False
|
||||
|
||||
@classmethod
|
||||
@property
|
||||
def name(cls) -> str: # noqa
|
||||
return "添加订阅"
|
||||
|
||||
@classmethod
|
||||
@property
|
||||
def description(cls) -> str: # noqa
|
||||
return "根据媒体列表添加订阅"
|
||||
|
||||
@classmethod
|
||||
@property
|
||||
def data(cls) -> dict: # noqa
|
||||
return AddSubscribeParams().model_dump()
|
||||
name = "添加订阅"
|
||||
description = "根据媒体列表添加订阅"
|
||||
data = AddSubscribeParams().model_dump()
|
||||
|
||||
@property
|
||||
def success(self) -> bool:
|
||||
|
||||
@@ -27,20 +27,9 @@ class FetchDownloadsAction(BaseAction):
|
||||
super().__init__(action_id)
|
||||
self._downloads = []
|
||||
|
||||
@classmethod
|
||||
@property
|
||||
def name(cls) -> str: # noqa
|
||||
return "获取下载任务"
|
||||
|
||||
@classmethod
|
||||
@property
|
||||
def description(cls) -> str: # noqa
|
||||
return "获取下载队列中的任务状态"
|
||||
|
||||
@classmethod
|
||||
@property
|
||||
def data(cls) -> dict: # noqa
|
||||
return FetchDownloadsParams().model_dump()
|
||||
name = "获取下载任务"
|
||||
description = "获取下载队列中的任务状态"
|
||||
data = FetchDownloadsParams().model_dump()
|
||||
|
||||
@property
|
||||
def success(self) -> bool:
|
||||
|
||||
@@ -115,20 +115,9 @@ class FetchMediasAction(BaseAction):
|
||||
if event_data.extra_sources:
|
||||
self.__inner_sources.extend([s.model_dump() for s in event_data.extra_sources])
|
||||
|
||||
@classmethod
|
||||
@property
|
||||
def name(cls) -> str: # noqa
|
||||
return "获取媒体数据"
|
||||
|
||||
@classmethod
|
||||
@property
|
||||
def description(cls) -> str: # noqa
|
||||
return "获取榜单等媒体数据列表"
|
||||
|
||||
@classmethod
|
||||
@property
|
||||
def data(cls) -> dict: # noqa
|
||||
return FetchMediasParams().model_dump()
|
||||
name = "获取媒体数据"
|
||||
description = "获取榜单等媒体数据列表"
|
||||
data = FetchMediasParams().model_dump()
|
||||
|
||||
@property
|
||||
def success(self) -> bool:
|
||||
|
||||
@@ -40,20 +40,9 @@ class FetchRssAction(BaseAction):
|
||||
self._rss_torrents = []
|
||||
self._has_error = False
|
||||
|
||||
@classmethod
|
||||
@property
|
||||
def name(cls) -> str: # noqa
|
||||
return "获取RSS资源"
|
||||
|
||||
@classmethod
|
||||
@property
|
||||
def description(cls) -> str: # noqa
|
||||
return "订阅RSS地址获取资源"
|
||||
|
||||
@classmethod
|
||||
@property
|
||||
def data(cls) -> dict: # noqa
|
||||
return FetchRssParams().model_dump()
|
||||
name = "获取RSS资源"
|
||||
description = "订阅RSS地址获取资源"
|
||||
data = FetchRssParams().model_dump()
|
||||
|
||||
@property
|
||||
def success(self) -> bool:
|
||||
|
||||
@@ -41,20 +41,9 @@ class FetchTorrentsAction(BaseAction):
|
||||
super().__init__(action_id)
|
||||
self._torrents = []
|
||||
|
||||
@classmethod
|
||||
@property
|
||||
def name(cls) -> str: # noqa
|
||||
return "搜索站点资源"
|
||||
|
||||
@classmethod
|
||||
@property
|
||||
def description(cls) -> str: # noqa
|
||||
return "搜索站点种子资源列表"
|
||||
|
||||
@classmethod
|
||||
@property
|
||||
def data(cls) -> dict: # noqa
|
||||
return FetchTorrentsParams().model_dump()
|
||||
name = "搜索站点资源"
|
||||
description = "搜索站点种子资源列表"
|
||||
data = FetchTorrentsParams().model_dump()
|
||||
|
||||
@property
|
||||
def success(self) -> bool:
|
||||
|
||||
@@ -32,20 +32,9 @@ class FilterMediasAction(BaseAction):
|
||||
super().__init__(action_id)
|
||||
self._medias = []
|
||||
|
||||
@classmethod
|
||||
@property
|
||||
def name(cls) -> str: # noqa
|
||||
return "过滤媒体数据"
|
||||
|
||||
@classmethod
|
||||
@property
|
||||
def description(cls) -> str: # noqa
|
||||
return "对媒体数据列表进行过滤"
|
||||
|
||||
@classmethod
|
||||
@property
|
||||
def data(cls) -> dict: # noqa
|
||||
return FilterMediasParams().model_dump()
|
||||
name = "过滤媒体数据"
|
||||
description = "对媒体数据列表进行过滤"
|
||||
data = FilterMediasParams().model_dump()
|
||||
|
||||
@property
|
||||
def success(self) -> bool:
|
||||
|
||||
@@ -37,20 +37,9 @@ class FilterTorrentsAction(BaseAction):
|
||||
super().__init__(action_id)
|
||||
self._torrents = []
|
||||
|
||||
@classmethod
|
||||
@property
|
||||
def name(cls) -> str: # noqa
|
||||
return "过滤资源"
|
||||
|
||||
@classmethod
|
||||
@property
|
||||
def description(cls) -> str: # noqa
|
||||
return "对资源列表数据进行过滤"
|
||||
|
||||
@classmethod
|
||||
@property
|
||||
def data(cls) -> dict: # noqa
|
||||
return FilterTorrentsParams().model_dump()
|
||||
name = "过滤资源"
|
||||
description = "对资源列表数据进行过滤"
|
||||
data = FilterTorrentsParams().model_dump()
|
||||
|
||||
@property
|
||||
def success(self) -> bool:
|
||||
|
||||
@@ -27,20 +27,9 @@ class InvokePluginAction(BaseAction):
|
||||
super().__init__(action_id)
|
||||
self._success = False
|
||||
|
||||
@classmethod
|
||||
@property
|
||||
def name(cls) -> str: # noqa
|
||||
return "调用插件"
|
||||
|
||||
@classmethod
|
||||
@property
|
||||
def description(cls) -> str: # noqa
|
||||
return "调用插件提供的动作"
|
||||
|
||||
@classmethod
|
||||
@property
|
||||
def data(cls) -> dict: # noqa
|
||||
return InvokePluginParams().model_dump()
|
||||
name = "调用插件"
|
||||
description = "调用插件提供的动作"
|
||||
data = InvokePluginParams().model_dump()
|
||||
|
||||
@property
|
||||
def success(self) -> bool:
|
||||
|
||||
@@ -9,20 +9,9 @@ class NoteAction(BaseAction):
|
||||
|
||||
contract = {}
|
||||
|
||||
@classmethod
|
||||
@property
|
||||
def name(cls) -> str: # noqa
|
||||
return "备注"
|
||||
|
||||
@classmethod
|
||||
@property
|
||||
def description(cls) -> str: # noqa
|
||||
return "给工作流添加备注"
|
||||
|
||||
@classmethod
|
||||
@property
|
||||
def data(cls) -> dict: # noqa
|
||||
return {}
|
||||
name = "备注"
|
||||
description = "给工作流添加备注"
|
||||
data = {}
|
||||
|
||||
@property
|
||||
def success(self) -> bool:
|
||||
|
||||
@@ -34,20 +34,9 @@ class ScanFileAction(BaseAction):
|
||||
self._fileitems = []
|
||||
self._has_error = False
|
||||
|
||||
@classmethod
|
||||
@property
|
||||
def name(cls) -> str: # noqa
|
||||
return "扫描目录"
|
||||
|
||||
@classmethod
|
||||
@property
|
||||
def description(cls) -> str: # noqa
|
||||
return "扫描目录文件到队列"
|
||||
|
||||
@classmethod
|
||||
@property
|
||||
def data(cls) -> dict: # noqa
|
||||
return ScanFileParams().model_dump()
|
||||
name = "扫描目录"
|
||||
description = "扫描目录文件到队列"
|
||||
data = ScanFileParams().model_dump()
|
||||
|
||||
@property
|
||||
def success(self) -> bool:
|
||||
|
||||
@@ -30,20 +30,9 @@ class ScrapeFileAction(BaseAction):
|
||||
self._scraped_files = []
|
||||
self._has_error = False
|
||||
|
||||
@classmethod
|
||||
@property
|
||||
def name(cls) -> str: # noqa
|
||||
return "刮削文件"
|
||||
|
||||
@classmethod
|
||||
@property
|
||||
def description(cls) -> str: # noqa
|
||||
return "刮削媒体信息和图片"
|
||||
|
||||
@classmethod
|
||||
@property
|
||||
def data(cls) -> dict: # noqa
|
||||
return ScrapeFileParams().model_dump()
|
||||
name = "刮削文件"
|
||||
description = "刮削媒体信息和图片"
|
||||
data = ScrapeFileParams().model_dump()
|
||||
|
||||
@property
|
||||
def success(self) -> bool:
|
||||
|
||||
@@ -19,20 +19,9 @@ class SendEventAction(BaseAction):
|
||||
|
||||
contract = {}
|
||||
|
||||
@classmethod
|
||||
@property
|
||||
def name(cls) -> str: # noqa
|
||||
return "发送事件"
|
||||
|
||||
@classmethod
|
||||
@property
|
||||
def description(cls) -> str: # noqa
|
||||
return "发送任务执行事件"
|
||||
|
||||
@classmethod
|
||||
@property
|
||||
def data(cls) -> dict: # noqa
|
||||
return SendEventParams().model_dump()
|
||||
name = "发送事件"
|
||||
description = "发送任务执行事件"
|
||||
data = SendEventParams().model_dump()
|
||||
|
||||
@property
|
||||
def success(self) -> bool:
|
||||
|
||||
@@ -27,20 +27,9 @@ class SendMessageAction(BaseAction):
|
||||
def __init__(self, action_id: str):
|
||||
super().__init__(action_id)
|
||||
|
||||
@classmethod
|
||||
@property
|
||||
def name(cls) -> str: # noqa
|
||||
return "发送消息"
|
||||
|
||||
@classmethod
|
||||
@property
|
||||
def description(cls) -> str: # noqa
|
||||
return "发送任务执行消息"
|
||||
|
||||
@classmethod
|
||||
@property
|
||||
def data(cls) -> dict: # noqa
|
||||
return SendMessageParams().model_dump()
|
||||
name = "发送消息"
|
||||
description = "发送任务执行消息"
|
||||
data = SendMessageParams().model_dump()
|
||||
|
||||
@property
|
||||
def success(self) -> bool:
|
||||
|
||||
@@ -41,20 +41,9 @@ class TransferFileAction(BaseAction):
|
||||
self._fileitems = []
|
||||
self._has_error = False
|
||||
|
||||
@classmethod
|
||||
@property
|
||||
def name(cls) -> str: # noqa
|
||||
return "整理文件"
|
||||
|
||||
@classmethod
|
||||
@property
|
||||
def description(cls) -> str: # noqa
|
||||
return "整理队列中的文件"
|
||||
|
||||
@classmethod
|
||||
@property
|
||||
def data(cls) -> dict: # noqa
|
||||
return TransferFileParams().model_dump()
|
||||
name = "整理文件"
|
||||
description = "整理队列中的文件"
|
||||
data = TransferFileParams().model_dump()
|
||||
|
||||
@property
|
||||
def success(self) -> bool:
|
||||
|
||||
+6
-3
@@ -3,7 +3,10 @@
|
||||
FROM ghcr.io/astral-sh/uv:0.12.5@sha256:e85be844203885286c60ffad8a858d48afb6c5a5c237ca0e67f12e74b8f174b1 AS uv
|
||||
|
||||
|
||||
FROM python:3.12.13-slim-bookworm AS base
|
||||
FROM rclone/rclone:1.75.0@sha256:b06aed988cf5967de7c25be5925240983981c757f4ed1ac9d2fa659d51d60548 AS rclone
|
||||
|
||||
|
||||
FROM python:3.14.7-slim-trixie@sha256:ce40764625a4ff50df3548277632e7f96c4e77fe75fa848aae9885476e7df5a4 AS base
|
||||
|
||||
|
||||
# 准备外部制品所需的最小工具集
|
||||
@@ -45,7 +48,7 @@ RUN apt-get update \
|
||||
-o /usr/share/postgresql-common/pgdg/apt.postgresql.org.asc \
|
||||
&& chmod 0644 /usr/share/postgresql-common/pgdg/apt.postgresql.org.asc \
|
||||
&& printf '%s\n' \
|
||||
'deb [signed-by=/usr/share/postgresql-common/pgdg/apt.postgresql.org.asc] https://apt.postgresql.org/pub/repos/apt bookworm-pgdg main' \
|
||||
'deb [signed-by=/usr/share/postgresql-common/pgdg/apt.postgresql.org.asc] https://apt.postgresql.org/pub/repos/apt trixie-pgdg main' \
|
||||
> /etc/apt/sources.list.d/pgdg.list \
|
||||
&& apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
@@ -78,7 +81,6 @@ RUN apt-get update \
|
||||
libchromaprint-tools \
|
||||
libjemalloc2 \
|
||||
&& dpkg-reconfigure --frontend noninteractive tzdata \
|
||||
&& curl https://rclone.org/install.sh | bash \
|
||||
&& ln -s /usr/lib/*-linux-gnu/libjemalloc.so.2 /usr/local/lib/libjemalloc.so \
|
||||
&& apt-get autoremove -y \
|
||||
&& apt-get clean \
|
||||
@@ -220,6 +222,7 @@ ENV LD_PRELOAD="/usr/local/lib/libjemalloc.so" \
|
||||
# 引入支持 amr 编码的静态 ffmpeg
|
||||
COPY --from=mwader/static-ffmpeg:8.1.1 /ffmpeg /usr/local/bin/
|
||||
COPY --from=mwader/static-ffmpeg:8.1.1 /ffprobe /usr/local/bin/
|
||||
COPY --from=rclone /usr/local/bin/rclone /usr/bin/rclone
|
||||
|
||||
# python 环境
|
||||
COPY --from=prepare_venv --chmod=777 ${VENV_PATH} ${VENV_PATH}
|
||||
|
||||
+6
-6
@@ -104,7 +104,7 @@ Usage:
|
||||
|
||||
Options:
|
||||
deps:
|
||||
--python PYTHON 用于创建虚拟环境的 Python 解释器,默认自动选择本地 3.12+ 版本
|
||||
--python PYTHON 用于创建虚拟环境的 Python 解释器,默认自动选择本地 3.14+ 版本
|
||||
--venv PATH 虚拟环境目录,默认 ./venv
|
||||
--recreate 删除并重建虚拟环境
|
||||
--config-dir PATH 指定配置目录
|
||||
@@ -144,7 +144,7 @@ show_setup_help() {
|
||||
Usage: moviepilot setup [OPTIONS]
|
||||
|
||||
Options:
|
||||
--python PYTHON 用于创建虚拟环境的 Python 解释器,默认自动选择本地 3.12+ 版本
|
||||
--python PYTHON 用于创建虚拟环境的 Python 解释器,默认自动选择本地 3.14+ 版本
|
||||
--venv PATH 虚拟环境目录,默认 ./venv
|
||||
--recreate 删除并重建虚拟环境
|
||||
--frontend-version TAG 前端版本,默认使用 version.py 中的 FRONTEND_VERSION
|
||||
@@ -190,7 +190,7 @@ Options:
|
||||
--ref REF 后端 Git 版本,默认 latest
|
||||
--frontend-version TAG 前端版本,默认使用 version.py 中的 FRONTEND_VERSION
|
||||
--node-version VER 本地 Node 运行时版本,默认 20.12.1
|
||||
--python PYTHON 用于安装后端依赖的 Python 解释器,默认自动选择本地 3.12+ 版本
|
||||
--python PYTHON 用于安装后端依赖的 Python 解释器,默认自动选择本地 3.14+ 版本
|
||||
--venv PATH 虚拟环境目录,默认 ./venv
|
||||
--recreate 删除并重建虚拟环境
|
||||
--skip-resources 更新 all 时跳过资源同步
|
||||
@@ -258,7 +258,7 @@ python_version_ok() {
|
||||
local python_bin="$1"
|
||||
"$python_bin" - <<'PY' >/dev/null 2>&1
|
||||
import sys
|
||||
raise SystemExit(0 if sys.version_info >= (3, 12) else 1)
|
||||
raise SystemExit(0 if sys.version_info >= (3, 14) else 1)
|
||||
PY
|
||||
}
|
||||
|
||||
@@ -376,7 +376,7 @@ require_bootstrap_python() {
|
||||
if [ -n "$python_bin" ]; then
|
||||
return 0
|
||||
fi
|
||||
echo "未找到可用的 Python 3.12+ 解释器,请先安装 Python 3.12 或更高版本" >&2
|
||||
echo "未找到可用的 Python 3.14+ 解释器,请先安装 Python 3.14 或更高版本" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
@@ -427,7 +427,7 @@ bootstrap_python_for_args() {
|
||||
local external_python
|
||||
external_python="$(find_external_python "$target_venv" || true)"
|
||||
if [ -z "$external_python" ]; then
|
||||
echo "重建虚拟环境需要 venv 外部的 Python 3.12+ 解释器" >&2
|
||||
echo "重建虚拟环境需要 venv 外部的 Python 3.14+ 解释器" >&2
|
||||
return 1
|
||||
fi
|
||||
printf '%s\n' "$external_python"
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
name = "moviepilot"
|
||||
# MoviePilot 不作为 Python 包发布,产品版本继续由 version.py 管理。
|
||||
version = "0"
|
||||
requires-python = ">=3.12"
|
||||
requires-python = ">=3.14"
|
||||
dependencies = [
|
||||
"aiofiles~=25.1.0",
|
||||
"aioshutil~=1.6",
|
||||
|
||||
@@ -8,6 +8,8 @@ asyncio_default_fixture_loop_scope = function
|
||||
# 仅对「无法在本仓修复根因」的已知上游/三方弃用告警做精确忽略,保持测试输出干净、
|
||||
# 让本仓自身的新告警更醒目。本仓代码引发的告警一律不在此忽略,应在源码/用例处修复。
|
||||
filterwarnings =
|
||||
ignore:'_UnionGenericAlias' is deprecated and slated for removal in Python 3\.17:DeprecationWarning:google\.genai\.types
|
||||
ignore:invalid escape sequence '\\&':SyntaxWarning:oss2\.api
|
||||
ignore:datetime.datetime.utcfromtimestamp\(\) is deprecated:DeprecationWarning
|
||||
ignore:'crypt' is deprecated:DeprecationWarning
|
||||
ignore:'audioop' is deprecated:DeprecationWarning
|
||||
|
||||
+11
-11
@@ -178,7 +178,7 @@ python_version_ok() {
|
||||
local python_bin="$1"
|
||||
"$python_bin" - <<'PY' >/dev/null 2>&1
|
||||
import sys
|
||||
raise SystemExit(0 if sys.version_info >= (3, 12) else 1)
|
||||
raise SystemExit(0 if sys.version_info >= (3, 14) else 1)
|
||||
PY
|
||||
}
|
||||
|
||||
@@ -228,20 +228,20 @@ find_uv_python() {
|
||||
python_install_hint() {
|
||||
case "$OS_NAME" in
|
||||
macOS)
|
||||
echo "脚本已尝试自动安装 Git、curl 和 Python 3.12+。" >&2
|
||||
echo "如果自动安装失败,请先安装 Homebrew,或手动执行:brew install git curl python@3.12" >&2
|
||||
echo "脚本已尝试自动安装 Git、curl 和 Python 3.14+。" >&2
|
||||
echo "如果自动安装失败,请先安装 Homebrew,或手动执行:brew install git curl python@3.14" >&2
|
||||
;;
|
||||
Linux*)
|
||||
echo "脚本已尝试自动安装 Git、curl 和 Python 3.12+。" >&2
|
||||
echo "如果自动安装失败,请先安装 Git、curl、Python 3.12+。" >&2
|
||||
echo "例如 Debian/Ubuntu: sudo apt install git curl python3.12" >&2
|
||||
echo "例如 Fedora/RHEL: sudo dnf install git curl python3.12" >&2
|
||||
echo "脚本已尝试自动安装 Git、curl 和 Python 3.14+。" >&2
|
||||
echo "如果自动安装失败,请先安装 Git、curl、Python 3.14+。" >&2
|
||||
echo "例如 Debian/Ubuntu: sudo apt install git curl python3.14" >&2
|
||||
echo "例如 Fedora/RHEL: sudo dnf install git curl python3.14" >&2
|
||||
;;
|
||||
Windows)
|
||||
echo "推荐在 WSL、Linux 或 macOS 终端中运行此脚本。" >&2
|
||||
;;
|
||||
*)
|
||||
echo "请先安装 Git、curl、Python 3.12+。" >&2
|
||||
echo "请先安装 Git、curl、Python 3.14+。" >&2
|
||||
;;
|
||||
esac
|
||||
}
|
||||
@@ -415,11 +415,11 @@ ensure_python() {
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "==> 未找到可用的 Python 3.12+,开始自动安装 Python 3.12"
|
||||
uv python install 3.12
|
||||
echo "==> 未找到可用的 Python 3.14+,开始自动安装 Python 3.14"
|
||||
uv python install 3.14
|
||||
PYTHON_BIN="$(find_uv_python "$(command -v uv)" || true)"
|
||||
if [[ -z "$PYTHON_BIN" ]] || ! python_version_ok "$PYTHON_BIN"; then
|
||||
echo "自动安装 Python 3.12 失败。" >&2
|
||||
echo "自动安装 Python 3.14 失败。" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -8,11 +8,11 @@ PROJECT_ROOT=""
|
||||
PYTHON_BIN=""
|
||||
COLLECTOR_PATH=""
|
||||
|
||||
# 判断候选 Python 是否满足 3.11 最低版本。
|
||||
# 判断候选 Python 是否满足 3.14 最低版本。
|
||||
python_version_ok() {
|
||||
"$1" - <<'PY' >/dev/null 2>&1
|
||||
import sys
|
||||
raise SystemExit(0 if sys.version_info >= (3, 11) else 1)
|
||||
raise SystemExit(0 if sys.version_info >= (3, 14) else 1)
|
||||
PY
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ find_python() {
|
||||
"${PROJECT_ROOT:+$PROJECT_ROOT/venv/bin/python}" \
|
||||
"${PROJECT_ROOT:+$PROJECT_ROOT/.venv/bin/python}" \
|
||||
"${VIRTUAL_ENV:+$VIRTUAL_ENV/bin/python}" \
|
||||
python3.13 python3.12 python3.11 python3; do
|
||||
python3.14 python3; do
|
||||
[[ -n "$candidate" ]] || continue
|
||||
if [[ "$candidate" == */* ]]; then
|
||||
resolved="$candidate"
|
||||
|
||||
@@ -33,8 +33,8 @@ PUBLIC_DIR = ROOT / "public"
|
||||
RUNTIME_DIR = ROOT / ".runtime"
|
||||
NODE_DIR = RUNTIME_DIR / "node"
|
||||
INSTALL_ENV_FILE = ROOT / ".moviepilot.env"
|
||||
MIN_PYTHON_VERSION = (3, 12)
|
||||
SUPPORTED_PYTHON_TEXT = "Python 3.12+"
|
||||
MIN_PYTHON_VERSION = (3, 14)
|
||||
SUPPORTED_PYTHON_TEXT = "Python 3.14+"
|
||||
UV_VERSION = "0.12.5"
|
||||
|
||||
CONFIG_DIR = LEGACY_CONFIG_DIR
|
||||
@@ -2722,7 +2722,7 @@ def install_deps(*, python_bin: str, venv_dir: Path, recreate: bool) -> Path:
|
||||
venv_dir
|
||||
):
|
||||
raise RuntimeError(
|
||||
"重建虚拟环境需要使用 venv 外部的 Python 3.12+ 解释器。"
|
||||
"重建虚拟环境需要使用 venv 外部的 Python 3.14+ 解释器。"
|
||||
)
|
||||
uv_bin = require_uv()
|
||||
temporary_uv_dir: Optional[TemporaryDirectory[str]] = None
|
||||
@@ -3719,7 +3719,7 @@ def build_parser() -> argparse.ArgumentParser:
|
||||
install_parser.add_argument(
|
||||
"--python",
|
||||
default=DEFAULT_BOOTSTRAP_PYTHON,
|
||||
help="用于创建虚拟环境的 Python 解释器,默认自动选择本地 3.12+ 版本",
|
||||
help="用于创建虚拟环境的 Python 解释器,默认自动选择本地 3.14+ 版本",
|
||||
)
|
||||
install_parser.add_argument(
|
||||
"--venv", default=str(ROOT / "venv"), help="虚拟环境目录"
|
||||
@@ -3781,7 +3781,7 @@ def build_parser() -> argparse.ArgumentParser:
|
||||
setup_parser.add_argument(
|
||||
"--python",
|
||||
default=DEFAULT_BOOTSTRAP_PYTHON,
|
||||
help="用于创建虚拟环境的 Python 解释器,默认自动选择本地 3.12+ 版本",
|
||||
help="用于创建虚拟环境的 Python 解释器,默认自动选择本地 3.14+ 版本",
|
||||
)
|
||||
setup_parser.add_argument("--venv", default=str(ROOT / "venv"), help="虚拟环境目录")
|
||||
setup_parser.add_argument(
|
||||
@@ -3853,7 +3853,7 @@ def build_parser() -> argparse.ArgumentParser:
|
||||
update_parser.add_argument(
|
||||
"--python",
|
||||
default=DEFAULT_BOOTSTRAP_PYTHON,
|
||||
help="用于安装后端依赖的 Python 解释器,默认自动选择本地 3.12+ 版本",
|
||||
help="用于安装后端依赖的 Python 解释器,默认自动选择本地 3.14+ 版本",
|
||||
)
|
||||
update_parser.add_argument(
|
||||
"--venv", default=str(ROOT / "venv"), help="虚拟环境目录"
|
||||
|
||||
@@ -1485,7 +1485,7 @@
|
||||
"producers": [
|
||||
{
|
||||
"caller": "app.workflow.actions.send_event",
|
||||
"line": 46
|
||||
"line": 35
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -142,7 +142,7 @@ class BluRayTest(TestCase):
|
||||
|
||||
# 测试手动刮削
|
||||
logger.debug(f"测试手动刮削 {path}")
|
||||
mock_metadata_nfo.call_count = 0
|
||||
mock_metadata_nfo.reset_mock()
|
||||
ScrapingChain().scrape_metadata(
|
||||
fileitem=fileitem, meta=meta, mediainfo=mediainfo, overwrite=True
|
||||
)
|
||||
@@ -151,7 +151,7 @@ class BluRayTest(TestCase):
|
||||
|
||||
# 测试自动刮削
|
||||
logger.debug(f"测试自动刮削 {path}")
|
||||
mock_metadata_nfo.call_count = 0
|
||||
mock_metadata_nfo.reset_mock()
|
||||
ScrapingChain().scrape_metadata_event(
|
||||
Event(
|
||||
event_type=EventType.MetadataScrape,
|
||||
|
||||
@@ -5,13 +5,15 @@ from pathlib import Path
|
||||
|
||||
|
||||
def test_runtime_image_installs_postgresql_18_client_from_pgdg() -> None:
|
||||
"""Bookworm 镜像必须从签名的 PGDG 源安装固定主版本客户端。"""
|
||||
"""Trixie 镜像必须从签名的 PGDG 源安装固定主版本客户端。"""
|
||||
dockerfile = (
|
||||
Path(__file__).resolve().parents[1] / "docker" / "Dockerfile"
|
||||
).read_text(encoding="utf-8")
|
||||
|
||||
assert re.search(
|
||||
r"^FROM python:[^\s]+-slim-bookworm AS base$", dockerfile, re.MULTILINE
|
||||
r"^FROM python:[^\s]+-slim-trixie@sha256:[0-9a-f]{64} AS base$",
|
||||
dockerfile,
|
||||
re.MULTILINE,
|
||||
)
|
||||
assert "https://www.postgresql.org/media/keys/ACCC4CF8.asc" in dockerfile
|
||||
for curl_option in (
|
||||
@@ -25,7 +27,7 @@ def test_runtime_image_installs_postgresql_18_client_from_pgdg() -> None:
|
||||
keyring = "/usr/share/postgresql-common/pgdg/apt.postgresql.org.asc"
|
||||
assert f"chmod 0644 {keyring}" in dockerfile
|
||||
assert f"signed-by={keyring}" in dockerfile
|
||||
assert "https://apt.postgresql.org/pub/repos/apt bookworm-pgdg main" in dockerfile
|
||||
assert "https://apt.postgresql.org/pub/repos/apt trixie-pgdg main" in dockerfile
|
||||
assert re.search(r"^\s+postgresql-client-18 \\$", dockerfile, re.MULTILINE)
|
||||
assert not re.search(r"^\s+postgresql-client \\$", dockerfile, re.MULTILINE)
|
||||
|
||||
|
||||
@@ -65,12 +65,12 @@ class LocalSetupConfigDirTests(unittest.TestCase):
|
||||
with patch.object(module, "get_python_version", return_value=(3, 15, 0)):
|
||||
module.ensure_supported_python("python3.15")
|
||||
|
||||
def test_supported_python_rejects_versions_below_3_12(self):
|
||||
def test_supported_python_rejects_versions_below_3_14(self):
|
||||
module = load_local_setup_module()
|
||||
|
||||
with patch.object(module, "get_python_version", return_value=(3, 11, 9)):
|
||||
with self.assertRaisesRegex(RuntimeError, r"Python 3\.12\+"):
|
||||
module.ensure_supported_python("python3.11")
|
||||
with patch.object(module, "get_python_version", return_value=(3, 13, 9)):
|
||||
with self.assertRaisesRegex(RuntimeError, r"Python 3\.14\+"):
|
||||
module.ensure_supported_python("python3.13")
|
||||
|
||||
def test_install_deps_installs_browser_runtime(self):
|
||||
module = load_local_setup_module()
|
||||
|
||||
@@ -30,7 +30,7 @@ def test_recreate_commands_use_external_bootstrap_python(tmp_path, arguments):
|
||||
bin_dir = tmp_path / "bin"
|
||||
bin_dir.mkdir()
|
||||
record = tmp_path / "record"
|
||||
external_python = bin_dir / "python3.12"
|
||||
external_python = bin_dir / "python3.14"
|
||||
external_python.write_text(
|
||||
"#!/bin/sh\n"
|
||||
"if [ \"$1\" = \"-\" ]; then exit 0; fi\n"
|
||||
@@ -47,7 +47,7 @@ def test_recreate_commands_use_external_bootstrap_python(tmp_path, arguments):
|
||||
)
|
||||
venv_python.write_text(venv_script, encoding="utf-8")
|
||||
venv_python.chmod(venv_python.stat().st_mode | stat.S_IXUSR)
|
||||
venv_alias = venv_python.with_name("python3.12")
|
||||
venv_alias = venv_python.with_name("python3.14")
|
||||
venv_alias.write_text(venv_script, encoding="utf-8")
|
||||
venv_alias.chmod(venv_alias.stat().st_mode | stat.S_IXUSR)
|
||||
|
||||
@@ -117,7 +117,7 @@ def test_recreate_accepts_explicit_external_python(tmp_path):
|
||||
("update", "backend", "--recreate"),
|
||||
],
|
||||
)
|
||||
@pytest.mark.parametrize("python_option", [(), ("--python", "python3.12")])
|
||||
@pytest.mark.parametrize("python_option", [(), ("--python", "python3.14")])
|
||||
def test_recreate_excludes_custom_venv_from_external_bootstrap(
|
||||
tmp_path, arguments, python_option
|
||||
):
|
||||
@@ -135,8 +135,8 @@ def test_recreate_excludes_custom_venv_from_external_bootstrap(
|
||||
external_bin = tmp_path / "external-bin"
|
||||
external_bin.mkdir()
|
||||
record = tmp_path / "record"
|
||||
target_python = target_bin / "python3.12"
|
||||
external_python = external_bin / "python3.12"
|
||||
target_python = target_bin / "python3.14"
|
||||
external_python = external_bin / "python3.14"
|
||||
for python_path in (target_python, external_python):
|
||||
python_path.write_text(
|
||||
"#!/bin/sh\n"
|
||||
|
||||
@@ -5,7 +5,7 @@ import app
|
||||
|
||||
def test_app_installs_known_oss2_invalid_escape_warning_filter():
|
||||
"""
|
||||
app 初始化过滤器应覆盖 oss2 在 Python 3.12 下产生的无害转义警告。
|
||||
app 初始化过滤器应覆盖 oss2 的无效转义警告。
|
||||
"""
|
||||
app._filter_third_party_startup_warnings()
|
||||
action, message, category, module, lineno = warnings.filters[0]
|
||||
@@ -15,3 +15,18 @@ def test_app_installs_known_oss2_invalid_escape_warning_filter():
|
||||
assert category is SyntaxWarning
|
||||
assert module is None
|
||||
assert lineno == 0
|
||||
|
||||
|
||||
def test_app_installs_google_genai_python314_warning_filter():
|
||||
"""app 初始化过滤器应覆盖 Google GenAI SDK 的 Python 3.14 弃用警告。"""
|
||||
app._filter_third_party_startup_warnings()
|
||||
|
||||
assert any(
|
||||
action == "ignore"
|
||||
and message.match("'_UnionGenericAlias' is deprecated and slated for removal in Python 3.17")
|
||||
and category is DeprecationWarning
|
||||
and module is not None
|
||||
and module.match("google.genai.types")
|
||||
and lineno == 0
|
||||
for action, message, category, module, lineno in warnings.filters
|
||||
)
|
||||
|
||||
@@ -192,20 +192,9 @@ def test_execute_with_inputs_maps_contract_inputs_outputs_and_runtime(monkeypatc
|
||||
"outputs": [{"name": "downloads", "label": "下载任务", "kind": "list"}],
|
||||
}
|
||||
|
||||
@classmethod
|
||||
@property
|
||||
def name(cls) -> str:
|
||||
return "契约动作"
|
||||
|
||||
@classmethod
|
||||
@property
|
||||
def description(cls) -> str:
|
||||
return "测试契约动作"
|
||||
|
||||
@classmethod
|
||||
@property
|
||||
def data(cls) -> dict:
|
||||
return {}
|
||||
name = "契约动作"
|
||||
description = "测试契约动作"
|
||||
data = {}
|
||||
|
||||
@property
|
||||
def success(self) -> bool:
|
||||
@@ -254,5 +243,8 @@ def test_workflow_manager_list_actions_exposes_contract():
|
||||
|
||||
actions = manager.list_actions()
|
||||
|
||||
assert actions[0]["name"] == "获取RSS资源"
|
||||
assert actions[0]["description"] == "订阅RSS地址获取资源"
|
||||
assert isinstance(actions[0]["data"], dict)
|
||||
assert actions[0]["contract"]["outputs"][0]["name"] == "torrents"
|
||||
assert actions[0]["contract"]["condition_fields"][0]["label"] == "资源"
|
||||
|
||||
Reference in New Issue
Block a user