From 598158757a436eb6ea554f7ce34182dc96f8d851 Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Fri, 4 Sep 2026 14:39:26 +0200 Subject: fix(node): say so when MusicBrainz lookups are inert MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Staying inert without a contact is the documented policy (module docstring, musicbay.md §3.1): the usage policy wants a contact in the User-Agent, so an unidentified client is never sent. Staying *silent* about it was not a decision — the operator sees Music tiles with no metadata or cover art and has nothing to search the logs for. Warns once per client rather than once per lookup, since the condition is constant for the client's lifetime. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01DtfG7z6wHWj8RKHCvxQtY1 --- packages/meshbay-node/src/meshbay_node/musicbrainz.py | 14 +++++++++++++- packages/meshbay-node/tests/test_musicbrainz.py | 17 +++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) (limited to 'packages/meshbay-node') diff --git a/packages/meshbay-node/src/meshbay_node/musicbrainz.py b/packages/meshbay-node/src/meshbay_node/musicbrainz.py index 59f3778..d5ca1e9 100644 --- a/packages/meshbay-node/src/meshbay_node/musicbrainz.py +++ b/packages/meshbay-node/src/meshbay_node/musicbrainz.py @@ -105,12 +105,24 @@ class MusicBrainzClient: self._client = httpx.AsyncClient(timeout=_TIMEOUT, transport=transport) self._rate_lock = asyncio.Lock() self._last_request_monotonic: float | None = None + self._warned_no_contact = False async def close(self) -> None: await self._client.aclose() async def _resolve_contact(self) -> str | None: - return self._owner_email or None + if self._owner_email: + return self._owner_email + # Staying inert is deliberate (see the module docstring), but doing it + # silently is not: the operator sees Music tiles with no metadata and + # nothing anywhere says why. Once per client, not per lookup. + if not self._warned_no_contact: + self._warned_no_contact = True + log.warning( + "MusicBrainz lookups are inert: the node owner's hub account has " + "no email on file, and the usage policy requires a contact in the " + "User-Agent. Music tiles will show no metadata or cover art.") + return None async def _pace(self) -> None: """Serializes every call through this client to >= _MIN_INTERVAL_SECS apart.""" diff --git a/packages/meshbay-node/tests/test_musicbrainz.py b/packages/meshbay-node/tests/test_musicbrainz.py index 843a691..cff7fea 100644 --- a/packages/meshbay-node/tests/test_musicbrainz.py +++ b/packages/meshbay-node/tests/test_musicbrainz.py @@ -246,3 +246,20 @@ async def test_calls_are_paced_at_least_min_interval_apart(): assert elapsed >= _MIN_INTERVAL_SECS * 0.9 await client.close() + + +@pytest.mark.asyncio +async def test_a_missing_contact_is_reported_once_not_silently(caplog): + """Staying inert is the policy; staying quiet about it left an operator with + blank Music tiles and nothing to search for.""" + def handle(request: httpx.Request) -> httpx.Response: # pragma: no cover + raise AssertionError("no request may be sent without a contact") + + client = MusicBrainzClient(owner_email="", transport=httpx.MockTransport(handle)) + with caplog.at_level("WARNING"): + await client.search_release("Anyone", "Anything") + await client.search_release("Anyone Else", "Anything Else") + + warnings = [r for r in caplog.records if "MusicBrainz lookups are inert" in r.message] + assert len(warnings) == 1, "warn once per client, not once per lookup" + await client.close() -- cgit v1.2.3