Fix issues with dynamically installing packages into the running server (#2318)
authorMarcel van der Veldt <m.vanderveldt@outlook.com>
Sun, 10 Aug 2025 23:40:44 +0000 (01:40 +0200)
committerGitHub <noreply@github.com>
Sun, 10 Aug 2025 23:40:44 +0000 (01:40 +0200)
* Always ensure uv is present for installing packages dynamically

* Add extra check for YTM to see if yt_dlp is useable

.github/workflows/release.yml
Dockerfile
Dockerfile.base
music_assistant/helpers/util.py
music_assistant/providers/ytmusic/__init__.py
pyproject.toml
requirements_all.txt

index abdad286f43cb4b05f7b170a461f670ceee81e08..a8c9ca1adf31c1a06a5ec594298e6106aa1f4484 100644 (file)
@@ -6,8 +6,8 @@ on:
 
 env:
   PYTHON_VERSION: "3.12"
-  BASE_IMAGE_VERSION_STABLE: "1.2.3"
-  BASE_IMAGE_VERSION_BETA: "1.3.0"
+  BASE_IMAGE_VERSION_STABLE: "1.3.1"
+  BASE_IMAGE_VERSION_BETA: "1.3.1"
 
 jobs:
   build-artifact:
index e3e3c91dbe6bc99f46fc46b4b2cefa896fc1abc2..de069a046051e79764f65d4dd2e9115b85171b2f 100644 (file)
@@ -8,8 +8,6 @@ FROM ghcr.io/music-assistant/base:$BASE_IMAGE_VERSION AS builder
 ADD dist dist
 COPY requirements_all.txt .
 
-# ensure UV is installed
-COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
 # create venv which will be copied to the final image
 ENV VIRTUAL_ENV=/app/venv
 RUN uv venv $VIRTUAL_ENV
index 0a64ba9c1f295ba1d35b64d915b8e9b86c49a786..d3ab4d4e1fb07507360b53919205c75aa63d648d 100644 (file)
@@ -24,6 +24,9 @@ RUN set -x \
 COPY --from=mwader/static-ffmpeg:7.1.1 /ffmpeg /usr/local/bin/
 COPY --from=mwader/static-ffmpeg:7.1.1 /ffprobe /usr/local/bin/
 
+# ensure UV is installed into the base image
+COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
+
 # Copy widevine client files to container
 RUN mkdir -p /usr/local/bin/widevine_cdm
 COPY widevine_cdm/* /usr/local/bin/widevine_cdm/
index f24d6051d26fbcf78ce175c07272131e149fa73a..a244e95855c22dd5f30ae23837148c9a17dbfea7 100644 (file)
@@ -424,32 +424,12 @@ def empty_queue[T](q: asyncio.Queue[T]) -> None:
             pass
 
 
-async def install_package(package: str, prefer_uv: bool = True) -> None:
+async def install_package(package: str) -> None:
     """Install package with pip, raise when install failed."""
     LOGGER.debug("Installing python package %s", package)
-    if await get_package_version("uv") is None:
-        # uv is not present on this system
-        prefer_uv = False
-    # determine args (either uv pip or regular pip)
-    if prefer_uv:
-        args = ["uv", "pip", "install", "--no-cache", "--find-links", HA_WHEELS, package]
-    else:
-        args = [
-            "pip",
-            "install",
-            "--no-cache-dir",
-            "--no-input",
-            "--find-links",
-            HA_WHEELS,
-            package,
-        ]
+    args = ["uv", "pip", "install", "--no-cache", "--find-links", HA_WHEELS, package]
     return_code, output = await check_output(*args)
     if return_code != 0:
-        if "Permission denied" in output.decode() and prefer_uv:
-            # try again with regular pip
-            # uv pip seems to have issues with permissions on docker installs
-            await install_package(package, prefer_uv=False)
-            return
         msg = f"Failed to install package {package}\n{output.decode()}"
         raise RuntimeError(msg)
 
index 0b021633cbc0d866c4226bdd9fe234ca78753df7..52a7544a5b69a59e03a13eceb4e4c62a5c7d7f20 100644 (file)
@@ -27,6 +27,7 @@ from music_assistant_models.errors import (
     InvalidDataError,
     LoginFailed,
     MediaNotFoundError,
+    SetupFailedError,
     UnplayableMediaError,
 )
 from music_assistant_models.media_items import (
@@ -1025,3 +1026,8 @@ class YoutubeMusicProvider(MusicProvider):
         # us from having to update MA to ensure this provider works.
         for package_name in PACKAGES_TO_INSTALL:
             await install_package(package_name)
+        # verify if the yt_dlp package is usable
+        try:
+            await asyncio.to_thread(importlib.import_module, "yt_dlp")
+        except ImportError:
+            raise SetupFailedError("Package yt_dlp failed to install")
index e22e2a65af4ec7938fbe3c45cffdaf0860d19365..cd5237bcd3a9e9bc0287dda956b448ba098aca15 100644 (file)
@@ -36,6 +36,7 @@ dependencies = [
   "xmltodict==0.14.2",
   "shortuuid==1.0.13",
   "zeroconf==0.147.0",
+  "uv>=0.8.0",
 ]
 description = "Music Assistant"
 license = {text = "Apache-2.0"}
index cfb2c3ca448e321e3e6ab00b515ea2c701e2ed5d..940c612fed818ddcec8882108f337cbce5d0b3f7 100644 (file)
@@ -54,6 +54,7 @@ soco==0.30.10
 soundcloudpy==0.1.4
 sxm==0.2.8
 unidecode==1.4.0
+uv>=0.8.0
 websocket-client==1.8.0
 xmltodict==0.14.2
 ytmusicapi==1.10.3