mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-07-12 16:02:35 +08:00
Rename package installer helper to package
This commit is contained in:
@@ -29,7 +29,7 @@ from requests import Response
|
|||||||
from app.core.cache import cached, is_fresh
|
from app.core.cache import cached, is_fresh
|
||||||
from app.core.config import settings
|
from app.core.config import settings
|
||||||
from app.db.systemconfig_oper import SystemConfigOper
|
from app.db.systemconfig_oper import SystemConfigOper
|
||||||
from app.helper.package_installer import PackageInstallRequest, build_package_install_strategies
|
from app.helper.package import PackageInstallRequest, build_package_install_strategies
|
||||||
from app.log import logger
|
from app.log import logger
|
||||||
from app.schemas.types import SystemConfigKey
|
from app.schemas.types import SystemConfigKey
|
||||||
from app.utils.http import RequestUtils, AsyncRequestUtils
|
from app.utils.http import RequestUtils, AsyncRequestUtils
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|||||||
curl \
|
curl \
|
||||||
wget \
|
wget \
|
||||||
git \
|
git \
|
||||||
|
gh \
|
||||||
busybox \
|
busybox \
|
||||||
tini \
|
tini \
|
||||||
cron \
|
cron \
|
||||||
|
|||||||
@@ -31,6 +31,10 @@ The application is structured as four distinct layers. Each layer has a defined
|
|||||||
|
|
||||||
## Layer Responsibilities and Boundaries
|
## Layer Responsibilities and Boundaries
|
||||||
|
|
||||||
|
### Shared File Placement Rule
|
||||||
|
|
||||||
|
Before creating a new file under `app/api/endpoints/`, `app/chain/`, `app/helper/`, or `app/utils/`, first check whether the capability belongs in an existing domain file. Prefer extending that file when the domain already exists. Create a new file only for a genuinely new domain or standalone reusable concern, and name it with a single noun according to `07-naming-conventions.md`.
|
||||||
|
|
||||||
### Entrypoint Layer
|
### Entrypoint Layer
|
||||||
|
|
||||||
**Directories:** `app/api/endpoints/`, `moviepilot` (CLI), `app/agent/`, scheduler callbacks, webhook handlers, message interactions.
|
**Directories:** `app/api/endpoints/`, `moviepilot` (CLI), `app/agent/`, scheduler callbacks, webhook handlers, message interactions.
|
||||||
@@ -41,7 +45,7 @@ The application is structured as four distinct layers. Each layer has a defined
|
|||||||
- Any logic that coordinates multiple modules, triggers events, touches caches, or combines workflows must be moved into `chain`.
|
- Any logic that coordinates multiple modules, triggers events, touches caches, or combines workflows must be moved into `chain`.
|
||||||
|
|
||||||
**Rules:**
|
**Rules:**
|
||||||
- Prefer adding new endpoints to an existing domain file. Create a new endpoint file only when introducing a new top-level resource domain.
|
- Prefer adding new endpoints to an existing domain file. Create a new endpoint file only when introducing a new top-level resource domain, and use a single-noun filename.
|
||||||
- After adding a new endpoint, register it in `app/api/apiv1.py`.
|
- After adding a new endpoint, register it in `app/api/apiv1.py`.
|
||||||
- Endpoints must not contain business logic that belongs in `chain`.
|
- Endpoints must not contain business logic that belongs in `chain`.
|
||||||
|
|
||||||
@@ -166,4 +170,4 @@ The application is structured as four distinct layers. Each layer has a defined
|
|||||||
| Few dozen lines of private logic in one chain or module | Private function in the same file; do not create a new helper |
|
| Few dozen lines of private logic in one chain or module | Private function in the same file; do not create a new helper |
|
||||||
| New module category or subtype | Also update `app/schemas/types.py` |
|
| New module category or subtype | Also update `app/schemas/types.py` |
|
||||||
|
|
||||||
*Last Updated: 2026-05-25*
|
*Last Updated: 2026-06-23*
|
||||||
|
|||||||
@@ -105,6 +105,8 @@ except:
|
|||||||
|
|
||||||
- One primary class per file is the norm for chains, modules, and helpers.
|
- One primary class per file is the norm for chains, modules, and helpers.
|
||||||
- Private helper functions in the same file are preferable to extracting a new helper for single-use logic.
|
- Private helper functions in the same file are preferable to extracting a new helper for single-use logic.
|
||||||
|
- Under `app/api/endpoints/`, `app/chain/`, `app/helper/`, and `app/utils/`, add code to an existing domain file whenever the domain already exists.
|
||||||
|
- New files under those directories must use a single noun filename such as `package.py`; avoid role-suffix names such as `package_installer.py` unless an established framework convention requires it.
|
||||||
- Keep files focused on one domain concern.
|
- Keep files focused on one domain concern.
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -118,4 +120,4 @@ except:
|
|||||||
- Do not add noisy markers like `# change starts here`, `# important`, or `# this is a fix`.
|
- Do not add noisy markers like `# change starts here`, `# important`, or `# this is a fix`.
|
||||||
- Do not write comments that restate what the code already clearly says.
|
- Do not write comments that restate what the code already clearly says.
|
||||||
|
|
||||||
*Last Updated: 2026-05-25*
|
*Last Updated: 2026-06-23*
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ All new code must follow these conventions. Consistent naming is how the codebas
|
|||||||
|
|
||||||
| Context | Convention | Examples |
|
| Context | Convention | Examples |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| Python source files | `snake_case.py` | `download_chain.py`, `qbittorrent.py` |
|
| Python source files | `snake_case.py` | `download.py`, `qbittorrent.py`, `package.py` |
|
||||||
|
| New domain files under `app/api/endpoints/`, `app/chain/`, `app/helper/`, `app/utils/` | Single noun `snake_case.py`; prefer an existing domain file before adding a new one | `package.py`, `plugin.py`, `torrent.py` |
|
||||||
| Module package directories | `snake_case/` | `qbittorrent/`, `synologychat/` |
|
| Module package directories | `snake_case/` | `qbittorrent/`, `synologychat/` |
|
||||||
| Test files | `test_<domain>.py` | `test_download_chain.py`, `test_subscribe_endpoint.py` |
|
| Test files | `test_<domain>.py` | `test_download_chain.py`, `test_subscribe_endpoint.py` |
|
||||||
| Alembic migrations | Auto-generated by Alembic; do not rename | `20240101_add_column.py` |
|
| Alembic migrations | Auto-generated by Alembic; do not rename | `20240101_add_column.py` |
|
||||||
@@ -99,4 +100,4 @@ All new code must follow these conventions. Consistent naming is how the codebas
|
|||||||
| `SystemConfigOper().get("RssUrls")` | `SystemConfigOper().get(SystemConfigKey.RssUrls)` |
|
| `SystemConfigOper().get("RssUrls")` | `SystemConfigOper().get(SystemConfigKey.RssUrls)` |
|
||||||
| `class subscribe_oper:` | `class SubscribeOper:` |
|
| `class subscribe_oper:` | `class SubscribeOper:` |
|
||||||
|
|
||||||
*Last Updated: 2026-05-25*
|
*Last Updated: 2026-06-23*
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from pathlib import Path
|
|||||||
ROOT = Path(__file__).resolve().parents[2]
|
ROOT = Path(__file__).resolve().parents[2]
|
||||||
sys.path.insert(0, str(ROOT))
|
sys.path.insert(0, str(ROOT))
|
||||||
|
|
||||||
from app.helper.package_installer import PackageInstallRequest, build_package_install_strategies
|
from app.helper.package import PackageInstallRequest, build_package_install_strategies
|
||||||
|
|
||||||
|
|
||||||
def sample(name: str, request: PackageInstallRequest) -> None:
|
def sample(name: str, request: PackageInstallRequest) -> None:
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
from app.helper.package_installer import (
|
from app.helper.package import (
|
||||||
PackageInstallRequest,
|
PackageInstallRequest,
|
||||||
build_package_install_env,
|
build_package_install_env,
|
||||||
build_package_install_strategies,
|
build_package_install_strategies,
|
||||||
@@ -106,7 +106,7 @@ def test_build_strategies_uses_pip_only_when_uv_missing(tmp_path):
|
|||||||
config_dir=tmp_path / "config",
|
config_dir=tmp_path / "config",
|
||||||
)
|
)
|
||||||
|
|
||||||
with patch("app.helper.package_installer._find_uv", return_value=None):
|
with patch("app.helper.package._find_uv", return_value=None):
|
||||||
strategies = build_package_install_strategies(request)
|
strategies = build_package_install_strategies(request)
|
||||||
|
|
||||||
assert [strategy.strategy_name for strategy in strategies] == ["pip:直连"]
|
assert [strategy.strategy_name for strategy in strategies] == ["pip:直连"]
|
||||||
|
|||||||
@@ -667,7 +667,7 @@ class TestPluginHelper:
|
|||||||
uv_bin.parent.mkdir(parents=True)
|
uv_bin.parent.mkdir(parents=True)
|
||||||
uv_bin.write_text("", encoding="utf-8")
|
uv_bin.write_text("", encoding="utf-8")
|
||||||
|
|
||||||
with patch("app.helper.package_installer._find_uv", return_value=uv_bin), \
|
with patch("app.helper.package._find_uv", return_value=uv_bin), \
|
||||||
patch.object(PluginHelper, "_PluginHelper__get_protected_runtime_packages", return_value={}), \
|
patch.object(PluginHelper, "_PluginHelper__get_protected_runtime_packages", return_value={}), \
|
||||||
patch.object(PluginHelper, "_PluginHelper__run_runtime_healthcheck", return_value=(True, "")), \
|
patch.object(PluginHelper, "_PluginHelper__run_runtime_healthcheck", return_value=(True, "")), \
|
||||||
patch("app.helper.plugin.SystemUtils.execute_with_subprocess", side_effect=fake_execute), \
|
patch("app.helper.plugin.SystemUtils.execute_with_subprocess", side_effect=fake_execute), \
|
||||||
@@ -830,7 +830,7 @@ class TestPluginHelper:
|
|||||||
return_value={}
|
return_value={}
|
||||||
):
|
):
|
||||||
with patch("app.helper.plugin.SystemUtils.execute_with_subprocess", side_effect=fake_execute):
|
with patch("app.helper.plugin.SystemUtils.execute_with_subprocess", side_effect=fake_execute):
|
||||||
with patch("app.helper.package_installer._find_uv", return_value=None):
|
with patch("app.helper.package._find_uv", return_value=None):
|
||||||
success, message = PluginHelper.pip_install_with_fallback(requirements_file)
|
success, message = PluginHelper.pip_install_with_fallback(requirements_file)
|
||||||
|
|
||||||
assert success
|
assert success
|
||||||
@@ -867,7 +867,7 @@ class TestPluginHelper:
|
|||||||
return_value={"fastapi": Version("0.115.14")}
|
return_value={"fastapi": Version("0.115.14")}
|
||||||
):
|
):
|
||||||
with patch("app.helper.plugin.SystemUtils.execute_with_subprocess", side_effect=fake_execute):
|
with patch("app.helper.plugin.SystemUtils.execute_with_subprocess", side_effect=fake_execute):
|
||||||
with patch("app.helper.package_installer._find_uv", return_value=None):
|
with patch("app.helper.package._find_uv", return_value=None):
|
||||||
success, message = PluginHelper.pip_install_with_fallback(requirements_file)
|
success, message = PluginHelper.pip_install_with_fallback(requirements_file)
|
||||||
|
|
||||||
assert success
|
assert success
|
||||||
@@ -913,7 +913,7 @@ class TestPluginHelper:
|
|||||||
return_value={"fastapi": Version("0.115.14")}
|
return_value={"fastapi": Version("0.115.14")}
|
||||||
):
|
):
|
||||||
with patch("app.helper.plugin.SystemUtils.execute_with_subprocess", side_effect=fake_execute):
|
with patch("app.helper.plugin.SystemUtils.execute_with_subprocess", side_effect=fake_execute):
|
||||||
with patch("app.helper.package_installer._find_uv", return_value=None):
|
with patch("app.helper.package._find_uv", return_value=None):
|
||||||
success, message = PluginHelper.pip_install_with_fallback(requirements_file)
|
success, message = PluginHelper.pip_install_with_fallback(requirements_file)
|
||||||
|
|
||||||
assert not success
|
assert not success
|
||||||
@@ -942,7 +942,7 @@ class TestPluginHelper:
|
|||||||
req = root / "plugin-requirements.txt"
|
req = root / "plugin-requirements.txt"
|
||||||
req.write_text("demo\n", encoding="utf-8")
|
req.write_text("demo\n", encoding="utf-8")
|
||||||
|
|
||||||
with patch("app.helper.package_installer._find_uv", return_value=None), \
|
with patch("app.helper.package._find_uv", return_value=None), \
|
||||||
patch.object(PluginHelper, "_PluginHelper__get_protected_runtime_packages", return_value={}), \
|
patch.object(PluginHelper, "_PluginHelper__get_protected_runtime_packages", return_value={}), \
|
||||||
patch.object(
|
patch.object(
|
||||||
PluginHelper,
|
PluginHelper,
|
||||||
@@ -990,7 +990,7 @@ class TestPluginHelper:
|
|||||||
uv_bin.parent.mkdir(parents=True)
|
uv_bin.parent.mkdir(parents=True)
|
||||||
uv_bin.write_text("", encoding="utf-8")
|
uv_bin.write_text("", encoding="utf-8")
|
||||||
|
|
||||||
with patch("app.helper.package_installer._find_uv", return_value=uv_bin), \
|
with patch("app.helper.package._find_uv", return_value=uv_bin), \
|
||||||
patch.object(PluginHelper, "_PluginHelper__get_protected_runtime_packages", return_value={}), \
|
patch.object(PluginHelper, "_PluginHelper__get_protected_runtime_packages", return_value={}), \
|
||||||
patch.object(
|
patch.object(
|
||||||
PluginHelper,
|
PluginHelper,
|
||||||
@@ -1014,7 +1014,7 @@ class TestPluginHelper:
|
|||||||
assert len(seen_install_commands) == 1
|
assert len(seen_install_commands) == 1
|
||||||
assert repair_calls
|
assert repair_calls
|
||||||
|
|
||||||
def test_repair_main_runtime_dependencies_uses_package_installer_semantics(self):
|
def test_repair_main_runtime_dependencies_uses_package_helper_semantics(self):
|
||||||
"""
|
"""
|
||||||
主运行环境恢复与插件安装使用同一套 cache、index、proxy 和安全日志语义。
|
主运行环境恢复与插件安装使用同一套 cache、index、proxy 和安全日志语义。
|
||||||
"""
|
"""
|
||||||
@@ -1037,7 +1037,7 @@ class TestPluginHelper:
|
|||||||
uv_bin.parent.mkdir(parents=True)
|
uv_bin.parent.mkdir(parents=True)
|
||||||
uv_bin.write_text("", encoding="utf-8")
|
uv_bin.write_text("", encoding="utf-8")
|
||||||
|
|
||||||
with patch("app.helper.package_installer._find_uv", return_value=uv_bin), \
|
with patch("app.helper.package._find_uv", return_value=uv_bin), \
|
||||||
patch("app.helper.plugin.settings.CONFIG_DIR", str(root / "config")), \
|
patch("app.helper.plugin.settings.CONFIG_DIR", str(root / "config")), \
|
||||||
patch("app.helper.plugin.settings.PACKAGE_CACHE_ROOT", str(root / "custom-package-cache")), \
|
patch("app.helper.plugin.settings.PACKAGE_CACHE_ROOT", str(root / "custom-package-cache")), \
|
||||||
patch("app.helper.plugin.settings.PIP_PROXY", "https://user:pass@mirror.example/simple"), \
|
patch("app.helper.plugin.settings.PIP_PROXY", "https://user:pass@mirror.example/simple"), \
|
||||||
|
|||||||
Reference in New Issue
Block a user