export: Add --format=jsonlines option

This adds support for the JSON Lines format as documented at
https://jsonlines.org/.

In this mode the data is output incrementally, whereas the other
modes load every item into memory and don't produce output until
the end.
This commit is contained in:
Sam Thursfield 2020-09-30 17:36:23 +02:00
parent 78f976320d
commit d9582f4bea
4 changed files with 34 additions and 7 deletions

View file

@ -54,6 +54,14 @@ class ExportPlugin(BeetsPlugin):
'sort_keys': True
}
},
'jsonlines': {
# JSON Lines formatting options.
'formatting': {
'ensure_ascii': False,
'separators': (',', ': '),
'sort_keys': True
}
},
'csv': {
# CSV module formatting options.
'formatting': {
@ -95,7 +103,7 @@ class ExportPlugin(BeetsPlugin):
)
cmd.parser.add_option(
u'-f', u'--format', default='json',
help=u"the output format: json (default), csv, or xml"
help=u"the output format: json (default), jsonlines, csv, or xml"
)
return [cmd]
@ -103,6 +111,7 @@ class ExportPlugin(BeetsPlugin):
file_path = opts.output
file_mode = 'a' if opts.append else 'w'
file_format = opts.format or self.config['default_format'].get(str)
file_format_is_line_based = (file_format == 'jsonlines')
format_options = self.config[file_format]['formatting'].get(dict)
export_format = ExportFormat.factory(
@ -130,9 +139,14 @@ class ExportPlugin(BeetsPlugin):
continue
data = key_filter(data)
items += [data]
export_format.export(items, **format_options)
if file_format_is_line_based:
export_format.export(data, **format_options)
else:
items += [data]
if not file_format_is_line_based:
export_format.export(items, **format_options)
class ExportFormat(object):
@ -147,7 +161,7 @@ class ExportFormat(object):
@classmethod
def factory(cls, file_type, **kwargs):
if file_type == "json":
if file_type in ["json", "jsonlines"]:
return JsonFormat(**kwargs)
elif file_type == "csv":
return CSVFormat(**kwargs)
@ -167,6 +181,7 @@ class JsonFormat(ExportFormat):
def export(self, data, **kwargs):
json.dump(data, self.out_stream, cls=ExportEncoder, **kwargs)
self.out_stream.write('\n')
class CSVFormat(ExportFormat):

View file

@ -25,7 +25,7 @@ New features:
`discogs_artistid`
:bug: `3413`
* :doc:`/plugins/export`: Added new ``-f`` (``--format``) flag;
which allows for the ability to export in json, csv and xml.
which allows for the ability to export in json, jsonlines, csv and xml.
Thanks to :user:`austinmm`.
:bug:`3402`
* :doc:`/plugins/unimported`: lets you find untracked files in your library directory.

View file

@ -39,14 +39,15 @@ The ``export`` command has these command-line options:
* ``--append``: Appends the data to the file instead of writing.
* ``--format`` or ``-f``: Specifies the format the data will be exported as. If not informed, JSON will be used by default. The format options include csv, json and xml.
* ``--format`` or ``-f``: Specifies the format the data will be exported as. If not informed, JSON will be used by default. The format options include csv, json, `jsonlines <https://jsonlines.org/>`_ and xml.
Configuration
-------------
To configure the plugin, make a ``export:`` section in your configuration
file.
For JSON export, these options are available under the ``json`` key:
For JSON export, these options are available under the ``json`` and
``jsonlines`` keys:
- **ensure_ascii**: Escape non-ASCII characters with ``\uXXXX`` entities.
- **indent**: The number of spaces for indentation.

View file

@ -66,6 +66,17 @@ class ExportPluginTest(unittest.TestCase, TestHelper):
self.assertTrue(key in json_data)
self.assertEqual(val, json_data[key])
def test_jsonlines_output(self):
item1 = self.create_item()
out = self.execute_command(
format_type='jsonlines',
artist=item1.artist
)
json_data = json.loads(out)
for key, val in self.test_values.items():
self.assertTrue(key in json_data)
self.assertEqual(val, json_data[key])
def test_csv_output(self):
item1 = self.create_item()
out = self.execute_command(