feat: 升级 V3 默认运行时至 Python 3.14 与 Debian Trixie (#6382)

This commit is contained in:
InfinityPacer
2026-08-22 07:16:35 +08:00
committed by GitHub
parent 3b1c0bb93d
commit 3a5947cc88
36 changed files with 149 additions and 1030 deletions
+7 -19
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+2 -2
View File
@@ -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
+6
View File
@@ -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 '\\&'",
+6 -19
View File
@@ -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:
"""
+3 -14
View File
@@ -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:
+3 -14
View File
@@ -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:
+3 -14
View File
@@ -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:
+3 -14
View File
@@ -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:
+3 -14
View File
@@ -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:
+3 -14
View File
@@ -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:
+3 -14
View File
@@ -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:
+3 -14
View File
@@ -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:
+3 -14
View File
@@ -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:
+3 -14
View File
@@ -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:
+3 -14
View File
@@ -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:
+3 -14
View File
@@ -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:
+3 -14
View File
@@ -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:
+3 -14
View File
@@ -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:
+3 -14
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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",
+2
View File
@@ -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
View File
@@ -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
}
+3 -3
View File
@@ -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"
+6 -6
View File
@@ -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="虚拟环境目录"
+1 -1
View File
@@ -1485,7 +1485,7 @@
"producers": [
{
"caller": "app.workflow.actions.send_event",
"line": 46
"line": 35
}
]
},
+2 -2
View File
@@ -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 -3
View File
@@ -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)
+4 -4
View File
@@ -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()
+5 -5
View File
@@ -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"
+16 -1
View File
@@ -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
)
+6 -14
View File
@@ -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"] == "资源"
Generated
+7 -718
View File
File diff suppressed because it is too large Load Diff