diff --git a/beetsplug/web/__init__.py b/beetsplug/web/__init__.py index 50a0c63e6..344a4160c 100644 --- a/beetsplug/web/__init__.py +++ b/beetsplug/web/__init__.py @@ -67,6 +67,64 @@ def json_generator(items, root): yield json.dumps(_rep(item)) yield ']}' +def _extract_ids(string_ids): + """Parses ``string_ids`` as a comme separated list of integers and returns + that list of integers. + """ + ids = [] + for id in string_ids.split(','): + try: + ids.append(int(id)) + except ValueError: + pass + return ids + +def resource(name): + """Decorates a function to handle RESTful HTTP requests for a resource. + """ + def make_responder(retriever): + def responder(entity_ids): + entity_ids = _extract_ids(entity_ids) + entities = [retriever(id) for id in entity_ids] + entities = [entity for entity in entities if entity] + + if len(entities) == 1: + return flask.jsonify(_rep(entities[0])) + elif entities: + return app.response_class( + json_generator(entities, root=name), + mimetype='application/json') + else: + return flask.abort(404) + responder.__name__ = 'get_%s' % name + return responder + return make_responder + +def resource_query(name): + """Decorates a function to handle RESTful HTTP queries for resources. + """ + def make_responder(query_func): + def responder(query): + parts = query.split('/') + entities = query_func(parts) + return flask.jsonify(results=[_rep(entities) for item in items]) + responder.__name__ = 'query_%s' % name + return responder + return make_responder + +def resource_list(name): + """Decorates a function to handle RESTful HTTP request for a list of + resources. + """ + def make_responder(list_all): + def responder(): + return app.response_class( + json_generator(g.lib.items(), root=name), + mimetype='application/json') + responder.__name__ = 'all_%s' % name + return responder + return make_responder + # Flask setup. @@ -79,17 +137,17 @@ def before_request(): # Items. -@app.route('/item/') -def single_item(item_id): - item = g.lib.get_item(item_id) - return flask.jsonify(_rep(item)) +@app.route('/item/') +@resource('items') +def get_item(id): + return g.lib.get_item(id) + @app.route('/item/') @app.route('/item/query/') +@resource_list('items') def all_items(): - return app.response_class( - json_generator(g.lib.items(), root='items'), - mimetype='application/json') + return g.lib.items() @app.route('/item//file') def item_file(item_id): @@ -100,32 +158,28 @@ def item_file(item_id): return response @app.route('/item/query/') -def item_query(query): - parts = query.split('/') - items = g.lib.items(parts) - return flask.jsonify(results=[_rep(item) for item in items]) +@resource_query('items') +def item_query(queries): + return g.lib.items(queries) # Albums. -@app.route('/album/') -def single_album(album_id): - album = g.lib.get_album(album_id) - return flask.jsonify(_rep(album)) +@app.route('/album/') +@resource('albums') +def get_album(id): + return g.lib.get_album(id) @app.route('/album/') @app.route('/album/query/') +@resource_list('albums') def all_albums(): - return app.response_class( - json_generator(g.lib.albums(), root='albums'), - mimetype='application/json') - + return g.lib.albums() @app.route('/album/query/') -def album_query(query): - parts = query.split('/') - albums = g.lib.albums(parts) - return flask.jsonify(results=[_rep(album) for album in albums]) +@resource_query('albums') +def album_query(queries): + return g.lib.album(queries) @app.route('/album//art') def album_art(album_id): diff --git a/test/test_web.py b/test/test_web.py index d038b410d..b8d9d5a8a 100644 --- a/test/test_web.py +++ b/test/test_web.py @@ -5,6 +5,7 @@ import _common import json import beets import beetsplug +from beets.library import Item, Album beetsplug.__path__ = ['./beetsplug', '../beetsplug'] from beetsplug import web @@ -15,23 +16,45 @@ class WebPluginTest(_common.LibTestCase): super(WebPluginTest, self).setUp() # Add fixtures - self.lib.add(beets.library.Item( - title = u'another title', - path = 'somepath' + str(_common._item_ident))) - self.lib.add(beets.library.Album()) - self.lib.add(beets.library.Album()) + for track in self.lib.items(): + track.remove() + self.lib.add(Item(title='title', path='', id=1)) + self.lib.add(Item(title='another title', path='', id=2)) + self.lib.add(Album(album='album', id=1)) + self.lib.add(Album(album='another album', id=2)) web.app.config['TESTING'] = True web.app.config['lib'] = self.lib self.client = web.app.test_client() - def test_get_item(self): + def test_get_all_items(self): response = self.client.get('/item/') response.json = json.loads(response.data) self.assertEqual(response.status_code, 200) self.assertEqual(len(response.json['items']), 2) + def test_get_single_item_by_id(self): + response = self.client.get('/item/1') + response.json = json.loads(response.data) + + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json['id'], 1) + self.assertEqual(response.json['title'], 'title') + + def test_get_multiple_items_by_id(self): + response = self.client.get('/item/1,2') + response.json = json.loads(response.data) + + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.json['items']), 2) + response_titles = [item['title'] for item in response.json['items']] + self.assertItemsEqual(response_titles, ['title', 'another title']) + + def test_get_single_item_not_found(self): + response = self.client.get('/item/3') + self.assertEqual(response.status_code, 404) + def test_get_item_empty_query(self): response = self.client.get('/item/query/') response.json = json.loads(response.data) @@ -39,13 +62,29 @@ class WebPluginTest(_common.LibTestCase): self.assertEqual(response.status_code, 200) self.assertEqual(len(response.json['items']), 2) - def test_get_album(self): + def test_get_all_albums(self): response = self.client.get('/album/') response.json = json.loads(response.data) self.assertEqual(response.status_code, 200) self.assertEqual(len(response.json['albums']), 2) + def test_get_single_album_by_id(self): + response = self.client.get('/album/2') + response.json = json.loads(response.data) + + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json['id'], 2) + self.assertEqual(response.json['album'], 'another album') + + def test_get_multiple_items_by_id(self): + response = self.client.get('/album/1,2') + response.json = json.loads(response.data) + + self.assertEqual(response.status_code, 200) + response_albums = [album['album'] for album in response.json['albums']] + self.assertItemsEqual(response_albums, ['album', 'another album']) + def test_get_album_empty_query(self): response = self.client.get('/album/query/') response.json = json.loads(response.data)