-
Notifications
You must be signed in to change notification settings - Fork 1.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Extended export plugin support #3400
Changes from 27 commits
e5b43d4
93e1026
4dfb6b9
a8a480a
8ff875b
c31b488
0e2c1e0
ec705fa
c5ebbe0
fa2c9ba
160e4db
294b3cd
4f0a2b7
db5d216
0d818ec
5193f1b
a9440ad
07138f8
4251ff7
eb6055e
21d8091
623f553
d86e31d
7f6630c
d7b0e93
5d7c937
c1b646f
d45b8bb
2291778
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -2,9 +2,11 @@ Export Plugin | |||||
============= | ||||||
|
||||||
The ``export`` plugin lets you get data from the items and export the content | ||||||
as `JSON`_. | ||||||
as `JSON`_, `CSV`_, or `XML`_. | ||||||
|
||||||
.. _JSON: https://www.json.org | ||||||
.. _CSV: https://fileinfo.com/extension/csv | ||||||
.. _XML: https://fileinfo.com/extension/xml | ||||||
|
||||||
Enable the ``export`` plugin (see :ref:`using-plugins` for help). Then, type ``beet export`` followed by a :doc:`query </reference/query>` to get the data from | ||||||
your library. For example, run this:: | ||||||
|
@@ -13,6 +15,7 @@ your library. For example, run this:: | |||||
|
||||||
to print a JSON file containing information about your Beatles tracks. | ||||||
|
||||||
|
||||||
Command-Line Options | ||||||
-------------------- | ||||||
|
||||||
|
@@ -36,24 +39,36 @@ 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. | ||||||
|
||||||
Configuration | ||||||
------------- | ||||||
|
||||||
To configure the plugin, make a ``export:`` section in your configuration | ||||||
file. Under the ``json`` key, these options are available: | ||||||
file. Under the ``json``, ``csv``, and ``xml`` keys, these options are available: | ||||||
|
||||||
- **ensure_ascii**: Escape non-ASCII characters with ``\uXXXX`` entities. | ||||||
- **JSON Formatting** | ||||||
- **ensure_ascii**: Escape non-ASCII characters with ``\uXXXX`` entities. | ||||||
|
||||||
- **indent**: The number of spaces for indentation. | ||||||
- **indent**: The number of spaces for indentation. | ||||||
|
||||||
- **separators**: A ``[item_separator, dict_separator]`` tuple. | ||||||
- **separators**: A ``[item_separator, dict_separator]`` tuple. | ||||||
|
||||||
- **sort_keys**: Sorts the keys in JSON dictionaries. | ||||||
- **sort_keys**: Sorts the keys in JSON dictionaries. | ||||||
|
||||||
These options match the options from the `Python json module`_. | ||||||
|
||||||
.. _Python json module: https://docs.python.org/2/library/json.html#basic-usage | ||||||
|
||||||
- **CSV Formatting** | ||||||
- **delimiter**: Used as the separating character between fields. The default value is a comma (,). | ||||||
|
||||||
- **dialect**: A dialect is a construct that allows you to create, store, and re-use various formatting parameters for your data. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Keeping this focused on what matters to users of this plugin, not to Python programmers. |
||||||
|
||||||
These options match the options from the `Python csv module`_. | ||||||
|
||||||
.. _Python csv module: https://docs.python.org/3/library/csv.html#csv-fmt-params | ||||||
|
||||||
The default options look like this:: | ||||||
|
||||||
export: | ||||||
|
@@ -62,4 +77,8 @@ The default options look like this:: | |||||
ensure_ascii: False | ||||||
indent: 4 | ||||||
separators: [',' , ': '] | ||||||
sort_keys: true | ||||||
sort_keys: True | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Lower case is OK here—it’s YAML, not Python.
Suggested change
|
||||||
csv: | ||||||
formatting: | ||||||
delimiter: ',' | ||||||
dialect: 'excel' | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
No quotes are usually necessary in YAML. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
# -*- coding: utf-8 -*- | ||
# This file is part of beets. | ||
# Copyright 2019, Carl Suster | ||
# | ||
# Permission is hereby granted, free of charge, to any person obtaining | ||
# a copy of this software and associated documentation files (the | ||
# "Software"), to deal in the Software without restriction, including | ||
# without limitation the rights to use, copy, modify, merge, publish, | ||
# distribute, sublicense, and/or sell copies of the Software, and to | ||
# permit persons to whom the Software is furnished to do so, subject to | ||
# the following conditions: | ||
# | ||
# The above copyright notice and this permission notice shall be | ||
# included in all copies or substantial portions of the Software. | ||
|
||
"""Test the beets.export utilities associated with the export plugin. | ||
""" | ||
|
||
from __future__ import division, absolute_import, print_function | ||
|
||
import unittest | ||
from test.helper import TestHelper | ||
import re # used to test csv format | ||
import json | ||
from xml.etree.ElementTree import Element | ||
import xml.etree.ElementTree as ET | ||
|
||
|
||
class ExportPluginTest(unittest.TestCase, TestHelper): | ||
def setUp(self): | ||
self.setup_beets() | ||
self.load_plugins('export') | ||
self.test_values = {'title': 'xtitle', 'album': 'xalbum'} | ||
|
||
def tearDown(self): | ||
self.unload_plugins() | ||
self.teardown_beets() | ||
|
||
def execute_command(self, format_type, artist): | ||
query = ','.join(self.test_values.keys()) | ||
out = self.run_with_output( | ||
'export', | ||
'-f', format_type, | ||
'-i', query, | ||
artist | ||
) | ||
return out | ||
|
||
def create_item(self): | ||
item, = self.add_item_fixtures() | ||
item.artist = 'xartist' | ||
item.title = self.test_values['title'] | ||
item.album = self.test_values['album'] | ||
item.write() | ||
item.store() | ||
return item | ||
|
||
def test_json_output(self): | ||
item1 = self.create_item() | ||
out = self.execute_command( | ||
format_type='json', | ||
artist=item1.artist | ||
) | ||
json_data = json.loads(out)[0] | ||
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( | ||
format_type='csv', | ||
artist=item1.artist | ||
) | ||
csv_list = re.split('\r', re.sub('\n', '', out)) | ||
head = re.split(',', csv_list[0]) | ||
vals = re.split(',|\r', csv_list[1]) | ||
for index, column in enumerate(head): | ||
self.assertTrue(self.test_values.get(column, None) is not None) | ||
self.assertEqual(vals[index], self.test_values[column]) | ||
|
||
def test_xml_output(self): | ||
item1 = self.create_item() | ||
out = self.execute_command( | ||
format_type='xml', | ||
artist=item1.artist | ||
) | ||
library = ET.fromstring(out) | ||
self.assertIsInstance(library, Element) | ||
for track in library[0]: | ||
for details in track: | ||
tag = details.tag | ||
txt = details.text | ||
self.assertTrue(tag in self.test_values, msg=tag) | ||
self.assertEqual(self.test_values[tag], txt, msg=txt) | ||
|
||
|
||
def suite(): | ||
return unittest.TestLoader().loadTestsFromName(__name__) | ||
|
||
if __name__ == '__main__': | ||
unittest.main(defaultTest='suite') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I notice this now behaves differently from it did before—the
kwargs
are now forwarded unconditionally instead of only when the path is unspecified. Does this change the user-visible behavior?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should not effect the user in any way. It simply simplifies the logic in my opinion.