Repository: beetbox/beets

Issue:
mbsync does not yield albuminfo_received / trackinfo_received as the docs suggest
### Problem

I am writing a plugin to override metadata and wish for it to apply when using `beet mbsync`. [The documentation for plugin events](http://beets.readthedocs.io/en/v1.4.6/dev/plugins.html#plugin-events) implies that listening for `albuminfo_received` and `trackinfo_received` will allow me to do just that.

However, running `beet -vv mbsync` for a given query does not show a log message saying that the event was ever sent.

```
$ beet -vv mbsync 'album:autobahn'
user configuration: /home/ilianaw/.config/beets/config.yaml
data directory: /home/ilianaw/.config/beets
plugin paths: /home/ilianaw/.config/beets/plug
Sending event: pluginload
/usr/lib/python3.6/site-packages/beets/plugins.py:114: DeprecationWarning: inspect.getargspec() is deprecated, use inspect.signature() or inspect.getfullargspec()
  argspec = inspect.getargspec(func)
library database: /home/ilianaw/.config/beets/library.db
library directory: /srv/media/ilianaw_music
Sending event: library_opened
Requesting MusicBrainz release f8fab745-79bd-31cd-be4b-ceb3d7e1e799
primary MB release type: album
mbsync: applying changes to Kraftwerk - Autobahn
Sending event: cli_exit
```

It appears (although I've not tested it) that this is a regression that occurred in https://github.com/beetbox/beets/commit/18a14e8005ee22073f1599fdc023e0eeda17eb1d. `mbsync` uses `hooks.album_for_mbid`, but only `hooks.albums_for_id` yields an `albuminfo_received`.

The solution that comes to my mind is to partially revert this commit by removing the `@plugins.notify_info_yielded` decorator on `albums_for_id`/`tracks_for_id` and using `plugins.send`. The use of `@plugins.notify_info_yielded` on `album_candidates`/`item_candidates` seems fine at first glance. If this sounds correct, I'll be happy to send a pull request.

### Setup

* OS: Fedora 28
* Python version: 3.6.5
* beets version: 1.4.6
* Turning off plugins made problem go away (yes/no): no

My configuration (output of `beet config`) is:

```yaml
plugins: chroma fetchart embedart mbsync scrub override
pluginpath: ~/.config/beets/plug
directory: /srv/media/ilianaw_music
ui:
  color: yes
paths:
  default: "$albumartist/$album%aunique{}/$track $title"
  singleton: "$artist/$title"
  comp: "Various Artists/$album%aunique{}/$track $title"
fetchart:
  sources:
    - amazon
    - coverart
  store_source: yes
embedart:
  remove_art_file: yes
```
(`override` is the plugin I am attempting to write.)


Discussion:
Thanks for your detective-work on this! I agree entirely with your astute diagnosis.

I've been trying to think of a way to salvage the decorator style, which is nice in itself, but I've been unable to come up with a better solution. So yes, I also concur with your idea to remove those decorators and replace them with explicit `send` calls---that seems like the most straightforward way to get both ID-based lookup functions to emit the right events.

Thank you again!

Resolving patch:
diff --git a/beets/autotag/hooks.py b/beets/autotag/hooks.py
--- a/beets/autotag/hooks.py
+++ b/beets/autotag/hooks.py
@@ -539,7 +539,10 @@ def album_for_mbid(release_id):
     if the ID is not found.
     """
     try:
-        return mb.album_for_id(release_id)
+        album = mb.album_for_id(release_id)
+        if album:
+            plugins.send(u'albuminfo_received', info=album)
+        return album
     except mb.MusicBrainzAPIError as exc:
         exc.log(log)
 
@@ -549,12 +552,14 @@ def track_for_mbid(recording_id):
     if the ID is not found.
     """
     try:
-        return mb.track_for_id(recording_id)
+        track = mb.track_for_id(recording_id)
+        if track:
+            plugins.send(u'trackinfo_received', info=track)
+        return track
     except mb.MusicBrainzAPIError as exc:
         exc.log(log)
 
 
-@plugins.notify_info_yielded(u'albuminfo_received')
 def albums_for_id(album_id):
     """Get a list of albums for an ID."""
     a = album_for_mbid(album_id)
@@ -562,10 +567,10 @@ def albums_for_id(album_id):
         yield a
     for a in plugins.album_for_id(album_id):
         if a:
+            plugins.send(u'albuminfo_received', info=a)
             yield a
 
 
-@plugins.notify_info_yielded(u'trackinfo_received')
 def tracks_for_id(track_id):
     """Get a list of tracks for an ID."""
     t = track_for_mbid(track_id)
@@ -573,6 +578,7 @@ def tracks_for_id(track_id):
         yield t
     for t in plugins.track_for_id(track_id):
         if t:
+            plugins.send(u'trackinfo_received', info=t)
             yield t
 
 


Test patch:
diff --git a/test/test_mbsync.py b/test/test_mbsync.py
--- a/test/test_mbsync.py
+++ b/test/test_mbsync.py
@@ -37,15 +37,15 @@ def tearDown(self):
         self.unload_plugins()
         self.teardown_beets()
 
-    @patch('beets.autotag.hooks.album_for_mbid')
-    @patch('beets.autotag.hooks.track_for_mbid')
-    def test_update_library(self, track_for_mbid, album_for_mbid):
-        album_for_mbid.return_value = \
+    @patch('beets.autotag.mb.album_for_id')
+    @patch('beets.autotag.mb.track_for_id')
+    def test_update_library(self, track_for_id, album_for_id):
+        album_for_id.return_value = \
             generate_album_info(
                 'album id',
                 [('track id', {'release_track_id': u'release track id'})]
             )
-        track_for_mbid.return_value = \
+        track_for_id.return_value = \
             generate_track_info(u'singleton track id',
                                 {'title': u'singleton info'})
 
@@ -65,7 +65,10 @@ def test_update_library(self, track_for_mbid, album_for_mbid):
         )
         self.lib.add(item)
 
-        self.run_command('mbsync')
+        with capture_log() as logs:
+            self.run_command('mbsync')
+        self.assertIn('Sending event: albuminfo_received', logs)
+        self.assertIn('Sending event: trackinfo_received', logs)
 
         item.load()
         self.assertEqual(item.title, u'singleton info')