mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-06-23 16:44:54 +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.config import settings
|
||||
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.schemas.types import SystemConfigKey
|
||||
from app.utils.http import RequestUtils, AsyncRequestUtils
|
||||
|
||||
@@ -28,6 +28,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
curl \
|
||||
wget \
|
||||
git \
|
||||
gh \
|
||||
busybox \
|
||||
tini \
|
||||
cron \
|
||||
|
||||
@@ -31,6 +31,10 @@ The application is structured as four distinct layers. Each layer has a defined
|
||||
|
||||
## 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
|
||||
|
||||
**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`.
|
||||
|
||||
**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`.
|
||||
- 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 |
|
||||
| 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.
|
||||
- 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.
|
||||
|
||||
---
|
||||
@@ -118,4 +120,4 @@ except:
|
||||
- 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.
|
||||
|
||||
*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 |
|
||||
|---|---|---|
|
||||
| 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/` |
|
||||
| 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` |
|
||||
@@ -99,4 +100,4 @@ All new code must follow these conventions. Consistent naming is how the codebas
|
||||
| `SystemConfigOper().get("RssUrls")` | `SystemConfigOper().get(SystemConfigKey.RssUrls)` |
|
||||
| `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]
|
||||
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:
|
||||
|
||||
@@ -3,7 +3,7 @@ from __future__ import annotations
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
from app.helper.package_installer import (
|
||||
from app.helper.package import (
|
||||
PackageInstallRequest,
|
||||
build_package_install_env,
|
||||
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",
|
||||
)
|
||||
|
||||
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)
|
||||
|
||||
assert [strategy.strategy_name for strategy in strategies] == ["pip:直连"]
|
||||
|
||||
@@ -667,7 +667,7 @@ class TestPluginHelper:
|
||||
uv_bin.parent.mkdir(parents=True)
|
||||
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__run_runtime_healthcheck", return_value=(True, "")), \
|
||||
patch("app.helper.plugin.SystemUtils.execute_with_subprocess", side_effect=fake_execute), \
|
||||
@@ -830,7 +830,7 @@ class TestPluginHelper:
|
||||
return_value={}
|
||||
):
|
||||
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)
|
||||
|
||||
assert success
|
||||
@@ -867,7 +867,7 @@ class TestPluginHelper:
|
||||
return_value={"fastapi": Version("0.115.14")}
|
||||
):
|
||||
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)
|
||||
|
||||
assert success
|
||||
@@ -913,7 +913,7 @@ class TestPluginHelper:
|
||||
return_value={"fastapi": Version("0.115.14")}
|
||||
):
|
||||
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)
|
||||
|
||||
assert not success
|
||||
@@ -942,7 +942,7 @@ class TestPluginHelper:
|
||||
req = root / "plugin-requirements.txt"
|
||||
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,
|
||||
@@ -990,7 +990,7 @@ class TestPluginHelper:
|
||||
uv_bin.parent.mkdir(parents=True)
|
||||
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,
|
||||
@@ -1014,7 +1014,7 @@ class TestPluginHelper:
|
||||
assert len(seen_install_commands) == 1
|
||||
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 和安全日志语义。
|
||||
"""
|
||||
@@ -1037,7 +1037,7 @@ class TestPluginHelper:
|
||||
uv_bin.parent.mkdir(parents=True)
|
||||
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.PACKAGE_CACHE_ROOT", str(root / "custom-package-cache")), \
|
||||
patch("app.helper.plugin.settings.PIP_PROXY", "https://user:pass@mirror.example/simple"), \
|
||||
|
||||
Reference in New Issue
Block a user