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.


Changed files:
beetsplug/acousticbrainz.py

diff --git a/beetsplug/acousticbrainz.py b/beetsplug/acousticbrainz.py
index f4960c301..3a6db7d4e 100644
--- 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 @@ ABSCHEME = {
 
 
 class AcousticPlugin(plugins.BeetsPlugin):
+    item_types = {
+        'average_loudness': types.FLOAT,
+        'chords_changes_rate': types.FLOAT,
+        'chords_key': types.STRING,
+        'chords_number_rate': types.FLOAT,
+        'chords_scale': types.STRING,
+        'danceable': types.FLOAT,
+        'gender': types.STRING,
+        'genre_rosamerica': types.STRING,
+        'initial_key': types.STRING,
+        'key_strength': types.FLOAT,
+        'mood_acoustic': types.FLOAT,
+        'mood_aggressive': types.FLOAT,
+        'mood_electronic': types.FLOAT,
+        'mood_happy': types.FLOAT,
+        'mood_party': types.FLOAT,
+        'mood_relaxed': types.FLOAT,
+        'mood_sad': types.FLOAT,
+        'rhythm': types.FLOAT,
+        'tonal': types.FLOAT,
+        'voice_instrumental': types.STRING,
+    }
+
     def __init__(self):
         super(AcousticPlugin, self).__init__()
 
@@ -151,7 +176,7 @@ class AcousticPlugin(plugins.BeetsPlugin):
                 return {}
 
             if res.status_code == 404:
-                self._log.info(u'recording ID {} not found', mbid)
+                self._log.info(u'recording ID \\'{}\\' not found', mbid)
                 return {}
 
             try:
@@ -174,26 +199,27 @@ class AcousticPlugin(plugins.BeetsPlugin):
             if not force:
                 mood_str = item.get('mood_acoustic', u'')
                 if mood_str:
-                    self._log.info(u'data already present for: {}', item)
+                    self._log.info(u'data already present for: \\'{}\\'', item)
                     continue
 
             # We can only fetch data for tracks with MBIDs.
             if not item.mb_trackid:
                 continue
 
-            self._log.info(u'getting data for: {}', item)
+            self._log.info(u'getting data for: \\'{}\\'', item)
             data = self._get_data(item.mb_trackid)
             if data:
                 for attr, val in self._map_data_to_scheme(data, ABSCHEME):
                     if not tags or attr in tags:
-                        self._log.debug(u'attribute {} of {} set to {}',
-                                        attr,
-                                        item,
-                                        val)
+                        self._log.debug(
+                            u'attribute \\'{}\\' of \\'{}\\' set to \\'{}\\'',
+                            attr,
+                            item,
+                            val)
                         setattr(item, attr, val)
                     else:
-                        self._log.debug(u'skipping attribute {} of {}'
-                                        u' (value {}) due to config',
+                        self._log.debug(u'skipping attribute \\'{}\\' of \\'{}\\''
+                                        u' (value \\'{}\\') due to config',
                                         attr,
                                         item,
                                         val)