Skip to content

Commit f22ca1a

Browse files
committed
Add missing valid seach parameters
We were missing search parameters for event, instrument, and place which means that any query for these items with a named field would fail. Also missing a few (new?) fields for some existing types. Added tests for all searches to ensure that named fields work as expected, and errors for invalid fields work. Signed-off-by: Alastair Porter <alastair@porter.net.nz>
1 parent 7652484 commit f22ca1a

File tree

4 files changed

+161
-51
lines changed

4 files changed

+161
-51
lines changed

musicbrainzngs/compat.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
HTTPHandler, build_opener, HTTPError, URLError
4444
from httplib import BadStatusLine, HTTPException
4545
from urlparse import urlunparse
46-
from urllib import urlencode
46+
from urllib import urlencode, quote_plus
4747

4848
bytes = str
4949
unicode = unicode
@@ -54,7 +54,7 @@
5454
HTTPHandler, build_opener
5555
from urllib.error import HTTPError, URLError
5656
from http.client import HTTPException, BadStatusLine
57-
from urllib.parse import urlunparse, urlencode
57+
from urllib.parse import urlunparse, urlencode, quote_plus
5858

5959
unicode = str
6060
bytes = bytes

musicbrainzngs/musicbrainz.py

+16-4
Original file line numberDiff line numberDiff line change
@@ -109,25 +109,37 @@
109109
],
110110
'area': [
111111
'aid', 'area', 'alias', 'begin', 'comment', 'end', 'ended',
112-
'iso', 'iso1', 'iso2', 'iso3', 'type'
112+
'iso', 'iso1', 'iso2', 'iso3', 'sortname', 'type'
113113
],
114114
'artist': [
115115
'arid', 'artist', 'artistaccent', 'alias', 'begin', 'comment',
116116
'country', 'end', 'ended', 'gender', 'ipi', 'sortname', 'tag', 'type',
117117
'area', 'beginarea', 'endarea'
118118
],
119+
'event': [
120+
'alias', 'aid', 'area', 'arid', 'artist', 'comment', 'eid', 'event',
121+
'pid', 'place', 'type', 'tag'
122+
],
123+
'instrument': [
124+
'alias', 'comment', 'description', 'iid', 'instrument',
125+
'type', 'tag'
126+
],
119127
'label': [
120128
'alias', 'begin', 'code', 'comment', 'country', 'end', 'ended',
121129
'ipi', 'label', 'labelaccent', 'laid', 'sortname', 'type', 'tag',
122130
'area'
123131
],
132+
'place': [
133+
'pid', 'address', 'alias', 'area', 'begin', 'comment', 'end', 'ended',
134+
'lat', 'long', 'place', 'placeaccent', 'type'
135+
],
124136
'recording': [
125137
'arid', 'artist', 'artistname', 'creditname', 'comment',
126138
'country', 'date', 'dur', 'format', 'isrc', 'number',
127139
'position', 'primarytype', 'qdur', 'recording',
128140
'recordingaccent', 'reid', 'release', 'rgid', 'rid',
129-
'secondarytype', 'status', 'tnum', 'tracks', 'tracksrelease',
130-
'tag', 'type', 'video'
141+
'secondarytype', 'status', 'tid', 'tnum', 'tracks',
142+
'tracksrelease', 'tag', 'type', 'video'
131143
],
132144
'release-group': [
133145
'arid', 'artist', 'artistname', 'comment', 'creditname',
@@ -144,7 +156,7 @@
144156
'tracksmedium', 'type'
145157
],
146158
'series': [
147-
'alias', 'comment', 'sid', 'series', 'type'
159+
'alias', 'comment', 'sid', 'series', 'type', 'tag'
148160
],
149161
'work': [
150162
'alias', 'arid', 'artist', 'comment', 'iswc', 'lang', 'tag',

test/test_mbxml_search.py

+8-45
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,11 @@
11
import unittest
22
import os
3-
import musicbrainzngs
43
from musicbrainzngs import mbxml
5-
from test import _common
64

75

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

108

11-
class UrlTest(unittest.TestCase):
12-
""" Test that the correct URL is generated when a search query is made """
13-
14-
def setUp(self):
15-
self.opener = _common.FakeOpener("<response/>")
16-
musicbrainzngs.compat.build_opener = lambda *args: self.opener
17-
18-
musicbrainzngs.set_useragent("a", "1")
19-
musicbrainzngs.set_rate_limit(False)
20-
21-
def testSearchArtist(self):
22-
musicbrainzngs.search_artists("Dynamo Go")
23-
self.assertEqual("http://musicbrainz.org/ws/2/artist/?query=Dynamo+Go", self.opener.get_url())
24-
25-
def testSearchEvent(self):
26-
musicbrainzngs.search_events("woodstock")
27-
self.assertEqual("http://musicbrainz.org/ws/2/event/?query=woodstock", self.opener.get_url())
28-
29-
def testSearchLabel(self):
30-
musicbrainzngs.search_labels("Waysafe")
31-
self.assertEqual("http://musicbrainz.org/ws/2/label/?query=Waysafe", self.opener.get_url())
32-
33-
def testSearchPlace(self):
34-
musicbrainzngs.search_places("Fillmore")
35-
self.assertEqual("http://musicbrainz.org/ws/2/place/?query=Fillmore", self.opener.get_url())
36-
37-
def testSearchRelease(self):
38-
musicbrainzngs.search_releases("Affordable Pop Music")
39-
self.assertEqual("http://musicbrainz.org/ws/2/release/?query=Affordable+Pop+Music", self.opener.get_url())
40-
41-
def testSearchReleaseGroup(self):
42-
musicbrainzngs.search_release_groups("Affordable Pop Music")
43-
self.assertEqual("http://musicbrainz.org/ws/2/release-group/?query=Affordable+Pop+Music", self.opener.get_url())
44-
45-
def testSearchRecording(self):
46-
musicbrainzngs.search_recordings("Thief of Hearts")
47-
self.assertEqual("http://musicbrainz.org/ws/2/recording/?query=Thief+of+Hearts", self.opener.get_url())
48-
49-
def testSearchWork(self):
50-
musicbrainzngs.search_works("Fountain City")
51-
self.assertEqual("http://musicbrainz.org/ws/2/work/?query=Fountain+City", self.opener.get_url())
52-
53-
549
class SearchArtistTest(unittest.TestCase):
5510
def testFields(self):
5611
fn = os.path.join(DATA_DIR, "search-artist.xml")
@@ -64,6 +19,7 @@ def testFields(self):
6419
# so check for it here
6520
self.assertEqual("100", one["ext:score"])
6621

22+
6723
class SearchReleaseTest(unittest.TestCase):
6824
def testFields(self):
6925
fn = os.path.join(DATA_DIR, "search-release.xml")
@@ -79,6 +35,7 @@ def testFields(self):
7935
self.assertEqual(1, one["medium-count"])
8036
self.assertEqual("CD", one["medium-list"][0]["format"])
8137

38+
8239
class SearchReleaseGroupTest(unittest.TestCase):
8340
def testFields(self):
8441
fn = os.path.join(DATA_DIR, "search-release-group.xml")
@@ -89,6 +46,7 @@ def testFields(self):
8946
one = res["release-group-list"][0]
9047
self.assertEqual("100", one["ext:score"])
9148

49+
9250
class SearchWorkTest(unittest.TestCase):
9351
def testFields(self):
9452
fn = os.path.join(DATA_DIR, "search-work.xml")
@@ -99,6 +57,7 @@ def testFields(self):
9957
one = res["work-list"][0]
10058
self.assertEqual("100", one["ext:score"])
10159

60+
10261
class SearchLabelTest(unittest.TestCase):
10362
def testFields(self):
10463
fn = os.path.join(DATA_DIR, "search-label.xml")
@@ -109,6 +68,7 @@ def testFields(self):
10968
one = res["label-list"][0]
11069
self.assertEqual("100", one["ext:score"])
11170

71+
11272
class SearchRecordingTest(unittest.TestCase):
11373
def testFields(self):
11474
fn = os.path.join(DATA_DIR, "search-recording.xml")
@@ -119,6 +79,7 @@ def testFields(self):
11979
one = res["recording-list"][0]
12080
self.assertEqual("100", one["ext:score"])
12181

82+
12283
class SearchInstrumentTest(unittest.TestCase):
12384
def testFields(self):
12485
fn = os.path.join(DATA_DIR, "search-instrument.xml")
@@ -131,6 +92,7 @@ def testFields(self):
13192
end = res["instrument-list"][-1]
13293
self.assertEqual("29", end["ext:score"])
13394

95+
13496
class SearchPlaceTest(unittest.TestCase):
13597
def testFields(self):
13698
fn = os.path.join(DATA_DIR, "search-place.xml")
@@ -144,6 +106,7 @@ def testFields(self):
144106
self.assertEqual("63", two["ext:score"])
145107
self.assertEqual("Southampton", two["disambiguation"])
146108

109+
147110
class SearchEventTest(unittest.TestCase):
148111
def testFields(self):
149112
fn = os.path.join(DATA_DIR, "search-event.xml")

test/test_search.py

+135
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import unittest
2+
3+
import musicbrainzngs
4+
from test import _common
5+
6+
7+
class SearchUrlTest(unittest.TestCase):
8+
""" Test that the correct URL is generated when a search query is made """
9+
10+
def setUp(self):
11+
self.opener = _common.FakeOpener("<response/>")
12+
musicbrainzngs.compat.build_opener = lambda *args: self.opener
13+
14+
musicbrainzngs.set_useragent("a", "1")
15+
musicbrainzngs.set_rate_limit(False)
16+
17+
def test_search_annotations(self):
18+
musicbrainzngs.search_annotations("Pieds")
19+
self.assertEquals("http://musicbrainz.org/ws/2/annotation/?query=Pieds", self.opener.get_url())
20+
21+
# Query fields
22+
musicbrainzngs.search_annotations(entity="bdb24cb5-404b-4f60-bba4-7b730325ae47")
23+
# TODO: We escape special characters and then urlencode all query parameters, which may
24+
# not be necessary, but MusicBrainz accepts it and appears to return the same value as without
25+
expected_query = 'entity:(bdb24cb5\-404b\-4f60\-bba4\-7b730325ae47)'
26+
expected = 'http://musicbrainz.org/ws/2/annotation/?query=%s' % musicbrainzngs.compat.quote_plus(expected_query)
27+
self.assertEquals(expected, self.opener.get_url())
28+
29+
# Invalid query field
30+
with self.assertRaises(musicbrainzngs.InvalidSearchFieldError):
31+
musicbrainzngs.search_annotations(foo="value")
32+
33+
def test_search_artists(self):
34+
musicbrainzngs.search_artists("Dynamo Go")
35+
self.assertEqual("http://musicbrainz.org/ws/2/artist/?query=Dynamo+Go", self.opener.get_url())
36+
37+
musicbrainzngs.search_artists(artist="Dynamo Go")
38+
expected_query = 'artist:(dynamo go)'
39+
expected = 'http://musicbrainz.org/ws/2/artist/?query=%s' % musicbrainzngs.compat.quote_plus(expected_query)
40+
self.assertEquals(expected, self.opener.get_url())
41+
42+
# Invalid query field
43+
with self.assertRaises(musicbrainzngs.InvalidSearchFieldError):
44+
musicbrainzngs.search_artists(foo="value")
45+
46+
def test_search_events(self):
47+
musicbrainzngs.search_events("woodstock")
48+
self.assertEqual("http://musicbrainz.org/ws/2/event/?query=woodstock", self.opener.get_url())
49+
50+
musicbrainzngs.search_events(event="woodstock")
51+
expected_query = 'event:(woodstock)'
52+
expected = 'http://musicbrainz.org/ws/2/event/?query=%s' % musicbrainzngs.compat.quote_plus(expected_query)
53+
self.assertEquals(expected, self.opener.get_url())
54+
55+
# Invalid query field
56+
with self.assertRaises(musicbrainzngs.InvalidSearchFieldError):
57+
musicbrainzngs.search_events(foo="value")
58+
59+
def test_search_labels(self):
60+
musicbrainzngs.search_labels("Waysafe")
61+
self.assertEqual("http://musicbrainz.org/ws/2/label/?query=Waysafe", self.opener.get_url())
62+
63+
musicbrainzngs.search_labels(label="Waysafe")
64+
expected_query = 'label:(waysafe)'
65+
expected = 'http://musicbrainz.org/ws/2/label/?query=%s' % musicbrainzngs.compat.quote_plus(expected_query)
66+
self.assertEquals(expected, self.opener.get_url())
67+
68+
# Invalid query field
69+
with self.assertRaises(musicbrainzngs.InvalidSearchFieldError):
70+
musicbrainzngs.search_labels(foo="value")
71+
72+
def test_search_places(self):
73+
musicbrainzngs.search_places("Fillmore")
74+
self.assertEqual("http://musicbrainz.org/ws/2/place/?query=Fillmore", self.opener.get_url())
75+
76+
musicbrainzngs.search_places(place="Fillmore")
77+
expected_query = 'place:(fillmore)'
78+
expected = 'http://musicbrainz.org/ws/2/place/?query=%s' % musicbrainzngs.compat.quote_plus(expected_query)
79+
self.assertEquals(expected, self.opener.get_url())
80+
81+
# Invalid query field
82+
with self.assertRaises(musicbrainzngs.InvalidSearchFieldError):
83+
musicbrainzngs.search_places(foo="value")
84+
85+
def test_search_releases(self):
86+
musicbrainzngs.search_releases("Affordable Pop Music")
87+
self.assertEqual("http://musicbrainz.org/ws/2/release/?query=Affordable+Pop+Music", self.opener.get_url())
88+
89+
musicbrainzngs.search_releases(release="Affordable Pop Music")
90+
expected_query = 'release:(affordable pop music)'
91+
expected = 'http://musicbrainz.org/ws/2/release/?query=%s' % musicbrainzngs.compat.quote_plus(expected_query)
92+
self.assertEquals(expected, self.opener.get_url())
93+
94+
# Invalid query field
95+
with self.assertRaises(musicbrainzngs.InvalidSearchFieldError):
96+
musicbrainzngs.search_releases(foo="value")
97+
98+
def test_search_release_groups(self):
99+
musicbrainzngs.search_release_groups("Affordable Pop Music")
100+
self.assertEqual("http://musicbrainz.org/ws/2/release-group/?query=Affordable+Pop+Music", self.opener.get_url())
101+
102+
musicbrainzngs.search_release_groups(releasegroup="Affordable Pop Music")
103+
expected_query = 'releasegroup:(affordable pop music)'
104+
expected = 'http://musicbrainz.org/ws/2/release-group/?query=%s' % musicbrainzngs.compat.quote_plus(expected_query)
105+
self.assertEquals(expected, self.opener.get_url())
106+
107+
# Invalid query field
108+
with self.assertRaises(musicbrainzngs.InvalidSearchFieldError):
109+
musicbrainzngs.search_release_groups(foo="value")
110+
111+
def test_search_recordings(self):
112+
musicbrainzngs.search_recordings("Thief of Hearts")
113+
self.assertEqual("http://musicbrainz.org/ws/2/recording/?query=Thief+of+Hearts", self.opener.get_url())
114+
115+
musicbrainzngs.search_recordings(recording="Thief of Hearts")
116+
expected_query = 'recording:(thief of hearts)'
117+
expected = 'http://musicbrainz.org/ws/2/recording/?query=%s' % musicbrainzngs.compat.quote_plus(expected_query)
118+
self.assertEquals(expected, self.opener.get_url())
119+
120+
# Invalid query field
121+
with self.assertRaises(musicbrainzngs.InvalidSearchFieldError):
122+
musicbrainzngs.search_recordings(foo="value")
123+
124+
def test_search_works(self):
125+
musicbrainzngs.search_works("Fountain City")
126+
self.assertEqual("http://musicbrainz.org/ws/2/work/?query=Fountain+City", self.opener.get_url())
127+
128+
musicbrainzngs.search_works(work="Fountain City")
129+
expected_query = 'work:(fountain city)'
130+
expected = 'http://musicbrainz.org/ws/2/work/?query=%s' % musicbrainzngs.compat.quote_plus(expected_query)
131+
self.assertEquals(expected, self.opener.get_url())
132+
133+
# Invalid query field
134+
with self.assertRaises(musicbrainzngs.InvalidSearchFieldError):
135+
musicbrainzngs.search_works(foo="value")

0 commit comments

Comments
 (0)