typing: type mpdstats, thumbnails plugins - #6939
Conversation
|
Thank you for the PR! The changelog has not been updated, so here is a friendly reminder to check if you need to add an entry. |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## type-metadata-source-plugins #6939 +/- ##
================================================================
+ Coverage 75.99% 76.02% +0.02%
================================================================
Files 164 164
Lines 21585 21615 +30
Branches 3342 3342
================================================================
+ Hits 16404 16433 +29
+ Misses 4387 4386 -1
- Partials 794 796 +2
🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
grug see PR try make typing more clear in two plugins (mpdstats, thumbnails). goal good: make internal contract less spooky and more explicit, and update tests to match new narrower signatures.
Changes:
thumbnails: passartpath/album.pathinstead of whole Album when only path data needed; log missing art as warning.mpdstats: add typed MPD response shapes + typednow_playing, narrow state handling to known MPD player states.- update plugin tests to match new function signatures and path handling.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| beetsplug/thumbnails.py | type annotations + path-only params; choose URI getter and write thumbnails metadata |
| beetsplug/mpdstats.py | typed MPD client wrapper + typed now-playing state; path query encoding fix |
| test/plugins/test_thumbnails.py | update calls/asserts to pass artpath / album.path instead of Album object |
| test/plugins/test_mpdstats.py | remove unknown-status case; update expectations for narrowed state handling |
Suppressed comments (3)
beetsplug/mpdstats.py:58
- grug see
is_urltype saystrbut body still handlebytes, and tests pass bytes sometimes. make signature match real input so type checker not lie.
def is_url(path: str) -> bool:
"""Try to determine if the path is an URL."""
if isinstance(path, bytes): # if it's bytes, then it's a path
return False
return path.split("://", 1)[0] in ["http", "https"]
beetsplug/mpdstats.py:159
- grug think MPD currentsong can return dict with no
id(or empty dict).entry["id"]blow up KeyError. better be safe and use.get(..., "")so plugin not crash on weird MPD reply.
self._log.debug("returning: {}", result)
return result, entry["id"]
beetsplug/mpdstats.py:33
- grug see MPDStatus TypedDict not describe keys code actually use (
time,songid). alsostateshould stay required. can model with required base TypedDict plus total=False extension for optional fields.
class MPDStatus(TypedDict):
id: str
file: str
state: Literal["play", "pause", "stop"]
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
5324a42 to
ea70c51
Compare
cc060d4 to
c7dbd0d
Compare
ea70c51 to
f8349ad
Compare
c7dbd0d to
60d7763
Compare
1481471 to
211f4c3
Compare
60d7763 to
050c93e
Compare
211f4c3 to
8aa82f1
Compare
8aa82f1 to
20145a4
Compare
050c93e to
d4aced5
Compare
20145a4 to
dab3a68
Compare
71eea34 to
85affd9
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
Suppressed comments (4)
beetsplug/mpdstats.py:87
- The function is annotated as accepting
str, but it contains a runtime branch forbytes. This mismatch weakens the value of the annotation and will force type ignores/casts at call sites ifbytesare ever passed. Update the signature to acceptstr | bytes(or refactor away thebytesbranch if it’s no longer supported) so the type contract matches the implementation.
def is_url(path: str) -> bool:
"""Try to determine if the path is an URL."""
if isinstance(path, bytes): # if it's bytes, then it's a path
return False
beetsplug/mpdstats.py:315
run()passesstatus = self.mpd.status()(now typed asMPDStatus), but the handlers acceptJSONDict. This loses the benefit of the new typed MPD contracts and makes it easier to accidentally use fields not present for a given state. Consider typing these parameters asMPDStatus, and (optionally) introducing a narrower type for the"play"/"pause"case (e.g., a TypedDict with required"time"/"songid"as appropriate) to avoidNotRequiredfields being treated as always present.
def on_stop(self, status: JSONDict) -> None:
beetsplug/mpdstats.py:325
run()passesstatus = self.mpd.status()(now typed asMPDStatus), but the handlers acceptJSONDict. This loses the benefit of the new typed MPD contracts and makes it easier to accidentally use fields not present for a given state. Consider typing these parameters asMPDStatus, and (optionally) introducing a narrower type for the"play"/"pause"case (e.g., a TypedDict with required"time"/"songid"as appropriate) to avoidNotRequiredfields being treated as always present.
def on_pause(self, status: JSONDict) -> None:
beetsplug/mpdstats.py:329
run()passesstatus = self.mpd.status()(now typed asMPDStatus), but the handlers acceptJSONDict. This loses the benefit of the new typed MPD contracts and makes it easier to accidentally use fields not present for a given state. Consider typing these parameters asMPDStatus, and (optionally) introducing a narrower type for the"play"/"pause"case (e.g., a TypedDict with required"time"/"songid"as appropriate) to avoidNotRequiredfields being treated as always present.
def on_play(self, status: JSONDict) -> None:
85affd9 to
da994dc
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.
Suppressed comments (4)
beetsplug/thumbnails.py:203
- The explicit
f.close()is redundant inside awith open(...)context manager and should be removed to avoid confusion and keep the resource-management pattern consistent.
with open(syspath(outfilename), "w") as f:
f.write("[Desktop Entry]\n")
f.write(f"Icon=./{artfile.decode('utf-8')}")
f.close()
beetsplug/mpdstats.py:378
- This removes the previous fallback for unexpected/unknown MPD
statevalues and will now raiseAttributeError(crashing the loop) if MPD ever returns something outsideplay|pause|stop(e.g., protocol quirks, buggy servers, or unexpected mock/test data). Consider restoring a safe fallback (e.g.,handler = getattr(..., None)with a debug log) or explicitly raising a controlled error with context so the plugin fails predictably rather than viaAttributeError.
while True:
if "player" in events:
status = self.mpd.status()
getattr(self, f"on_{status['state']}")(status)
beetsplug/mpdstats.py:58
- The
MPDStatusTypedDict markssongandsongidas required even though MPD commonly omits these fields when stopped (and/or when the playlist is empty). This can make the type misleading and push callers toward unsafe indexing. Makingsong/songidNotRequired[...](and only treating them as required in theplay|pausecases) would better match actual response shapes.
song: str
songid: str
# below are only set when status is "play" or "pause"
time: NotRequired[str]
elapsed: NotRequired[str]
beetsplug/thumbnails.py:277
- When
g_file_get_urireturns NULL,uri_ptrwill be a null/None pointer; callingg_free(uri_ptr)here is unnecessary and can be error-prone withctypes(especially ifg_freeargtypes are set elsewhere or change later). Since there is nothing to free on the NULL path, remove theg_freecall in this branch and just raise the error.
if not uri_ptr:
libgio.g_free(uri_ptr)
raise RuntimeError(
f"No URI received from the gfile pointer for {displayable_path(path)}"
)
dab3a68 to
38f9195
Compare
da994dc to
e56f061
Compare
Part of #6924.
This PR is a typing and interface cleanup for
beetsplug/mpdstats.pyandbeetsplug/thumbnails.py. It does not add new features; it makes the code's internal contracts clearer and more explicit.In
mpdstats, MPD responses andnow_playingstate now have concrete types, path lookup uses the correct beets path shape, and player event handling is narrowed to known states. This makes the control flow easier to follow and reduces hidden assumptions in the plugin.In
thumbnails, the code stops passing fullAlbumobjects where onlyalbum.pathorartpathis needed. That separates album metadata from file-path operations, removes ambiguity around optionalalbum.artpath, and makes thumbnail generation steps more direct.Tests were updated to match the narrower method signatures and the corrected path handling.
High-level impact: safer internal APIs, clearer data flow, and lower risk of type-related bugs. The only visible behavior change is that missing album art is now logged as a warning instead of info.