aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-node
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-09-04 14:39:26 +0200
committerChristophe Besson <cbesson@gmail.com>2026-09-04 14:39:26 +0200
commit598158757a436eb6ea554f7ce34182dc96f8d851 (patch)
tree53f91f8a97ac750f44c8a09d9cca0a7cb6f4ba21 /packages/meshbay-node
parentc2eade6db582966fa7fc3dd037f952baf3ae1cb5 (diff)
downloadmeshbay-598158757a436eb6ea554f7ce34182dc96f8d851.tar.gz
fix(node): say so when MusicBrainz lookups are inert
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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DtfG7z6wHWj8RKHCvxQtY1
Diffstat (limited to 'packages/meshbay-node')
-rw-r--r--packages/meshbay-node/src/meshbay_node/musicbrainz.py14
-rw-r--r--packages/meshbay-node/tests/test_musicbrainz.py17
2 files changed, 30 insertions, 1 deletions
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()