Repository: beetbox/beets

Issue:
Bring back NO_COLOR support?
Back in 2019 I added support for the [NO_COLOR environment variable](https://no-color.org/). This was removed in be290e54441987fdb80437f7982f4d5ad01bac59 as part of a refactor. Unless I'm mistaken it seems there's no way to disable colors from the command line anymore. It must be done in the config file. That's really inconvenient.

What was the reason for removing `NO_COLOR` support? Could it be brought back?


Discussion:

Patch:
diff --git a/beets/ui/__init__.py b/beets/ui/__init__.py
index 5e445613f..a98e8ee43 100644
--- a/beets/ui/__init__.py
+++ b/beets/ui/__init__.py
@@ -591,7 +591,7 @@ def colorize(color_name, text):
     """Colorize text if colored output is enabled. (Like _colorize but
     conditional.)
     """
-    if config["ui"]["color"]:
+    if config["ui"]["color"] and "NO_COLOR" not in os.environ:
         global COLORS
         if not COLORS:
             # Read all color configurations and set global variable COLORS.
@@ -858,7 +858,7 @@ def split_into_lines(string, width_tuple):
                     m.group("esc") + raw_word + RESET_COLOR
                     for raw_word in raw_words
                 ]
-            elif raw_words:
+            else:
                 # Pretext stops mid-word
                 if m.group("esc") != RESET_COLOR:
                     # Add the rest of the current word, with a reset after it
diff --git a/beets/ui/commands.py b/beets/ui/commands.py
index 63f25fca9..439858477 100755
--- a/beets/ui/commands.py
+++ b/beets/ui/commands.py
@@ -1460,12 +1460,6 @@ import_cmd.parser.add_option(
     dest="quiet",
     help="never prompt for input: skip albums instead",
 )
-import_cmd.parser.add_option(
-    "--quiet-fallback",
-    type="string",
-    dest="quiet_fallback",
-    help="decision in quiet mode when no strong match: skip or asis",
-)
 import_cmd.parser.add_option(
     "-l",
     "--log",
diff --git a/beetsplug/deezer.py b/beetsplug/deezer.py
index a2e16f59f..4c70d841e 100644
--- a/beetsplug/deezer.py
+++ b/beetsplug/deezer.py
@@ -63,19 +63,6 @@ class DeezerPlugin(MetadataSourcePlugin, BeetsPlugin):
 
         return [deezer_update_cmd]
 
-    def fetch_data(self, url):
-        try:
-            response = requests.get(url, timeout=10)
-            response.raise_for_status()
-            data = response.json()
-        except requests.exceptions.RequestException as e:
-            self._log.error("Error fetching data from {}\n Error: {}", url, e)
-            return None
-        if "error" in data:
-            self._log.error("Deezer API error: {}", data["error"]["message"])
-            return None
-        return data
-
     def album_for_id(self, album_id):
         """Fetch an album by its Deezer ID or URL and return an
         AlbumInfo object or None if the album is not found.
@@ -88,8 +75,13 @@ class DeezerPlugin(MetadataSourcePlugin, BeetsPlugin):
         deezer_id = self._get_id("album", album_id, self.id_regex)
         if deezer_id is None:
             return None
-        album_data = self.fetch_data(self.album_url + deezer_id)
-        if album_data is None:
+
+        album_data = requests.get(self.album_url + deezer_id).json()
+        if "error" in album_data:
+            self._log.debug(
+                f"Error fetching album {album_id}: "
+                f"{album_data['error']['message']}"
+            )
             return None
         contributors = album_data.get("contributors")
         if contributors is not None:
@@ -115,14 +107,9 @@ class DeezerPlugin(MetadataSourcePlugin, BeetsPlugin):
                 "Invalid `release_date` returned "
                 "by {} API: '{}'".format(self.data_source, release_date)
             )
-        tracks_obj = self.fetch_data(self.album_url + deezer_id + "/tracks")
-        if tracks_obj is None:
-            return None
-        try:
-            tracks_data = tracks_obj["data"]
-        except KeyError:
-            self._log.debug("Error fetching album tracks for {}", deezer_id)
-            tracks_data = None
+
+        tracks_obj = requests.get(self.album_url + deezer_id + "/tracks").json()
+        tracks_data = tracks_obj["data"]
         if not tracks_data:
             return None
         while "next" in tracks_obj:
@@ -205,26 +192,21 @@ class DeezerPlugin(MetadataSourcePlugin, BeetsPlugin):
             deezer_id = self._get_id("track", track_id, self.id_regex)
             if deezer_id is None:
                 return None
-            track_data = self.fetch_data(self.track_url + deezer_id)
-            if track_data is None:
+            track_data = requests.get(self.track_url + deezer_id).json()
+            if "error" in track_data:
+                self._log.debug(
+                    f"Error fetching track {track_id}: "
+                    f"{track_data['error']['message']}"
+                )
                 return None
         track = self._get_track(track_data)
 
         # Get album's tracks to set `track.index` (position on the entire
         # release) and `track.medium_total` (total number of tracks on
         # the track's disc).
-        album_tracks_obj = self.fetch_data(
+        album_tracks_data = requests.get(
             self.album_url + str(track_data["album"]["id"]) + "/tracks"
-        )
-        if album_tracks_obj is None:
-            return None
-        try:
-            album_tracks_data = album_tracks_obj["data"]
-        except KeyError:
-            self._log.debug(
-                "Error fetching album tracks for {}", track_data["album"]["id"]
-            )
-            return None
+        ).json()["data"]
         medium_total = 0
         for i, track_data in enumerate(album_tracks_data, start=1):
             if track_data["disk_number"] == track.medium:
@@ -301,9 +283,11 @@ class DeezerPlugin(MetadataSourcePlugin, BeetsPlugin):
                 self._log.debug("No deezer_track_id present for: {}", item)
                 continue
             try:
-                rank = self.fetch_data(
-                    f"{self.track_url}{deezer_track_id}"
-                ).get("rank")
+                rank = (
+                    requests.get(f"{self.track_url}{deezer_track_id}")
+                    .json()
+                    .get("rank")
+                )
                 self._log.debug(
                     "Deezer track: {} has {} rank", deezer_track_id, rank
                 )
diff --git a/beetsplug/mbsubmit.py b/beetsplug/mbsubmit.py
index d215e616c..e4c0f372e 100644
--- a/beetsplug/mbsubmit.py
+++ b/beetsplug/mbsubmit.py
@@ -21,13 +21,11 @@ implemented by MusicBrainz yet.
 [1] https://wiki.musicbrainz.org/History:How_To_Parse_Track_Listings
 """
 
-import subprocess
 
 from beets import ui
 from beets.autotag import Recommendation
 from beets.plugins import BeetsPlugin
 from beets.ui.commands import PromptChoice
-from beets.util import displayable_path
 from beetsplug.info import print_data
 
 
@@ -39,7 +37,6 @@ class MBSubmitPlugin(BeetsPlugin):
             {
                 "format": "$track. $title - $artist ($length)",
                 "threshold": "medium",
-                "picard_path": "picard",
             }
         )
 
@@ -59,21 +56,7 @@ class MBSubmitPlugin(BeetsPlugin):
 
     def before_choose_candidate_event(self, session, task):
         if task.rec <= self.threshold:
-            return [
-                PromptChoice("p", "Print tracks", self.print_tracks),
-                PromptChoice("o", "Open files with Picard", self.picard),
-            ]
-
-    def picard(self, session, task):
-        paths = []
-        for p in task.paths:
-            paths.append(displayable_path(p))
-        try:
-            picard_path = self.config["picard_path"].as_str()
-            subprocess.Popen([picard_path] + paths)
-            self._log.info("launched picard from\n{}", picard_path)
-        except OSError as exc:
-            self._log.error(f"Could not open picard, got error:\n{exc}")
+            return [PromptChoice("p", "Print tracks", self.print_tracks)]
 
     def print_tracks(self, session, task):
         for i in sorted(task.items, key=lambda i: i.track):
diff --git a/beetsplug/spotify.py b/beetsplug/spotify.py
index a825ef35a..705209de5 100644
--- a/beetsplug/spotify.py
+++ b/beetsplug/spotify.py
@@ -184,9 +184,6 @@ class SpotifyPlugin(MetadataSourcePlugin, BeetsPlugin):
         except requests.exceptions.ReadTimeout:
             self._log.error("ReadTimeout.")
             raise SpotifyAPIError("Request timed out.")
-        except requests.exceptions.ConnectionError as e:
-            self._log.error(f"Network error: {e}")
-            raise SpotifyAPIError("Network error.")
         except requests.exceptions.RequestException as e:
             if e.response.status_code == 401:
                 self._log.debug(
@@ -220,9 +217,6 @@ class SpotifyPlugin(MetadataSourcePlugin, BeetsPlugin):
             elif e.response.status_code == 503:
                 self._log.error("Service Unavailable.")
                 raise SpotifyAPIError("Service Unavailable.")
-            elif e.response.status_code == 502:
-                self._log.error("Bad Gateway.")
-                raise SpotifyAPIError("Bad Gateway.")
             elif e.response is not None:
                 raise SpotifyAPIError(
                     f"{self.data_source} API error:\n{e.response.text}\n"
diff --git a/docs/changelog.rst b/docs/changelog.rst
index 233423299..ae284b1cd 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -13,11 +13,10 @@ Major new features:
 
 * The beets importer UI received a major overhaul. Several new configuration
   options are available for customizing layout and colors: :ref:`ui_options`.
-  :bug:`3721`
+  :bug:`3721` :bug:`5028`
 
 New features:
 
-* :doc:`plugins/mbsubmit`: add new prompt choices helping further to submit unmatched tracks to MusicBrainz faster.
 * :doc:`plugins/spotify`: We now fetch track's ISRC, EAN, and UPC identifiers from Spotify when using the ``spotifysync`` command.
   :bug:`4992`
 * :doc:`plugins/discogs`: supply a value for the `cover_art_url` attribute, for use by `fetchart`.
@@ -146,14 +145,9 @@ New features:
   plugin which allows to replace fields based on a given library query.
 * :doc:`/plugins/lyrics`: Add LRCLIB as a new lyrics provider and a new
   `synced` option to prefer synced lyrics over plain lyrics.
-* :ref:`import-cmd`: Expose import.quiet_fallback as CLI option.
 
 Bug fixes:
 
-* :doc:`/plugins/spotify`: Improve handling of ConnectionError.
-* :doc:`/plugins/deezer`: Improve Deezer plugin error handling and set requests timeout to 10 seconds.
-  :bug:`4983`
-* :doc:`/plugins/spotify`: Add bad gateway (502) error handling.
 * :doc:`/plugins/spotify`: Add a limit of 3 retries, instead of retrying endlessly when the API is not available.
 * Fix a crash when the Spotify API timeouts or does not return a `Retry-After` interval.
   :bug:`4942`
diff --git a/docs/plugins/mbsubmit.rst b/docs/plugins/mbsubmit.rst
index 0e86ddc69..5cb9be8f1 100644
--- a/docs/plugins/mbsubmit.rst
+++ b/docs/plugins/mbsubmit.rst
@@ -1,40 +1,23 @@
 MusicBrainz Submit Plugin
 =========================
 
-The ``mbsubmit`` plugin provides extra prompt choices when an import session
-fails to find a good enough match for a release. Additionally, it provides an
-``mbsubmit`` command that prints the tracks of the current album in a format
-that is parseable by MusicBrainz's `track parser`_. The prompt choices are:
-
-- Print the tracks to stdout in a format suitable for MusicBrainz's `track
-  parser`_.
-
-- Open the program `Picard`_ with the unmatched folder as an input, allowing
-  you to start submitting the unmatched release to MusicBrainz with many input
-  fields already filled in, thanks to Picard reading the preexisting tags of
-  the files.
-
-For the last option, `Picard`_ is assumed to be installed and available on the
-machine including a ``picard`` executable. Picard developers list `download
-options`_. `other GNU/Linux distributions`_ may distribute Picard via their
-package manager as well.
+The ``mbsubmit`` plugin provides an extra prompt choice during an import
+session and a ``mbsubmit`` command that prints the tracks of the current
+album in a format that is parseable by MusicBrainz's `track parser`_.
 
 .. _track parser: https://wiki.musicbrainz.org/History:How_To_Parse_Track_Listings
-.. _Picard: https://picard.musicbrainz.org/
-.. _download options: https://picard.musicbrainz.org/downloads/
-.. _other GNU/Linux distributions: https://repology.org/project/picard-tagger/versions
 
 Usage
 -----
 
 Enable the ``mbsubmit`` plugin in your configuration (see :ref:`using-plugins`)
-and select one of the options mentioned above. Here the option ``Print tracks``
-choice is demonstrated::
+and select the ``Print tracks`` choice which is by default displayed when no
+strong recommendations are found for the album::
 
     No matching release found for 3 tracks.
     For help, see: https://beets.readthedocs.org/en/latest/faq.html#nomatch
     [U]se as-is, as Tracks, Group albums, Skip, Enter search, enter Id, aBort,
-    Print tracks, Open files with Picard? p
+    Print tracks? p
     01. An Obscure Track - An Obscure Artist (3:37)
     02. Another Obscure Track - An Obscure Artist (2:05)
     03. The Third Track - Another Obscure Artist (3:02)
@@ -70,11 +53,6 @@ file. The following options are available:
   Default: ``medium`` (causing the choice to be displayed for all albums that
   have a recommendation of medium strength or lower). Valid values: ``none``,
   ``low``, ``medium``, ``strong``.
-- **picard_path**: The path to the ``picard`` executable. Could be an absolute
-  path, and if not, ``$PATH`` is consulted. The default value is simply
-  ``picard``. Windows users will have to find and specify the absolute path to
-  their ``picard.exe``. That would probably be:
-  ``C:\Program Files\MusicBrainz Picard\picard.exe``.
 
 Please note that some values of the ``threshold`` configuration option might
 require other ``beets`` command line switches to be enabled in order to work as
diff --git a/docs/reference/cli.rst b/docs/reference/cli.rst
index a2997c70e..86c615dbb 100644
--- a/docs/reference/cli.rst
+++ b/docs/reference/cli.rst
@@ -93,10 +93,8 @@ Optional command flags:
 * Relatedly, the ``-q`` (quiet) option can help with large imports by
   autotagging without ever bothering to ask for user input. Whenever the
   normal autotagger mode would ask for confirmation, the quiet mode
-  performs a fallback action that can be configured using the
-  ``quiet_fallback`` configuration or ``--quiet-fallback`` CLI option.
-  By default it pessimistically ``skip``s the file.
-  Alternatively, it can be used as is, by configuring ``asis``.
+  pessimistically skips the album. The quiet mode also disables the tagger's
+  ability to resume interrupted imports.
 
 * Speaking of resuming interrupted imports, the tagger will prompt you if it
   seems like the last import of the directory was interrupted (by you or by


Test patch:
diff --git a/test/plugins/test_mbsubmit.py b/test/plugins/test_mbsubmit.py
index e495a73a9..6f9c81c04 100644
--- a/test/plugins/test_mbsubmit.py
+++ b/test/plugins/test_mbsubmit.py
@@ -45,7 +45,7 @@ class MBSubmitPluginTest(
 
         # Manually build the string for comparing the output.
         tracklist = (
-            "Open files with Picard? "
+            "Print tracks? "
             "01. Tag Title 1 - Tag Artist (0:01)\n"
             "02. Tag Title 2 - Tag Artist (0:01)"
         )
@@ -61,9 +61,7 @@ class MBSubmitPluginTest(
                 self.importer.run()
 
         # Manually build the string for comparing the output.
-        tracklist = (
-            "Open files with Picard? " "02. Tag Title 2 - Tag Artist (0:01)"
-        )
+        tracklist = "Print tracks? " "02. Tag Title 2 - Tag Artist (0:01)"
         self.assertIn(tracklist, output.getvalue())