mirror of
https://github.com/rembo10/headphones.git
synced 2026-07-21 16:34:01 +01:00
Changed findArtist to use the musicbrainzngs library, the output is identical.
(old musicbrainz 2 library is deprecated and broken)
This commit is contained in:
+26
-22
@@ -13,6 +13,8 @@ import headphones
|
|||||||
from headphones import logger, db
|
from headphones import logger, db
|
||||||
from headphones.helpers import multikeysort, replace_all
|
from headphones.helpers import multikeysort, replace_all
|
||||||
|
|
||||||
|
import lib.musicbrainzngs as musicbrainzngs
|
||||||
|
|
||||||
mb_lock = threading.Lock()
|
mb_lock = threading.Lock()
|
||||||
|
|
||||||
|
|
||||||
@@ -46,6 +48,12 @@ def startmb(forcemb=False):
|
|||||||
mbport = 5000
|
mbport = 5000
|
||||||
sleepytime = 0
|
sleepytime = 0
|
||||||
|
|
||||||
|
musicbrainzngs.set_useragent("headphones","0.0","https://github.com/doskir/headphones")
|
||||||
|
logger.info("set user agent")
|
||||||
|
musicbrainzngs.set_hostname(mbhost + ":" + str(mbport))
|
||||||
|
logger.info("set host and port")
|
||||||
|
|
||||||
|
#q = musicbrainzngs
|
||||||
service = ws.WebService(host=mbhost, port=mbport, username=mbuser, password=mbpass, mirror=headphones.MIRROR)
|
service = ws.WebService(host=mbhost, port=mbport, username=mbuser, password=mbpass, mirror=headphones.MIRROR)
|
||||||
q = ws.Query(service)
|
q = ws.Query(service)
|
||||||
|
|
||||||
@@ -56,7 +64,7 @@ def startmb(forcemb=False):
|
|||||||
def findArtist(name, limit=1):
|
def findArtist(name, limit=1):
|
||||||
|
|
||||||
with mb_lock:
|
with mb_lock:
|
||||||
|
limit = 25
|
||||||
artistlist = []
|
artistlist = []
|
||||||
attempt = 0
|
attempt = 0
|
||||||
artistResults = None
|
artistResults = None
|
||||||
@@ -68,11 +76,10 @@ def findArtist(name, limit=1):
|
|||||||
q, sleepytime = startmb(forcemb=True)
|
q, sleepytime = startmb(forcemb=True)
|
||||||
|
|
||||||
while attempt < 5:
|
while attempt < 5:
|
||||||
|
|
||||||
try:
|
try:
|
||||||
artistResults = q.getArtists(ws.ArtistFilter(query=name, limit=limit))
|
artistResults = musicbrainzngs.search_artists(query=name,limit=limit)['artist-list']
|
||||||
break
|
break
|
||||||
except WebServiceError, e:
|
except WebServiceError, e:#need to update the exceptions
|
||||||
logger.warn('Attempt to query MusicBrainz for %s failed (%s)' % (name, str(e)))
|
logger.warn('Attempt to query MusicBrainz for %s failed (%s)' % (name, str(e)))
|
||||||
attempt += 1
|
attempt += 1
|
||||||
time.sleep(10)
|
time.sleep(10)
|
||||||
@@ -81,36 +88,33 @@ def findArtist(name, limit=1):
|
|||||||
|
|
||||||
if not artistResults:
|
if not artistResults:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
for result in artistResults:
|
for result in artistResults:
|
||||||
|
if 'disambiguation' in result:
|
||||||
if result.artist.name != result.artist.getUniqueName() and limit == 1:
|
uniquename = unicode(result['sort-name'] + " (" + result['disambiguation'] + ")")
|
||||||
|
else:
|
||||||
|
uniquename = unicode(result['sort-name'])
|
||||||
|
if result['name'] != uniquename and limit == 1:
|
||||||
logger.debug('Found an artist with a disambiguation: %s - doing an album based search' % name)
|
logger.debug('Found an artist with a disambiguation: %s - doing an album based search' % name)
|
||||||
artistdict = findArtistbyAlbum(name)
|
artistdict = findArtistbyAlbum(name)
|
||||||
|
|
||||||
if not artistdict:
|
if not artistdict:
|
||||||
logger.debug('Cannot determine the best match from an artist/album search. Using top match instead')
|
logger.debug('Cannot determine the best match from an artist/album search. Using top match instead')
|
||||||
artistlist.append({
|
artistlist.append({
|
||||||
'name': result.artist.name,
|
'name': unicode(result['sort-name']),
|
||||||
'uniquename': result.artist.getUniqueName(),
|
'uniquename': uniquename,
|
||||||
'id': u.extractUuid(result.artist.id),
|
'id': unicode(result['id']),
|
||||||
'url': result.artist.id,
|
'url': unicode("http://musicbrainz.org/artist/" + result['id']),#probably needs to be changed
|
||||||
'score': result.score
|
'score': int(result['ext:score'])
|
||||||
})
|
})
|
||||||
|
|
||||||
else:
|
else:
|
||||||
artistlist.append(artistdict)
|
artistlist.append(artistdict)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
artistlist.append({
|
artistlist.append({
|
||||||
'name': result.artist.name,
|
'name': unicode(result['sort-name']),
|
||||||
'uniquename': result.artist.getUniqueName(),
|
'uniquename': uniquename,
|
||||||
'id': u.extractUuid(result.artist.id),
|
'id': unicode(result['id']),
|
||||||
'url': result.artist.id,
|
'url': unicode("http://musicbrainz.org/artist/" + result['id']),#probably needs to be changed
|
||||||
'score': result.score
|
'score': int(result['ext:score'])
|
||||||
})
|
})
|
||||||
|
|
||||||
return artistlist
|
return artistlist
|
||||||
|
|
||||||
def findRelease(name, limit=1):
|
def findRelease(name, limit=1):
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
from musicbrainz import *
|
from lib.musicbrainzngs.musicbrainz import *
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2012 Kenneth Reitz.
|
||||||
|
|
||||||
|
# Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
# purpose with or without fee is hereby granted, provided that the above
|
||||||
|
# copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
"""
|
||||||
|
pythoncompat
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# -------
|
||||||
|
# Pythons
|
||||||
|
# -------
|
||||||
|
|
||||||
|
# Syntax sugar.
|
||||||
|
_ver = sys.version_info
|
||||||
|
|
||||||
|
#: Python 2.x?
|
||||||
|
is_py2 = (_ver[0] == 2)
|
||||||
|
|
||||||
|
#: Python 3.x?
|
||||||
|
is_py3 = (_ver[0] == 3)
|
||||||
|
|
||||||
|
# ---------
|
||||||
|
# Specifics
|
||||||
|
# ---------
|
||||||
|
|
||||||
|
if is_py2:
|
||||||
|
from StringIO import StringIO
|
||||||
|
from urllib2 import HTTPPasswordMgr, HTTPDigestAuthHandler, Request,\
|
||||||
|
HTTPHandler, build_opener, HTTPError, URLError,\
|
||||||
|
build_opener
|
||||||
|
from httplib import BadStatusLine, HTTPException
|
||||||
|
from urlparse import urlunparse
|
||||||
|
from urllib import urlencode
|
||||||
|
|
||||||
|
bytes = str
|
||||||
|
unicode = unicode
|
||||||
|
basestring = basestring
|
||||||
|
elif is_py3:
|
||||||
|
from io import StringIO
|
||||||
|
from urllib.request import HTTPPasswordMgr, HTTPDigestAuthHandler, Request,\
|
||||||
|
HTTPHandler, build_opener
|
||||||
|
from urllib.error import HTTPError, URLError
|
||||||
|
from http.client import HTTPException, BadStatusLine
|
||||||
|
from urllib.parse import urlunparse, urlencode
|
||||||
|
|
||||||
|
unicode = str
|
||||||
|
bytes = bytes
|
||||||
|
basestring = (str,bytes)
|
||||||
@@ -4,9 +4,11 @@
|
|||||||
# See the COPYING file for more information.
|
# See the COPYING file for more information.
|
||||||
|
|
||||||
import xml.etree.ElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
import string
|
|
||||||
import StringIO
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from lib.musicbrainzngs import compat
|
||||||
|
from lib.musicbrainzngs import util
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from ET import fixtag
|
from ET import fixtag
|
||||||
except:
|
except:
|
||||||
@@ -16,7 +18,7 @@ except:
|
|||||||
# tag and namespace declaration, if any
|
# tag and namespace declaration, if any
|
||||||
if isinstance(tag, ET.QName):
|
if isinstance(tag, ET.QName):
|
||||||
tag = tag.text
|
tag = tag.text
|
||||||
namespace_uri, tag = string.split(tag[1:], "}", 1)
|
namespace_uri, tag = tag[1:].split("}", 1)
|
||||||
prefix = namespaces.get(namespace_uri)
|
prefix = namespaces.get(namespace_uri)
|
||||||
if prefix is None:
|
if prefix is None:
|
||||||
prefix = "ns%d" % len(namespaces)
|
prefix = "ns%d" % len(namespaces)
|
||||||
@@ -29,6 +31,7 @@ except:
|
|||||||
xmlns = None
|
xmlns = None
|
||||||
return "%s:%s" % (prefix, tag), xmlns
|
return "%s:%s" % (prefix, tag), xmlns
|
||||||
|
|
||||||
|
|
||||||
NS_MAP = {"http://musicbrainz.org/ns/mmd-2.0#": "ws2",
|
NS_MAP = {"http://musicbrainz.org/ns/mmd-2.0#": "ws2",
|
||||||
"http://musicbrainz.org/ns/ext#-2.0": "ext"}
|
"http://musicbrainz.org/ns/ext#-2.0": "ext"}
|
||||||
_log = logging.getLogger("python-musicbrainz-ngs")
|
_log = logging.getLogger("python-musicbrainz-ngs")
|
||||||
@@ -113,9 +116,7 @@ def parse_inner(inner_els, element):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
def parse_message(message):
|
def parse_message(message):
|
||||||
s = message.read()
|
tree = util.bytes_to_elementtree(message)
|
||||||
f = StringIO.StringIO(s)
|
|
||||||
tree = ET.ElementTree(file=f)
|
|
||||||
root = tree.getroot()
|
root = tree.getroot()
|
||||||
result = {}
|
result = {}
|
||||||
valid_elements = {"artist": parse_artist,
|
valid_elements = {"artist": parse_artist,
|
||||||
@@ -176,7 +177,8 @@ def parse_artist_list(al):
|
|||||||
def parse_artist(artist):
|
def parse_artist(artist):
|
||||||
result = {}
|
result = {}
|
||||||
attribs = ["id", "type", "ext:score"]
|
attribs = ["id", "type", "ext:score"]
|
||||||
elements = ["name", "sort-name", "country", "user-rating", "disambiguation"]
|
elements = ["name", "sort-name", "country", "user-rating",
|
||||||
|
"disambiguation", "gender", "ipi"]
|
||||||
inner_els = {"life-span": parse_artist_lifespan,
|
inner_els = {"life-span": parse_artist_lifespan,
|
||||||
"recording-list": parse_recording_list,
|
"recording-list": parse_recording_list,
|
||||||
"release-list": parse_release_list,
|
"release-list": parse_release_list,
|
||||||
@@ -199,7 +201,8 @@ def parse_label_list(ll):
|
|||||||
def parse_label(label):
|
def parse_label(label):
|
||||||
result = {}
|
result = {}
|
||||||
attribs = ["id", "type", "ext:score"]
|
attribs = ["id", "type", "ext:score"]
|
||||||
elements = ["name", "sort-name", "country", "label-code", "user-rating"]
|
elements = ["name", "sort-name", "country", "label-code", "user-rating",
|
||||||
|
"ipi", "disambiguation"]
|
||||||
inner_els = {"life-span": parse_artist_lifespan,
|
inner_els = {"life-span": parse_artist_lifespan,
|
||||||
"release-list": parse_release_list,
|
"release-list": parse_release_list,
|
||||||
"tag-list": parse_tag_list,
|
"tag-list": parse_tag_list,
|
||||||
|
|||||||
@@ -3,19 +3,18 @@
|
|||||||
# This file is distributed under a BSD-2-Clause type license.
|
# This file is distributed under a BSD-2-Clause type license.
|
||||||
# See the COPYING file for more information.
|
# See the COPYING file for more information.
|
||||||
|
|
||||||
import urlparse
|
|
||||||
import urllib2
|
|
||||||
import urllib
|
|
||||||
import mbxml
|
|
||||||
import re
|
import re
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
import logging
|
import logging
|
||||||
import httplib
|
|
||||||
import socket
|
import socket
|
||||||
import xml.etree.ElementTree as etree
|
import xml.etree.ElementTree as etree
|
||||||
from xml.parsers import expat
|
from xml.parsers import expat
|
||||||
|
|
||||||
|
from lib.musicbrainzngs import mbxml
|
||||||
|
from lib.musicbrainzngs import util
|
||||||
|
from lib.musicbrainzngs import compat
|
||||||
|
|
||||||
_version = "0.3dev"
|
_version = "0.3dev"
|
||||||
_log = logging.getLogger("musicbrainzngs")
|
_log = logging.getLogger("musicbrainzngs")
|
||||||
|
|
||||||
@@ -74,6 +73,7 @@ VALID_INCLUDES = {
|
|||||||
'puid': ["artists", "releases", "puids", "echoprints", "isrcs"],
|
'puid': ["artists", "releases", "puids", "echoprints", "isrcs"],
|
||||||
'isrc': ["artists", "releases", "puids", "echoprints", "isrcs"],
|
'isrc': ["artists", "releases", "puids", "echoprints", "isrcs"],
|
||||||
'iswc': ["artists"],
|
'iswc': ["artists"],
|
||||||
|
'collection': ['releases'],
|
||||||
}
|
}
|
||||||
VALID_RELEASE_TYPES = [
|
VALID_RELEASE_TYPES = [
|
||||||
"nat", "album", "single", "ep", "compilation", "soundtrack", "spokenword",
|
"nat", "album", "single", "ep", "compilation", "soundtrack", "spokenword",
|
||||||
@@ -83,29 +83,33 @@ VALID_RELEASE_STATUSES = ["official", "promotion", "bootleg", "pseudo-release"]
|
|||||||
VALID_SEARCH_FIELDS = {
|
VALID_SEARCH_FIELDS = {
|
||||||
'artist': [
|
'artist': [
|
||||||
'arid', 'artist', 'sortname', 'type', 'begin', 'end', 'comment',
|
'arid', 'artist', 'sortname', 'type', 'begin', 'end', 'comment',
|
||||||
'alias', 'country', 'gender', 'tag'
|
'alias', 'country', 'gender', 'tag', 'ipi', 'artistaccent'
|
||||||
],
|
],
|
||||||
'release-group': [
|
'release-group': [
|
||||||
'rgid', 'releasegroup', 'reid', 'release', 'arid', 'artist',
|
'rgid', 'releasegroup', 'reid', 'release', 'arid', 'artist',
|
||||||
'artistname', 'creditname', 'type', 'tag'
|
'artistname', 'creditname', 'type', 'tag', 'releasegroupaccent',
|
||||||
|
'releases', 'comment'
|
||||||
],
|
],
|
||||||
'release': [
|
'release': [
|
||||||
'reid', 'release', 'arid', 'artist', 'artistname', 'creditname',
|
'reid', 'release', 'arid', 'artist', 'artistname', 'creditname',
|
||||||
'type', 'status', 'tracks', 'tracksmedium', 'discids',
|
'type', 'status', 'tracks', 'tracksmedium', 'discids',
|
||||||
'discidsmedium', 'mediums', 'date', 'asin', 'lang', 'script',
|
'discidsmedium', 'mediums', 'date', 'asin', 'lang', 'script',
|
||||||
'country', 'date', 'label', 'catno', 'barcode', 'puid'
|
'country', 'date', 'label', 'catno', 'barcode', 'puid', 'comment',
|
||||||
|
'format', 'releaseaccent', 'rgid'
|
||||||
],
|
],
|
||||||
'recording': [
|
'recording': [
|
||||||
'rid', 'recording', 'isrc', 'arid', 'artist', 'artistname',
|
'rid', 'recording', 'isrc', 'arid', 'artist', 'artistname',
|
||||||
'creditname', 'reid', 'release', 'type', 'status', 'tracks',
|
'creditname', 'reid', 'release', 'type', 'status', 'tracks',
|
||||||
'tracksrelease', 'dur', 'qdur', 'tnum', 'position', 'tag'
|
'tracksrelease', 'dur', 'qdur', 'tnum', 'position', 'tag', 'comment',
|
||||||
|
'country', 'date' 'format', 'recordingaccent'
|
||||||
],
|
],
|
||||||
'label': [
|
'label': [
|
||||||
'laid', 'label', 'sortname', 'type', 'code', 'country', 'begin',
|
'laid', 'label', 'sortname', 'type', 'code', 'country', 'begin',
|
||||||
'end', 'comment', 'alias', 'tag'
|
'end', 'comment', 'alias', 'tag', 'ipi', 'labelaccent'
|
||||||
],
|
],
|
||||||
'work': [
|
'work': [
|
||||||
'wid', 'work', 'iswc', 'type', 'arid', 'artist', 'alias', 'tag'
|
'wid', 'work', 'iswc', 'type', 'arid', 'artist', 'alias', 'tag',
|
||||||
|
'comment', 'workaccent'
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,9 +190,9 @@ def _check_filter_and_make_params(entity, includes, release_status=[], release_t
|
|||||||
the filters can be used with the given includes. Return a params
|
the filters can be used with the given includes. Return a params
|
||||||
dict that can be passed to _do_mb_query.
|
dict that can be passed to _do_mb_query.
|
||||||
"""
|
"""
|
||||||
if isinstance(release_status, basestring):
|
if isinstance(release_status, compat.basestring):
|
||||||
release_status = [release_status]
|
release_status = [release_status]
|
||||||
if isinstance(release_type, basestring):
|
if isinstance(release_type, compat.basestring):
|
||||||
release_type = [release_type]
|
release_type = [release_type]
|
||||||
_check_filter(release_status, VALID_RELEASE_STATUSES)
|
_check_filter(release_status, VALID_RELEASE_STATUSES)
|
||||||
_check_filter(release_type, VALID_RELEASE_TYPES)
|
_check_filter(release_type, VALID_RELEASE_TYPES)
|
||||||
@@ -226,8 +230,8 @@ def auth(u, p):
|
|||||||
password = p
|
password = p
|
||||||
|
|
||||||
def set_useragent(app, version, contact=None):
|
def set_useragent(app, version, contact=None):
|
||||||
""" Set the User-Agent to be used for requests to the MusicBrainz webservice.
|
"""Set the User-Agent to be used for requests to the MusicBrainz webservice.
|
||||||
This should be set before requests are made."""
|
This must be set before requests are made."""
|
||||||
global _useragent, _client
|
global _useragent, _client
|
||||||
if contact is not None:
|
if contact is not None:
|
||||||
_useragent = "%s/%s python-musicbrainz-ngs/%s ( %s )" % (app, version, _version, contact)
|
_useragent = "%s/%s python-musicbrainz-ngs/%s ( %s )" % (app, version, _version, contact)
|
||||||
@@ -237,6 +241,8 @@ def set_useragent(app, version, contact=None):
|
|||||||
_log.debug("set user-agent to %s" % _useragent)
|
_log.debug("set user-agent to %s" % _useragent)
|
||||||
|
|
||||||
def set_hostname(new_hostname):
|
def set_hostname(new_hostname):
|
||||||
|
"""Set the base hostname for MusicBrainz webservice requests.
|
||||||
|
Defaults to 'musicbrainz.org'."""
|
||||||
global hostname
|
global hostname
|
||||||
hostname = new_hostname
|
hostname = new_hostname
|
||||||
|
|
||||||
@@ -244,17 +250,26 @@ def set_hostname(new_hostname):
|
|||||||
|
|
||||||
limit_interval = 1.0
|
limit_interval = 1.0
|
||||||
limit_requests = 1
|
limit_requests = 1
|
||||||
|
do_rate_limit = True
|
||||||
|
|
||||||
def set_rate_limit(new_interval=1.0, new_requests=1):
|
def set_rate_limit(rate_limit=True, new_interval=1.0, new_requests=1):
|
||||||
"""Sets the rate limiting behavior of the module. Must be invoked
|
"""Sets the rate limiting behavior of the module. Must be invoked
|
||||||
before the first Web service call. Specify the number of requests
|
before the first Web service call.
|
||||||
(`new_requests`) that may be made per given interval
|
If the `rate_limit` parameter is set to True, then only a set number
|
||||||
(`new_interval`).
|
of requests (`new_requests`) will be made per given interval
|
||||||
|
(`new_interval`). If `rate_limit` is False, then no rate limiting
|
||||||
|
will occur.
|
||||||
"""
|
"""
|
||||||
global limit_interval
|
global limit_interval
|
||||||
global limit_requests
|
global limit_requests
|
||||||
|
global do_rate_limit
|
||||||
|
if new_interval <= 0.0:
|
||||||
|
raise ValueError("new_interval can't be less than 0")
|
||||||
|
if new_requests <= 0:
|
||||||
|
raise ValueError("new_requests can't be less than 0")
|
||||||
limit_interval = new_interval
|
limit_interval = new_interval
|
||||||
limit_requests = new_requests
|
limit_requests = new_requests
|
||||||
|
do_rate_limit = rate_limit
|
||||||
|
|
||||||
class _rate_limit(object):
|
class _rate_limit(object):
|
||||||
"""A decorator that limits the rate at which the function may be
|
"""A decorator that limits the rate at which the function may be
|
||||||
@@ -290,6 +305,7 @@ class _rate_limit(object):
|
|||||||
|
|
||||||
def __call__(self, *args, **kwargs):
|
def __call__(self, *args, **kwargs):
|
||||||
with self.lock:
|
with self.lock:
|
||||||
|
if do_rate_limit:
|
||||||
self._update_remaining()
|
self._update_remaining()
|
||||||
|
|
||||||
# Delay if necessary.
|
# Delay if necessary.
|
||||||
@@ -302,11 +318,8 @@ class _rate_limit(object):
|
|||||||
self.remaining_requests -= 1.0
|
self.remaining_requests -= 1.0
|
||||||
return self.fun(*args, **kwargs)
|
return self.fun(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
# Generic support for making HTTP requests.
|
|
||||||
|
|
||||||
# From pymb2
|
# From pymb2
|
||||||
class _RedirectPasswordMgr(urllib2.HTTPPasswordMgr):
|
class _RedirectPasswordMgr(compat.HTTPPasswordMgr):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._realms = { }
|
self._realms = { }
|
||||||
|
|
||||||
@@ -321,18 +334,18 @@ class _RedirectPasswordMgr(urllib2.HTTPPasswordMgr):
|
|||||||
# ignoring the uri parameter intentionally
|
# ignoring the uri parameter intentionally
|
||||||
self._realms[realm] = (username, password)
|
self._realms[realm] = (username, password)
|
||||||
|
|
||||||
class _DigestAuthHandler(urllib2.HTTPDigestAuthHandler):
|
class _DigestAuthHandler(compat.HTTPDigestAuthHandler):
|
||||||
def get_authorization (self, req, chal):
|
def get_authorization (self, req, chal):
|
||||||
qop = chal.get ('qop', None)
|
qop = chal.get ('qop', None)
|
||||||
if qop and ',' in qop and 'auth' in qop.split (','):
|
if qop and ',' in qop and 'auth' in qop.split (','):
|
||||||
chal['qop'] = 'auth'
|
chal['qop'] = 'auth'
|
||||||
|
|
||||||
return urllib2.HTTPDigestAuthHandler.get_authorization (self, req, chal)
|
return compat.HTTPDigestAuthHandler.get_authorization (self, req, chal)
|
||||||
|
|
||||||
class _MusicbrainzHttpRequest(urllib2.Request):
|
class _MusicbrainzHttpRequest(compat.Request):
|
||||||
""" A custom request handler that allows DELETE and PUT"""
|
""" A custom request handler that allows DELETE and PUT"""
|
||||||
def __init__(self, method, url, data=None):
|
def __init__(self, method, url, data=None):
|
||||||
urllib2.Request.__init__(self, url, data)
|
compat.Request.__init__(self, url, data)
|
||||||
allowed_m = ["GET", "POST", "DELETE", "PUT"]
|
allowed_m = ["GET", "POST", "DELETE", "PUT"]
|
||||||
if method not in allowed_m:
|
if method not in allowed_m:
|
||||||
raise ValueError("invalid method: %s" % method)
|
raise ValueError("invalid method: %s" % method)
|
||||||
@@ -362,8 +375,8 @@ def _safe_open(opener, req, body=None, max_retries=8, retry_delay_delta=2.0):
|
|||||||
else:
|
else:
|
||||||
f = opener.open(req)
|
f = opener.open(req)
|
||||||
|
|
||||||
except urllib2.HTTPError, exc:
|
except compat.HTTPError as exc:
|
||||||
if exc.code in (400, 404):
|
if exc.code in (400, 404, 411):
|
||||||
# Bad request, not found, etc.
|
# Bad request, not found, etc.
|
||||||
raise ResponseError(cause=exc)
|
raise ResponseError(cause=exc)
|
||||||
elif exc.code in (503, 502, 500):
|
elif exc.code in (503, 502, 500):
|
||||||
@@ -374,19 +387,23 @@ def _safe_open(opener, req, body=None, max_retries=8, retry_delay_delta=2.0):
|
|||||||
# retrying for now.
|
# retrying for now.
|
||||||
_log.debug("unknown HTTP error %i" % exc.code)
|
_log.debug("unknown HTTP error %i" % exc.code)
|
||||||
last_exc = exc
|
last_exc = exc
|
||||||
except httplib.BadStatusLine, exc:
|
except compat.BadStatusLine as exc:
|
||||||
_log.debug("bad status line")
|
_log.debug("bad status line")
|
||||||
last_exc = exc
|
last_exc = exc
|
||||||
except httplib.HTTPException, exc:
|
except compat.HTTPException as exc:
|
||||||
_log.debug("miscellaneous HTTP exception: %s" % str(exc))
|
_log.debug("miscellaneous HTTP exception: %s" % str(exc))
|
||||||
last_exc = exc
|
last_exc = exc
|
||||||
except urllib2.URLError, exc:
|
except compat.URLError as exc:
|
||||||
if isinstance(exc.reason, socket.error):
|
if isinstance(exc.reason, socket.error):
|
||||||
code = exc.reason.errno
|
code = exc.reason.errno
|
||||||
if code == 104: # "Connection reset by peer."
|
if code == 104: # "Connection reset by peer."
|
||||||
continue
|
continue
|
||||||
raise NetworkError(cause=exc)
|
raise NetworkError(cause=exc)
|
||||||
except IOError, exc:
|
except socket.error as exc:
|
||||||
|
if exc.errno == 104:
|
||||||
|
continue
|
||||||
|
raise NetworkError(cause=exc)
|
||||||
|
except IOError as exc:
|
||||||
raise NetworkError(cause=exc)
|
raise NetworkError(cause=exc)
|
||||||
else:
|
else:
|
||||||
# No exception! Yay!
|
# No exception! Yay!
|
||||||
@@ -425,23 +442,23 @@ def _mb_request(path, method='GET', auth_required=False, client_required=False,
|
|||||||
|
|
||||||
# Encode Unicode arguments using UTF-8.
|
# Encode Unicode arguments using UTF-8.
|
||||||
for key, value in args.items():
|
for key, value in args.items():
|
||||||
if isinstance(value, unicode):
|
if isinstance(value, compat.unicode):
|
||||||
args[key] = value.encode('utf8')
|
args[key] = value.encode('utf8')
|
||||||
|
|
||||||
# Construct the full URL for the request, including hostname and
|
# Construct the full URL for the request, including hostname and
|
||||||
# query string.
|
# query string.
|
||||||
url = urlparse.urlunparse((
|
url = compat.urlunparse((
|
||||||
'http',
|
'http',
|
||||||
hostname,
|
hostname,
|
||||||
'/ws/2/%s' % path,
|
'/ws/2/%s' % path,
|
||||||
'',
|
'',
|
||||||
urllib.urlencode(args),
|
compat.urlencode(args),
|
||||||
''
|
''
|
||||||
))
|
))
|
||||||
_log.debug("%s request for %s" % (method, url))
|
_log.debug("%s request for %s" % (method, url))
|
||||||
|
|
||||||
# Set up HTTP request handler and URL opener.
|
# Set up HTTP request handler and URL opener.
|
||||||
httpHandler = urllib2.HTTPHandler(debuglevel=0)
|
httpHandler = compat.HTTPHandler(debuglevel=0)
|
||||||
handlers = [httpHandler]
|
handlers = [httpHandler]
|
||||||
|
|
||||||
# Add credentials if required.
|
# Add credentials if required.
|
||||||
@@ -455,7 +472,7 @@ def _mb_request(path, method='GET', auth_required=False, client_required=False,
|
|||||||
authHandler.add_password("musicbrainz.org", (), user, password)
|
authHandler.add_password("musicbrainz.org", (), user, password)
|
||||||
handlers.append(authHandler)
|
handlers.append(authHandler)
|
||||||
|
|
||||||
opener = urllib2.build_opener(*handlers)
|
opener = compat.build_opener(*handlers)
|
||||||
|
|
||||||
# Make request.
|
# Make request.
|
||||||
req = _MusicbrainzHttpRequest(method, url, data)
|
req = _MusicbrainzHttpRequest(method, url, data)
|
||||||
@@ -463,6 +480,10 @@ def _mb_request(path, method='GET', auth_required=False, client_required=False,
|
|||||||
_log.debug("requesting with UA %s" % _useragent)
|
_log.debug("requesting with UA %s" % _useragent)
|
||||||
if body:
|
if body:
|
||||||
req.add_header('Content-Type', 'application/xml; charset=UTF-8')
|
req.add_header('Content-Type', 'application/xml; charset=UTF-8')
|
||||||
|
elif not data and not req.has_header('Content-Length'):
|
||||||
|
# Explicitly indicate zero content length if no request data
|
||||||
|
# will be sent (avoids HTTP 411 error).
|
||||||
|
req.add_header('Content-Length', '0')
|
||||||
f = _safe_open(opener, req, body)
|
f = _safe_open(opener, req, body)
|
||||||
|
|
||||||
# Parse the response.
|
# Parse the response.
|
||||||
@@ -496,6 +517,8 @@ def _do_mb_query(entity, id, includes=[], params={}):
|
|||||||
response is parsed and returned.
|
response is parsed and returned.
|
||||||
"""
|
"""
|
||||||
# Build arguments.
|
# Build arguments.
|
||||||
|
if not isinstance(includes, list):
|
||||||
|
includes = [includes]
|
||||||
_check_includes(entity, includes)
|
_check_includes(entity, includes)
|
||||||
auth_required = _is_auth_required(entity, includes)
|
auth_required = _is_auth_required(entity, includes)
|
||||||
args = dict(params)
|
args = dict(params)
|
||||||
@@ -507,15 +530,28 @@ def _do_mb_query(entity, id, includes=[], params={}):
|
|||||||
path = '%s/%s' % (entity, id)
|
path = '%s/%s' % (entity, id)
|
||||||
return _mb_request(path, 'GET', auth_required, args=args)
|
return _mb_request(path, 'GET', auth_required, args=args)
|
||||||
|
|
||||||
def _do_mb_search(entity, query='', fields={}, limit=None, offset=None):
|
def _do_mb_search(entity, query='', fields={},
|
||||||
|
limit=None, offset=None, strict=False):
|
||||||
"""Perform a full-text search on the MusicBrainz search server.
|
"""Perform a full-text search on the MusicBrainz search server.
|
||||||
`query` is a free-form query string and `fields` is a dictionary
|
`query` is a lucene query string when no fields are set,
|
||||||
|
but is escaped when any fields are given. `fields` is a dictionary
|
||||||
of key/value query parameters. They keys in `fields` must be valid
|
of key/value query parameters. They keys in `fields` must be valid
|
||||||
for the given entity type.
|
for the given entity type.
|
||||||
"""
|
"""
|
||||||
# Encode the query terms as a Lucene query string.
|
# Encode the query terms as a Lucene query string.
|
||||||
query_parts = [query.replace('\x00', '').strip()]
|
query_parts = []
|
||||||
for key, value in fields.iteritems():
|
if query:
|
||||||
|
clean_query = util._unicode(query)
|
||||||
|
if fields:
|
||||||
|
clean_query = re.sub(r'([+\-&|!(){}\[\]\^"~*?:\\])',
|
||||||
|
r'\\\1', clean_query)
|
||||||
|
if strict:
|
||||||
|
query_parts.append('"%s"' % clean_query)
|
||||||
|
else:
|
||||||
|
query_parts.append(clean_query.lower())
|
||||||
|
else:
|
||||||
|
query_parts.append(clean_query)
|
||||||
|
for key, value in fields.items():
|
||||||
# Ensure this is a valid search field.
|
# Ensure this is a valid search field.
|
||||||
if key not in VALID_SEARCH_FIELDS[entity]:
|
if key not in VALID_SEARCH_FIELDS[entity]:
|
||||||
raise InvalidSearchFieldError(
|
raise InvalidSearchFieldError(
|
||||||
@@ -523,12 +559,19 @@ def _do_mb_search(entity, query='', fields={}, limit=None, offset=None):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Escape Lucene's special characters.
|
# Escape Lucene's special characters.
|
||||||
|
value = util._unicode(value)
|
||||||
value = re.sub(r'([+\-&|!(){}\[\]\^"~*?:\\])', r'\\\1', value)
|
value = re.sub(r'([+\-&|!(){}\[\]\^"~*?:\\])', r'\\\1', value)
|
||||||
value = value.replace('\x00', '').strip()
|
|
||||||
value = value.lower() # Avoid binary operators like OR.
|
|
||||||
if value:
|
if value:
|
||||||
query_parts.append(u'%s:(%s)' % (key, value))
|
if strict:
|
||||||
full_query = u' '.join(query_parts).strip()
|
query_parts.append('%s:"%s"' % (key, value))
|
||||||
|
else:
|
||||||
|
value = value.lower() # avoid AND / OR
|
||||||
|
query_parts.append('%s:(%s)' % (key, value))
|
||||||
|
if strict:
|
||||||
|
full_query = ' AND '.join(query_parts).strip()
|
||||||
|
else:
|
||||||
|
full_query = ' '.join(query_parts).strip()
|
||||||
|
|
||||||
if not full_query:
|
if not full_query:
|
||||||
raise ValueError('at least one query term is required')
|
raise ValueError('at least one query term is required')
|
||||||
|
|
||||||
@@ -562,23 +605,23 @@ def _do_mb_post(path, body):
|
|||||||
|
|
||||||
# Single entity by ID
|
# Single entity by ID
|
||||||
def get_artist_by_id(id, includes=[], release_status=[], release_type=[]):
|
def get_artist_by_id(id, includes=[], release_status=[], release_type=[]):
|
||||||
params = _check_filter_and_make_params(includes, release_status, release_type)
|
params = _check_filter_and_make_params("artist", includes, release_status, release_type)
|
||||||
return _do_mb_query("artist", id, includes, params)
|
return _do_mb_query("artist", id, includes, params)
|
||||||
|
|
||||||
def get_label_by_id(id, includes=[], release_status=[], release_type=[]):
|
def get_label_by_id(id, includes=[], release_status=[], release_type=[]):
|
||||||
params = _check_filter_and_make_params(includes, release_status, release_type)
|
params = _check_filter_and_make_params("label", includes, release_status, release_type)
|
||||||
return _do_mb_query("label", id, includes, params)
|
return _do_mb_query("label", id, includes, params)
|
||||||
|
|
||||||
def get_recording_by_id(id, includes=[], release_status=[], release_type=[]):
|
def get_recording_by_id(id, includes=[], release_status=[], release_type=[]):
|
||||||
params = _check_filter_and_make_params(includes, release_status, release_type)
|
params = _check_filter_and_make_params("recording", includes, release_status, release_type)
|
||||||
return _do_mb_query("recording", id, includes, params)
|
return _do_mb_query("recording", id, includes, params)
|
||||||
|
|
||||||
def get_release_by_id(id, includes=[], release_status=[], release_type=[]):
|
def get_release_by_id(id, includes=[], release_status=[], release_type=[]):
|
||||||
params = _check_filter_and_make_params(includes, release_status, release_type)
|
params = _check_filter_and_make_params("release", includes, release_status, release_type)
|
||||||
return _do_mb_query("release", id, includes, params)
|
return _do_mb_query("release", id, includes, params)
|
||||||
|
|
||||||
def get_release_group_by_id(id, includes=[], release_status=[], release_type=[]):
|
def get_release_group_by_id(id, includes=[], release_status=[], release_type=[]):
|
||||||
params = _check_filter_and_make_params(includes, release_status, release_type)
|
params = _check_filter_and_make_params("release-group", includes, release_status, release_type)
|
||||||
return _do_mb_query("release-group", id, includes, params)
|
return _do_mb_query("release-group", id, includes, params)
|
||||||
|
|
||||||
def get_work_by_id(id, includes=[]):
|
def get_work_by_id(id, includes=[]):
|
||||||
@@ -587,54 +630,68 @@ def get_work_by_id(id, includes=[]):
|
|||||||
|
|
||||||
# Searching
|
# Searching
|
||||||
|
|
||||||
def search_artists(query='', limit=None, offset=None, **fields):
|
def search_artists(query='', limit=None, offset=None, strict=False, **fields):
|
||||||
"""Search for artists by a free-form `query` string and/or any of
|
"""Search for artists by a free-form `query` string or any of
|
||||||
the following keyword arguments specifying field queries:
|
the following keyword arguments specifying field queries:
|
||||||
arid, artist, sortname, type, begin, end, comment, alias, country,
|
arid, artist, sortname, type, begin, end, comment, alias, country,
|
||||||
gender, tag
|
gender, tag
|
||||||
|
When `fields` are set, special lucene characters are escaped
|
||||||
|
in the `query`.
|
||||||
"""
|
"""
|
||||||
return _do_mb_search('artist', query, fields, limit, offset)
|
return _do_mb_search('artist', query, fields, limit, offset, strict)
|
||||||
|
|
||||||
def search_labels(query='', limit=None, offset=None, **fields):
|
def search_labels(query='', limit=None, offset=None, strict=False, **fields):
|
||||||
"""Search for labels by a free-form `query` string and/or any of
|
"""Search for labels by a free-form `query` string or any of
|
||||||
the following keyword arguments specifying field queries:
|
the following keyword arguments specifying field queries:
|
||||||
laid, label, sortname, type, code, country, begin, end, comment,
|
laid, label, sortname, type, code, country, begin, end, comment,
|
||||||
alias, tag
|
alias, tag
|
||||||
|
When `fields` are set, special lucene characters are escaped
|
||||||
|
in the `query`.
|
||||||
"""
|
"""
|
||||||
return _do_mb_search('label', query, fields, limit, offset)
|
return _do_mb_search('label', query, fields, limit, offset, strict)
|
||||||
|
|
||||||
def search_recordings(query='', limit=None, offset=None, **fields):
|
def search_recordings(query='', limit=None, offset=None, strict=False, **fields):
|
||||||
"""Search for recordings by a free-form `query` string and/or any of
|
"""Search for recordings by a free-form `query` string or any of
|
||||||
the following keyword arguments specifying field queries:
|
the following keyword arguments specifying field queries:
|
||||||
rid, recording, isrc, arid, artist, artistname, creditname, reid,
|
rid, recording, isrc, arid, artist, artistname, creditname, reid,
|
||||||
release, type, status, tracks, tracksrelease, dur, qdur, tnum,
|
release, type, status, tracks, tracksrelease, dur, qdur, tnum,
|
||||||
position, tag
|
position, tag
|
||||||
|
When `fields` are set, special lucene characters are escaped
|
||||||
|
in the `query`.
|
||||||
"""
|
"""
|
||||||
return _do_mb_search('recording', query, fields, limit, offset)
|
return _do_mb_search('recording', query, fields, limit, offset, strict)
|
||||||
|
|
||||||
def search_releases(query='', limit=None, offset=None, **fields):
|
def search_releases(query='', limit=None, offset=None, strict=False, **fields):
|
||||||
"""Search for releases by a free-form `query` string and/or any of
|
"""Search for releases by a free-form `query` string or any of
|
||||||
the following keyword arguments specifying field queries:
|
the following keyword arguments specifying field queries:
|
||||||
reid, release, arid, artist, artistname, creditname, type, status,
|
reid, release, arid, artist, artistname, creditname, type, status,
|
||||||
tracks, tracksmedium, discids, discidsmedium, mediums, date, asin,
|
tracks, tracksmedium, discids, discidsmedium, mediums, date, asin,
|
||||||
lang, script, country, date, label, catno, barcode, puid
|
lang, script, country, date, label, catno, barcode, puid
|
||||||
|
When `fields` are set, special lucene characters are escaped
|
||||||
|
in the `query`.
|
||||||
"""
|
"""
|
||||||
return _do_mb_search('release', query, fields, limit, offset)
|
return _do_mb_search('release', query, fields, limit, offset, strict)
|
||||||
|
|
||||||
def search_release_groups(query='', limit=None, offset=None, **fields):
|
def search_release_groups(query='', limit=None, offset=None,
|
||||||
"""Search for release groups by a free-form `query` string and/or
|
strict=False, **fields):
|
||||||
|
"""Search for release groups by a free-form `query` string or
|
||||||
any of the following keyword arguments specifying field queries:
|
any of the following keyword arguments specifying field queries:
|
||||||
rgid, releasegroup, reid, release, arid, artist, artistname,
|
rgid, releasegroup, reid, release, arid, artist, artistname,
|
||||||
creditname, type, tag
|
creditname, type, tag
|
||||||
|
When `fields` are set, special lucene characters are escaped
|
||||||
|
in the `query`.
|
||||||
"""
|
"""
|
||||||
return _do_mb_search('release-group', query, fields, limit, offset)
|
return _do_mb_search('release-group', query, fields,
|
||||||
|
limit, offset, strict)
|
||||||
|
|
||||||
def search_works(query='', limit=None, offset=None, **fields):
|
def search_works(query='', limit=None, offset=None, strict=False, **fields):
|
||||||
"""Search for works by a free-form `query` string and/or any of
|
"""Search for works by a free-form `query` string or any of
|
||||||
the following keyword arguments specifying field queries:
|
the following keyword arguments specifying field queries:
|
||||||
wid, work, iswc, type, arid, artist, alias, tag
|
wid, work, iswc, type, arid, artist, alias, tag
|
||||||
|
When `fields` are set, special lucene characters are escaped
|
||||||
|
in the `query`.
|
||||||
"""
|
"""
|
||||||
return _do_mb_search('work', query, fields, limit, offset)
|
return _do_mb_search('work', query, fields, limit, offset, strict)
|
||||||
|
|
||||||
|
|
||||||
# Lists of entities
|
# Lists of entities
|
||||||
@@ -721,35 +778,40 @@ def get_releases_in_collection(collection):
|
|||||||
# Submission methods
|
# Submission methods
|
||||||
|
|
||||||
def submit_barcodes(barcodes):
|
def submit_barcodes(barcodes):
|
||||||
"""
|
"""Submits a set of {release1: barcode1, release2:barcode2}
|
||||||
Submits a set of {release1: barcode1, release2:barcode2}
|
|
||||||
Must call auth(user, pass) first
|
Must call auth(user, pass) first"""
|
||||||
"""
|
|
||||||
query = mbxml.make_barcode_request(barcodes)
|
query = mbxml.make_barcode_request(barcodes)
|
||||||
return _do_mb_post("release", query)
|
return _do_mb_post("release", query)
|
||||||
|
|
||||||
def submit_puids(puids):
|
def submit_puids(puids):
|
||||||
|
"""Submit PUIDs.
|
||||||
|
|
||||||
|
Must call auth(user, pass) first"""
|
||||||
query = mbxml.make_puid_request(puids)
|
query = mbxml.make_puid_request(puids)
|
||||||
return _do_mb_post("recording", query)
|
return _do_mb_post("recording", query)
|
||||||
|
|
||||||
def submit_echoprints(echoprints):
|
def submit_echoprints(echoprints):
|
||||||
|
"""Submit echoprints.
|
||||||
|
|
||||||
|
Must call auth(user, pass) first"""
|
||||||
query = mbxml.make_echoprint_request(echoprints)
|
query = mbxml.make_echoprint_request(echoprints)
|
||||||
return _do_mb_post("recording", query)
|
return _do_mb_post("recording", query)
|
||||||
|
|
||||||
def submit_isrcs(recordings_isrcs):
|
def submit_isrcs(recordings_isrcs):
|
||||||
"""
|
"""Submit ISRCs.
|
||||||
Submit ISRCs.
|
Submits a set of {recording-id: [isrc1, isrc1, ...]}
|
||||||
Submits a set of {recording-id: [isrc1, irc1]}
|
|
||||||
Must call auth(user, pass) first
|
Must call auth(user, pass) first"""
|
||||||
"""
|
|
||||||
query = mbxml.make_isrc_request(recordings_isrcs=recordings_isrcs)
|
query = mbxml.make_isrc_request(recordings_isrcs=recordings_isrcs)
|
||||||
return _do_mb_post("recording", query)
|
return _do_mb_post("recording", query)
|
||||||
|
|
||||||
def submit_tags(artist_tags={}, recording_tags={}):
|
def submit_tags(artist_tags={}, recording_tags={}):
|
||||||
""" Submit user tags.
|
"""Submit user tags.
|
||||||
Artist or recording parameters are of the form:
|
Artist or recording parameters are of the form:
|
||||||
{'entityid': [taglist]}
|
{'entityid': [taglist]}
|
||||||
"""
|
|
||||||
|
Must call auth(user, pass) first"""
|
||||||
query = mbxml.make_tag_request(artist_tags, recording_tags)
|
query = mbxml.make_tag_request(artist_tags, recording_tags)
|
||||||
return _do_mb_post("tag", query)
|
return _do_mb_post("tag", query)
|
||||||
|
|
||||||
@@ -757,15 +819,24 @@ def submit_ratings(artist_ratings={}, recording_ratings={}):
|
|||||||
""" Submit user ratings.
|
""" Submit user ratings.
|
||||||
Artist or recording parameters are of the form:
|
Artist or recording parameters are of the form:
|
||||||
{'entityid': rating}
|
{'entityid': rating}
|
||||||
"""
|
|
||||||
|
Must call auth(user, pass) first"""
|
||||||
query = mbxml.make_rating_request(artist_ratings, recording_ratings)
|
query = mbxml.make_rating_request(artist_ratings, recording_ratings)
|
||||||
return _do_mb_post("rating", query)
|
return _do_mb_post("rating", query)
|
||||||
|
|
||||||
def add_releases_to_collection(collection, releases=[]):
|
def add_releases_to_collection(collection, releases=[]):
|
||||||
|
"""Add releases to a collection.
|
||||||
|
Collection and releases should be identified by their MBIDs
|
||||||
|
|
||||||
|
Must call auth(user, pass) first"""
|
||||||
# XXX: Maximum URI length of 16kb means we should only allow ~400 releases
|
# XXX: Maximum URI length of 16kb means we should only allow ~400 releases
|
||||||
releaselist = ";".join(releases)
|
releaselist = ";".join(releases)
|
||||||
_do_mb_put("collection/%s/releases/%s" % (collection, releaselist))
|
_do_mb_put("collection/%s/releases/%s" % (collection, releaselist))
|
||||||
|
|
||||||
def remove_releases_from_collection(collection, releases=[]):
|
def remove_releases_from_collection(collection, releases=[]):
|
||||||
|
"""Remove releases from a collection.
|
||||||
|
Collection and releases should be identified by their MBIDs
|
||||||
|
|
||||||
|
Must call auth(user, pass) first"""
|
||||||
releaselist = ";".join(releases)
|
releaselist = ";".join(releases)
|
||||||
_do_mb_delete("collection/%s/releases/%s" % (collection, releaselist))
|
_do_mb_delete("collection/%s/releases/%s" % (collection, releaselist))
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# This file is part of the musicbrainzngs library
|
||||||
|
# Copyright (C) Alastair Porter, Adrian Sampson, and others
|
||||||
|
# This file is distributed under a BSD-2-Clause type license.
|
||||||
|
# See the COPYING file for more information.
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import locale
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
|
||||||
|
from . import compat
|
||||||
|
|
||||||
|
def _unicode(string, encoding=None):
|
||||||
|
"""Try to decode byte strings to unicode.
|
||||||
|
This can only be a guess, but this might be better than failing.
|
||||||
|
It is safe to use this on numbers or strings that are already unicode.
|
||||||
|
"""
|
||||||
|
if isinstance(string, compat.unicode):
|
||||||
|
unicode_string = string
|
||||||
|
elif isinstance(string, compat.bytes):
|
||||||
|
# use given encoding, stdin, preferred until something != None is found
|
||||||
|
if encoding is None:
|
||||||
|
encoding = sys.stdin.encoding
|
||||||
|
if encoding is None:
|
||||||
|
encoding = locale.getpreferredencoding()
|
||||||
|
unicode_string = string.decode(encoding, "ignore")
|
||||||
|
else:
|
||||||
|
unicode_string = compat.unicode(string)
|
||||||
|
return unicode_string.replace('\x00', '').strip()
|
||||||
|
|
||||||
|
def bytes_to_elementtree(_bytes):
|
||||||
|
if compat.is_py3:
|
||||||
|
s = _unicode(_bytes.read(), "utf-8")
|
||||||
|
else:
|
||||||
|
s = _bytes.read()
|
||||||
|
f = compat.StringIO(s)
|
||||||
|
tree = ET.ElementTree(file=f)
|
||||||
|
return tree
|
||||||
Reference in New Issue
Block a user