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