Skip to content

Commit

Permalink
Merge branch 'instruments'
Browse files Browse the repository at this point in the history
Signed-off-by: Alastair Porter <[email protected]>
  • Loading branch information
alastair committed Nov 23, 2015
2 parents afa9784 + b86d01b commit cffa983
Show file tree
Hide file tree
Showing 13 changed files with 160 additions and 10 deletions.
22 changes: 22 additions & 0 deletions musicbrainzngs/mbxml.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ def parse_message(message):
result = {}
valid_elements = {"area": parse_area,
"artist": parse_artist,
"instrument": parse_instrument,
"label": parse_label,
"place": parse_place,
"event": parse_event,
Expand All @@ -155,6 +156,7 @@ def parse_message(message):
"label-list": parse_label_list,
"place-list": parse_place_list,
"event-list": parse_event_list,
"instrument-list": parse_instrument_list,
"release-list": parse_release_list,
"release-group-list": parse_release_group_list,
"series-list": parse_series_list,
Expand Down Expand Up @@ -293,6 +295,19 @@ def parse_event(event):

return result

def parse_instrument(instrument):
result = {}
attribs = ["id", "type", "ext:score"]
elements = ["name", "description", "disambiguation"]
inner_els = {"relation-list": parse_relation_list,
"tag-list": parse_tag_list,
"alias-list": parse_alias_list,
"annotation": parse_annotation}
result.update(parse_attributes(attribs, instrument))
result.update(parse_elements(elements, inner_els, instrument))

return result

def parse_label_list(ll):
return [parse_label(l) for l in ll]

Expand Down Expand Up @@ -336,6 +351,7 @@ def parse_relation(relation):
elements = ["target", "direction", "begin", "end", "ended", "ordering-key"]
inner_els = {"area": parse_area,
"artist": parse_artist,
"instrument": parse_instrument,
"label": parse_label,
"place": parse_place,
"event": parse_event,
Expand Down Expand Up @@ -548,6 +564,12 @@ def parse_cdstub(cdstub):
def parse_offset_list(ol):
return [int(o.text) for o in ol]

def parse_instrument_list(rl):
result = []
for r in rl:
result.append(parse_instrument(r))
return result

def parse_release_list(rl):
result = []
for r in rl:
Expand Down
14 changes: 10 additions & 4 deletions musicbrainzngs/musicbrainz.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@

# Constants for validation.

RELATABLE_TYPES = ['area', 'artist', 'label', 'place', 'event', 'recording', 'release', 'release-group', 'series', 'url', 'work']
RELATABLE_TYPES = ['area', 'artist', 'label', 'place', 'event', 'recording', 'release', 'release-group', 'series', 'url', 'work', 'instrument']
RELATION_INCLUDES = [entity + '-rels' for entity in RELATABLE_TYPES]
TAG_INCLUDES = ["tags", "user-tags"]
RATING_INCLUDES = ["ratings", "user-ratings"]
Expand All @@ -42,9 +42,8 @@
'annotation': [

],
'instrument': [

],
'instrument': ["aliases", "annotation"
] + RELATION_INCLUDES + TAG_INCLUDES,
'label': [
"releases", # Subqueries
"discids", "media",
Expand Down Expand Up @@ -922,6 +921,13 @@ def search_events(query='', limit=None, offset=None, strict=False, **fields):
*Available search fields*: {fields}"""
return _do_mb_search('event', query, fields, limit, offset, strict)

@_docstring('instrument')
def search_instruments(query='', limit=None, offset=None, strict=False, **fields):
"""Search for instruments and return a dict with a 'instrument-list' key.
*Available search fields*: {fields}"""
return _do_mb_search('instrument', query, fields, limit, offset, strict)

@_docstring('label')
def search_labels(query='', limit=None, offset=None, strict=False, **fields):
"""Search for labels and return a dict with a 'label-list' key.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><metadata xmlns="http://musicbrainz.org/ns/mmd-2.0#"><instrument id="01ba56a2-4306-493d-8088-c7e9b671c74e" type="String instrument"><name>kemenche</name><description>Various types of stringed bowed musical instruments having their origin in the Eastern Mediterranean</description><relation-list target-type="instrument"><relation type-id="12678b88-1adb-3536-890e-9b39b9a14b2d" type="children"><target>04a21d03-535a-4ace-9098-12013867b8e5</target><direction>backward</direction><instrument id="04a21d03-535a-4ace-9098-12013867b8e5"><name>fiddle</name></instrument></relation><relation type="children" type-id="12678b88-1adb-3536-890e-9b39b9a14b2d"><target>ad09a4ed-d1b6-47c3-ac85-acb531244a4d</target><instrument id="ad09a4ed-d1b6-47c3-ac85-acb531244a4d"><name>kemençe of the Black Sea</name><description>Turkish box-shaped kemenche, mainly used for folk music.</description></instrument></relation><relation type="children" type-id="12678b88-1adb-3536-890e-9b39b9a14b2d"><target>b9692581-c117-47f3-9524-3deeb69c6d3f</target><instrument id="b9692581-c117-47f3-9524-3deeb69c6d3f"><name>classical kemençe</name><description>Turkish bowl-shaped kemenche, mainly used in classical Ottoman music</description></instrument></relation></relation-list></instrument></metadata>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><metadata xmlns="http://musicbrainz.org/ns/mmd-2.0#"><instrument type="Other instrument" id="6505f98c-f698-4406-8bf4-8ca43d05c36f"><name>bass</name><description>Bass is a common but generic credit which refers to more than one instrument, the most common being the bass guitar and the double bass (a.k.a. contrabass, acoustic upright bass, wood bass). Please use the correct instrument if you know which one is intended.</description><alias-list count="14"><alias locale="de" primary="primary" sort-name="Bass" type="Instrument name">Bass</alias><alias locale="it" sort-name="Basso" primary="primary" type="Instrument name">Basso</alias><alias sort-name="baixo" primary="primary" locale="pt_BR" type="Instrument name">baixo</alias><alias type="Instrument name" sort-name="bajo (genérico, no usar)" primary="primary" locale="es">bajo (genérico, no usar)</alias><alias locale="da" primary="primary" sort-name="bas" type="Instrument name">bas</alias><alias type="Instrument name" primary="primary" sort-name="bas" locale="hr">bas</alias><alias type="Instrument name" primary="primary" sort-name="bas" locale="nl">bas</alias><alias type="Instrument name" primary="primary" sort-name="bas" locale="tr">bas</alias><alias type="Instrument name" locale="en" primary="primary" sort-name="bass">bass</alias><alias type="Instrument name" sort-name="bass" primary="primary" locale="et">bass</alias><alias locale="fr" primary="primary" sort-name="basses" type="Instrument name">basses</alias><alias type="Instrument name" locale="fi" sort-name="basso" primary="primary">basso</alias><alias type="Instrument name" sort-name="μπάσο" primary="primary" locale="el">μπάσο</alias><alias type="Instrument name" locale="ja" primary="primary" sort-name="ベース">ベース</alias></alias-list></instrument></metadata>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><metadata xmlns="http://musicbrainz.org/ns/mmd-2.0#"><instrument id="6505f98c-f698-4406-8bf4-8ca43d05c36f" type="Other instrument"><name>bass</name><description>Bass is a common but generic credit which refers to more than one instrument, the most common being the bass guitar and the double bass (a.k.a. contrabass, acoustic upright bass, wood bass). Please use the correct instrument if you know which one is intended.</description><tag-list><tag count="1"><name>fixme</name></tag><tag count="0"><name>never use this</name></tag><tag count="0"><name>please don't use this</name></tag></tag-list></instrument></metadata>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><metadata xmlns="http://musicbrainz.org/ns/mmd-2.0#"><instrument id="9447c0af-5569-48f2-b4c5-241105d58c91" type="Wind instrument"><name>bass saxophone</name><description>The bass saxophone is the second largest existing member of the saxophone family (not counting the subcontrabass tubax). It is similar in design to a baritone saxophone, but it is larger, with a longer loop near the mouthpiece.</description></instrument></metadata>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><metadata xmlns="http://musicbrainz.org/ns/mmd-2.0#"><instrument type="Other instrument" id="d00cec5f-f9bc-4235-a54f-6639a02d4e4c"><name>bullroarer</name><description>A bullroarer consists of a piece of wood attached to a long cord which is then swung in a circle.</description><annotation><text>Hornbostel-Sachs: 412.22</text></annotation></instrument></metadata>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><metadata xmlns="http://musicbrainz.org/ns/mmd-2.0#"><instrument id="d00cec5f-f9bc-4235-a54f-6639a02d4e4c" type="Other instrument"><name>bullroarer</name><description>A bullroarer consists of a piece of wood attached to a long cord which is then swung in a circle.</description><relation-list target-type="url"><relation type-id="0e62afec-12f3-3d0f-b122-956207839854" type="information page"><target id="e0132182-3ab0-405f-adde-f2504c97900e">http://en.wikisource.org/wiki/1911_Encyclop%C3%A6dia_Britannica/Bullroarer</target></relation><relation type-id="1486fccd-cf59-35e4-9399-b50e2b255877" type="wikidata"><target id="5c0633b0-12fd-4e54-8383-9cc0e049aa3a">http://www.wikidata.org/wiki/Q666971</target></relation><relation type-id="f64eacbd-1ea1-381e-9886-2cfb552b7d90" type="image"><target id="8513926d-184f-4ef7-920c-ec3b69e9a00a">https://commons.wikimedia.org/wiki/File:Bull_roarers.jpg</target></relation></relation-list></instrument></metadata>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><metadata xmlns="http://musicbrainz.org/ns/mmd-2.0#"><instrument type="String instrument" id="dabdeb41-560f-4d84-aa6a-cf22349326fe"><name>tar</name><disambiguation>lute</disambiguation><description>The tar is a long-necked, waisted lute found in Azerbaijan, Iran, Armenia, Georgia, and other areas near the Caucasus region. Not to be confused with the drum of the same name.</description></instrument></metadata>
1 change: 1 addition & 0 deletions test/data/search-instrument.xml

Large diffs are not rendered by default.

22 changes: 22 additions & 0 deletions test/test_getentity.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,3 +115,25 @@ def testGetByDiscid(self):
self.assertEqual("http://musicbrainz.org/ws/2/discid/discid?cdstubs=no&toc=toc", self.opener.get_url())


def testGetInstrument(self):

musicbrainzngs.get_instrument_by_id("6505f98c-f698-4406-8bf4-8ca43d05c36f")
self.assertEqual("http://musicbrainz.org/ws/2/instrument/6505f98c-f698-4406-8bf4-8ca43d05c36f", self.opener.get_url())

# Tags
musicbrainzngs.get_instrument_by_id("6505f98c-f698-4406-8bf4-8ca43d05c36f", includes="tags")
self.assertEqual("http://musicbrainz.org/ws/2/instrument/6505f98c-f698-4406-8bf4-8ca43d05c36f?inc=tags", self.opener.get_url())

# some rels
musicbrainzngs.get_instrument_by_id("6505f98c-f698-4406-8bf4-8ca43d05c36f", includes=["instrument-rels", "url-rels"])
self.assertEqual("http://musicbrainz.org/ws/2/instrument/6505f98c-f698-4406-8bf4-8ca43d05c36f?inc=instrument-rels+url-rels", self.opener.get_url())

# alias, annotation
musicbrainzngs.get_instrument_by_id("d00cec5f-f9bc-4235-a54f-6639a02d4e4c", includes=["aliases", "annotation"])
self.assertEqual("http://musicbrainz.org/ws/2/instrument/d00cec5f-f9bc-4235-a54f-6639a02d4e4c?inc=aliases+annotation", self.opener.get_url())

# Ratings are used on almost all other entites but instrument
self.assertRaises(musicbrainzngs.UsageError,
musicbrainzngs.get_instrument_by_id,
"dabdeb41-560f-4d84-aa6a-cf22349326fe", includes=["ratings"])

78 changes: 78 additions & 0 deletions test/test_mbxml_instrument.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# -*- coding: UTF-8 -*-
# Tests for parsing instrument queries

import unittest
import os
import sys
# Insert .. at the beginning of path so we use this version instead
# of something that's already been installed
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
from test import _common
import musicbrainzngs

class GetInstrumentTest(unittest.TestCase):
def setUp(self):
self.datadir = os.path.join(os.path.dirname(__file__), "data", "instrument")

def testData(self):
res = _common.open_and_parse_test_data(self.datadir, "9447c0af-5569-48f2-b4c5-241105d58c91.xml")
inst = res["instrument"]

self.assertEqual(inst["id"], "9447c0af-5569-48f2-b4c5-241105d58c91")
self.assertEqual(inst["name"], "bass saxophone")
self.assertEqual(inst["type"], "Wind instrument")
self.assertTrue(inst["description"].startswith("The bass saxophone"))

def testAliases(self):
res = _common.open_and_parse_test_data(self.datadir, "6505f98c-f698-4406-8bf4-8ca43d05c36f-aliases.xml")
inst = res["instrument"]

aliases = inst["alias-list"]
self.assertEqual(len(aliases), 14)
self.assertEqual(aliases[1]["locale"], "it")
self.assertEqual(aliases[1]["type"], "Instrument name")
self.assertEqual(aliases[1]["primary"], "primary")
self.assertEqual(aliases[1]["sort-name"], "Basso")
self.assertEqual(aliases[1]["alias"], "Basso")


def testTags(self):
res = _common.open_and_parse_test_data(self.datadir, "6505f98c-f698-4406-8bf4-8ca43d05c36f-tags.xml")
inst = res["instrument"]

tags = inst["tag-list"]
self.assertEqual(len(tags), 3)
self.assertEqual(tags[0]["name"], "fixme")
self.assertEqual(tags[0]["count"], "1")

def testUrlRels(self):
res = _common.open_and_parse_test_data(self.datadir, "d00cec5f-f9bc-4235-a54f-6639a02d4e4c-url-rels.xml")
inst = res["instrument"]

rels = inst["url-relation-list"]
self.assertEqual(len(rels), 3)
self.assertEqual(rels[0]["type"], "information page")
self.assertEqual(rels[0]["type-id"], "0e62afec-12f3-3d0f-b122-956207839854")
self.assertTrue(rels[0]["target"].startswith("http://en.wikisource"))

def testAnnotations(self):
res = _common.open_and_parse_test_data(self.datadir, "d00cec5f-f9bc-4235-a54f-6639a02d4e4c-annotation.xml")
inst = res["instrument"]
self.assertEqual(inst["annotation"]["text"], "Hornbostel-Sachs: 412.22")

def testInstrumentRels(self):
res = _common.open_and_parse_test_data(self.datadir, "01ba56a2-4306-493d-8088-c7e9b671c74e-instrument-rels.xml")
inst = res["instrument"]

rels = inst["instrument-relation-list"]
self.assertEqual(len(rels), 3)
self.assertEqual(rels[1]["type"], "children")
self.assertEqual(rels[1]["type-id"], "12678b88-1adb-3536-890e-9b39b9a14b2d")
self.assertEqual(rels[1]["target"], "ad09a4ed-d1b6-47c3-ac85-acb531244a4d")
self.assertEqual(rels[1]["instrument"]["id"], "ad09a4ed-d1b6-47c3-ac85-acb531244a4d")
self.assertTrue(rels[1]["instrument"]["name"].startswith(b"kemen\xc3\xa7e".decode("utf-8")))

def testDisambiguation(self):
res = _common.open_and_parse_test_data(self.datadir, "dabdeb41-560f-4d84-aa6a-cf22349326fe.xml")
inst = res["instrument"]
self.assertEqual(inst["disambiguation"], "lute")
26 changes: 20 additions & 6 deletions test/test_mbxml_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
from test import _common


DATA_DIR = os.path.join(os.path.dirname(__file__), "data")

class UrlTest(unittest.TestCase):
""" Test that the correct URL is generated when a search query is made """

Expand Down Expand Up @@ -45,7 +47,7 @@ def testSearchRecording(self):

class SearchArtistTest(unittest.TestCase):
def testFields(self):
fn = os.path.join(os.path.dirname(__file__), "data", "search-artist.xml")
fn = os.path.join(DATA_DIR, "search-artist.xml")
with open(fn) as msg:
res = mbxml.parse_message(msg)
self.assertEqual(25, len(res["artist-list"]))
Expand All @@ -58,7 +60,7 @@ def testFields(self):

class SearchReleaseTest(unittest.TestCase):
def testFields(self):
fn = os.path.join(os.path.dirname(__file__), "data", "search-release.xml")
fn = os.path.join(DATA_DIR, "search-release.xml")
with open(fn) as msg:
res = mbxml.parse_message(msg)
self.assertEqual(25, len(res["release-list"]))
Expand All @@ -68,7 +70,7 @@ def testFields(self):

class SearchReleaseGroupTest(unittest.TestCase):
def testFields(self):
fn = os.path.join(os.path.dirname(__file__), "data", "search-release-group.xml")
fn = os.path.join(DATA_DIR, "search-release-group.xml")
with open(fn) as msg:
res = mbxml.parse_message(msg)
self.assertEqual(25, len(res["release-group-list"]))
Expand All @@ -78,7 +80,7 @@ def testFields(self):

class SearchWorkTest(unittest.TestCase):
def testFields(self):
fn = os.path.join(os.path.dirname(__file__), "data", "search-work.xml")
fn = os.path.join(DATA_DIR, "search-work.xml")
with open(fn) as msg:
res = mbxml.parse_message(msg)
self.assertEqual(25, len(res["work-list"]))
Expand All @@ -88,7 +90,7 @@ def testFields(self):

class SearchLabelTest(unittest.TestCase):
def testFields(self):
fn = os.path.join(os.path.dirname(__file__), "data", "search-label.xml")
fn = os.path.join(DATA_DIR, "search-label.xml")
with open(fn) as msg:
res = mbxml.parse_message(msg)
self.assertEqual(1, len(res["label-list"]))
Expand All @@ -98,10 +100,22 @@ def testFields(self):

class SearchRecordingTest(unittest.TestCase):
def testFields(self):
fn = os.path.join(os.path.dirname(__file__), "data", "search-recording.xml")
fn = os.path.join(DATA_DIR, "search-recording.xml")
with open(fn) as msg:
res = mbxml.parse_message(msg)
self.assertEqual(25, len(res["recording-list"]))
self.assertEqual(1258, res["recording-count"])
one = res["recording-list"][0]
self.assertEqual("100", one["ext:score"])

class SearchInstrumentTest(unittest.TestCase):
def testFields(self):
fn = os.path.join(DATA_DIR, "search-instrument.xml")
with open(fn) as msg:
res = mbxml.parse_message(msg)
self.assertEqual(23, len(res["instrument-list"]))
self.assertEqual(23, res["instrument-count"])
one = res["instrument-list"][0]
self.assertEqual("100", one["ext:score"])
end = res["instrument-list"][-1]
self.assertEqual("29", end["ext:score"])

0 comments on commit cffa983

Please sign in to comment.