From d9582f4bea092836f8d86aab91c60151f824e13b Mon Sep 17 00:00:00 2001 From: Sam Thursfield Date: Wed, 30 Sep 2020 17:36:23 +0200 Subject: [PATCH] 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. --- beetsplug/export.py | 23 +++++++++++++++++++---- docs/changelog.rst | 2 +- docs/plugins/export.rst | 5 +++-- test/test_export.py | 11 +++++++++++ 4 files changed, 34 insertions(+), 7 deletions(-) diff --git a/beetsplug/export.py b/beetsplug/export.py index 8d98d0ba2..957180db2 100644 --- a/beetsplug/export.py +++ b/beetsplug/export.py @@ -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): diff --git a/docs/changelog.rst b/docs/changelog.rst index 47b0398c0..e02bebfa2 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -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. diff --git a/docs/plugins/export.rst b/docs/plugins/export.rst index f3756718c..284d2b8b6 100644 --- a/docs/plugins/export.rst +++ b/docs/plugins/export.rst @@ -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 `_ 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. diff --git a/test/test_export.py b/test/test_export.py index 779e74423..f0a8eb0f7 100644 --- a/test/test_export.py +++ b/test/test_export.py @@ -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(