From d83a9ef03d40039d2f9562137cb2e66453214e07 Mon Sep 17 00:00:00 2001 From: beefcarver Date: Fri, 30 Oct 2026 18:42:50 -0400 Subject: [PATCH] fix(mpris): keep the service importable without dbus-next The stub dbus_property() returned a bare function, so the read/write properties (@Volume.setter and friends) raised AttributeError at import time. That made any host without dbus-next -- macOS, Windows, minimal containers -- crash before the TUI could start, because app.py imports this module unconditionally. Replace the stub with a descriptor that supports .getter()/.setter(), and give the service a short label for the status bar. --- somafm_tui/mpris.py | 37 +++++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/somafm_tui/mpris.py b/somafm_tui/mpris.py index 9e21ffa..d5721c7 100644 --- a/somafm_tui/mpris.py +++ b/somafm_tui/mpris.py @@ -44,9 +44,39 @@ except Exception: # pragma: no cover def emit_properties_changed(self, *args: Any, **kwargs: Any) -> None: return None + class _StubProperty: # type: ignore[no-redef] + """Stand-in for ``dbus_next.service.dbus_property`` on D-Bus-less hosts. + + It has to behave like a descriptor that also supports ``.setter()``, + because the interfaces below define read/write properties with the + usual decorator idiom. Importing this module must never fail just + because there is no session bus (macOS, Windows, minimal containers). + """ + + def __init__(self, fget: Any = None) -> None: + self.fget = fget + self.fset: Any = None + + def getter(self, fget: Any) -> "_StubProperty": + self.fget = fget + return self + + def setter(self, fset: Any) -> "_StubProperty": + self.fset = fset + return self + + def __get__(self, obj: Any, objtype: Any = None) -> Any: + if obj is None or self.fget is None: + return self + return self.fget(obj) + + def __set__(self, obj: Any, value: Any) -> None: + if self.fset is not None: + self.fset(obj, value) + def dbus_property(*args: Any, **kwargs: Any): # type: ignore[no-redef] - def decorator(function): - return function + def decorator(function: Any) -> _StubProperty: + return _StubProperty(function) return decorator @@ -254,6 +284,9 @@ class _PlayerInterface(ServiceInterface): class MprisService: + #: Status-bar name for this backend (see :mod:`somafm_tui.mediasession`). + label = "mpris" + def __init__( self, actions: "queue.Queue[str]", enabled: bool = True ) -> None: