Repository: beetbox/beets

Issue:
acousticbrainz: Really small float values are stored as strings
### Problem

Using the acousticbrainz plugin (I don't know if it's specific to it, it might be a wider issue), when storing really small values, they end up formatted in an exponential style, e.g. `1.82764233614e-05`. As a result, the whole (flexible, in this case) attribute ends up being treated as a string - which forbids using ranges in query, for example.

Here's an example with the recording `9dcdb20b-3bdf-4d1f-842f-d3cb545bea28` on MusicBrainz. The `mood_happy` probability is really low on AcousticBrainz: http://acousticbrainz.org/api/v1/9dcdb20b-3bdf-4d1f-842f-d3cb545bea28/high-level?n=0 (field value is `0.0000182764233614`). In beets, it ends up being stored as `1.82764233614e-05`:

```sh
$ beet -vv acousticbrainz M83 Ludivine -f
user configuration: /root/.config/beets/config.yaml
data directory: /root/.config/beets
plugin paths: /root/.config/beets/beets-alternatives/beetsplug
Sending event: pluginload
lyrics: Disabling google source: no API key configured.
inline: adding item field disc_with_title
inline: adding album field spinnin_catalognum
library database: /root/.config/beets/beets.blb
library directory: /media/sao/Musique
Sending event: library_opened
acousticbrainz: getting data for: M83 - Junk - Ludivine
acousticbrainz: fetching URL: https://acousticbrainz.org/9dcdb20b-3bdf-4d1f-842f-d3cb545bea28/low-level
acousticbrainz: fetching URL: https://acousticbrainz.org/9dcdb20b-3bdf-4d1f-842f-d3cb545bea28/high-level
acousticbrainz: attribute danceable of M83 - Junk - Ludivine set to 0.00766881043091
acousticbrainz: attribute gender of M83 - Junk - Ludivine set to male
acousticbrainz: attribute genre_rosamerica of M83 - Junk - Ludivine set to cla
acousticbrainz: attribute mood_acoustic of M83 - Junk - Ludivine set to 0.995353162289
acousticbrainz: attribute mood_aggressive of M83 - Junk - Ludivine set to 0.042748786509
acousticbrainz: attribute mood_electronic of M83 - Junk - Ludivine set to 0.0935237780213
acousticbrainz: attribute mood_happy of M83 - Junk - Ludivine set to 1.82764233614e-05
acousticbrainz: attribute mood_party of M83 - Junk - Ludivine set to 0.0153690651059
acousticbrainz: attribute mood_relaxed of M83 - Junk - Ludivine set to 0.987963736057
acousticbrainz: attribute mood_sad of M83 - Junk - Ludivine set to 0.956809282303
acousticbrainz: attribute rhythm of M83 - Junk - Ludivine set to Tango
acousticbrainz: attribute tonal of M83 - Junk - Ludivine set to 0.435981780291
acousticbrainz: attribute voice_instrumental of M83 - Junk - Ludivine set to instrumental
acousticbrainz: attribute average_loudness of M83 - Junk - Ludivine set to 0.753472864628
acousticbrainz: attribute bpm of M83 - Junk - Ludivine set to 106.67628479
acousticbrainz: attribute chords_changes_rate of M83 - Junk - Ludivine set to 0.0346172600985
acousticbrainz: attribute chords_key of M83 - Junk - Ludivine set to D
acousticbrainz: attribute chords_number_rate of M83 - Junk - Ludivine set to 0.00487567018718
acousticbrainz: attribute chords_scale of M83 - Junk - Ludivine set to major
acousticbrainz: attribute key_strength of M83 - Junk - Ludivine set to 0.704598069191
acousticbrainz: attribute initial_key of M83 - Junk - Ludivine set to F# minor
Sending event: database_change
Sending event: write
```

### Setup

* OS: Linux
* Python version: 3.6.4
* beets version: 1.4.6
* Turning off plugins made problem go away (yes/no): irrelevant, I guess

My configuration (output of `beet config`) is: probably irrelevant too, I just have the plugin enabled without any configuration.



Discussion:
Wow, that’s pretty bad! Is it possible to cause the same problem using `beet modify` to set a value to a very small number? If so, we’ll know this is an issue in the core, not with this specific plugin.
Good catch. It works fine with `beet modify` (either in the preview and in the saved value, displayed with `beet ls`.
Thanks! It looks like we need to be more careful about how we convert values from the analysis tool’s output to numeric Python values.

Resolving patch:
diff --git a/beets/dbcore/types.py b/beets/dbcore/types.py
--- a/beets/dbcore/types.py
+++ b/beets/dbcore/types.py
@@ -173,14 +173,17 @@ def __init__(self, primary=True):
 
 
 class Float(Type):
-    """A basic floating-point type.
+    """A basic floating-point type. Supports padding.
     """
     sql = u'REAL'
     query = query.NumericQuery
     model_type = float
 
+    def __init__(self, digits=1):
+        self.digits = digits
+
     def format(self, value):
-        return u'{0:.1f}'.format(value or 0.0)
+        return u'{0:.{1}f}'.format(value or 0, self.digits)
 
 
 class NullFloat(Float):
diff --git a/beetsplug/acousticbrainz.py b/beetsplug/acousticbrainz.py
--- a/beetsplug/acousticbrainz.py
+++ b/beetsplug/acousticbrainz.py
@@ -17,10 +17,12 @@
 """
 from __future__ import division, absolute_import, print_function
 
+from collections import defaultdict
+
 import requests
 
-from collections import defaultdict
 from beets import plugins, ui
+from beets.dbcore import types
 
 ACOUSTIC_BASE = "https://acousticbrainz.org/"
 LEVELS = ["/low-level", "/high-level"]
@@ -104,6 +106,29 @@
 
 
 class AcousticPlugin(plugins.BeetsPlugin):
+    item_types = {
+        'average_loudness': types.Float(6),
+        'chords_changes_rate': types.Float(6),
+        'chords_key': types.STRING,
+        'chords_number_rate': types.Float(6),
+        'chords_scale': types.STRING,
+        'danceable': types.Float(6),
+        'gender': types.STRING,
+        'genre_rosamerica': types.STRING,
+        'initial_key': types.STRING,
+        'key_strength': types.Float(6),
+        'mood_acoustic': types.Float(6),
+        'mood_aggressive': types.Float(6),
+        'mood_electronic': types.Float(6),
+        'mood_happy': types.Float(6),
+        'mood_party': types.Float(6),
+        'mood_relaxed': types.Float(6),
+        'mood_sad': types.Float(6),
+        'rhythm': types.Float(6),
+        'tonal': types.Float(6),
+        'voice_instrumental': types.STRING,
+    }
+
     def __init__(self):
         super(AcousticPlugin, self).__init__()
 


Test patch: