diff --git a/build/lib/discogs_client/__init__.py b/build/lib/discogs_client/__init__.py deleted file mode 100644 index be72688..0000000 --- a/build/lib/discogs_client/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -from __future__ import absolute_import, division, print_function, unicode_literals - -__version_info__ = 2, 2, 2 -__version__ = '2.2.2' - -from discogs_client.client import Client -from discogs_client.models import Artist, Release, Master, Label, User, \ - Listing, Track, Price, Video, List, ListItem diff --git a/build/lib/discogs_client/client.py b/build/lib/discogs_client/client.py deleted file mode 100644 index 87c4c21..0000000 --- a/build/lib/discogs_client/client.py +++ /dev/null @@ -1,198 +0,0 @@ -from __future__ import absolute_import, division, print_function, unicode_literals - -import warnings -import json -try: - # python2 - from urllib import urlencode -except ImportError: - # python3 - from urllib.parse import urlencode - -from discogs_client import models -from discogs_client.exceptions import ConfigurationError, HTTPError, AuthorizationError -from discogs_client.utils import update_qs -from discogs_client.fetchers import RequestsFetcher, OAuth2Fetcher, UserTokenRequestsFetcher - - -class Client(object): - _base_url = 'https://api.discogs.com' - _request_token_url = 'https://api.discogs.com/oauth/request_token' - _authorize_url = 'https://www.discogs.com/oauth/authorize' - _access_token_url = 'https://api.discogs.com/oauth/access_token' - - def __init__(self, user_agent, consumer_key=None, consumer_secret=None, token=None, secret=None, user_token=None): - """An interface to the Discogs API.""" - self.user_agent = user_agent - self.verbose = False - self._fetcher = RequestsFetcher() - - if consumer_key and consumer_secret: - self.set_consumer_key(consumer_key, consumer_secret) - if token and secret: - self.set_token(token, secret) - elif user_token is not None: - self._fetcher = UserTokenRequestsFetcher(user_token) - - def set_consumer_key(self, consumer_key, consumer_secret): - self._fetcher = OAuth2Fetcher(consumer_key, consumer_secret) - - def set_token(self, token, secret): - try: - self._fetcher.store_token(token, secret) - except AttributeError: - raise ConfigurationError('You must first set the consumer key and secret.') - - def get_authorize_url(self, callback_url=None): - """ - Returns a tuple of (, , ). - Send a Discogs user to the authorize URL to get the verifier for the access token. - """ - # Forget existing tokens - self._fetcher.forget_token() - - params = {} - params['User-Agent'] = self.user_agent - params['Content-Type'] = 'application/x-www-form-urlencoded' - if callback_url: - params['oauth_callback'] = callback_url - postdata = urlencode(params) - - content, status_code = self._fetcher.fetch(self, 'POST', self._request_token_url, data=postdata, headers=params) - if status_code != 200: - raise AuthorizationError('Could not get request token.', status_code, content) - - token, secret = self._fetcher.store_token_from_qs(content) - - params = {'oauth_token': token} - query_string = urlencode(params) - - return (token, secret, '?'.join((self._authorize_url, query_string))) - - def get_access_token(self, verifier): - """ - Uses the verifier to exchange a request token for an access token. - """ - if isinstance(verifier, bytes): - verifier = verifier.decode('utf8') - - self._fetcher.set_verifier(verifier) - - params = {} - params['User-Agent'] = self.user_agent - - content, status_code = self._fetcher.fetch(self, 'POST', self._access_token_url, headers=params) - if status_code != 200: - raise HTTPError('Invalid response from access token URL.', status_code) - - token, secret = self._fetcher.store_token_from_qs(content) - - return token, secret - - def _check_user_agent(self): - if not self.user_agent: - raise ConfigurationError('Invalid or no User-Agent set.') - - def _request(self, method, url, data=None): - if self.verbose: - print(' '.join((method, url))) - - self._check_user_agent() - - headers = { - 'Accept-Encoding': 'gzip', - 'User-Agent': self.user_agent, - } - - if data: - headers['Content-Type'] = 'application/x-www-form-urlencoded' - - content, status_code = self._fetcher.fetch(self, method, url, data=data, headers=headers) - - if status_code == 204: - return None - - body = json.loads(content.decode('utf8')) - - if 200 <= status_code < 300: - return body - else: - raise HTTPError(body['message'], status_code) - - def _get(self, url): - return self._request('GET', url) - - def _delete(self, url): - return self._request('DELETE', url) - - def _post(self, url, data): - return self._request('POST', url, data) - - def _patch(self, url, data): - return self._request('PATCH', url, data) - - def _put(self, url, data): - return self._request('PUT', url, data) - - def search(self, *query, **fields): - """ - Search the Discogs database. Returns a paginated list of objects - (Artists, Releases, Masters, and Labels). The keyword arguments to this - function are serialized into the request's query string. - """ - if query: - unicode_query = [] - for q in query: - try: - unicode_q = q.decode('utf8') - except (UnicodeDecodeError, UnicodeEncodeError, AttributeError): - unicode_q = q - unicode_query.append(unicode_q) - fields['q'] = ' '.join(unicode_query) - return models.MixedPaginatedList( - self, - update_qs(self._base_url + '/database/search', fields), - 'results' - ) - - def artist(self, id): - """Fetch an Artist by ID.""" - return models.Artist(self, {'id': id}) - - def release(self, id): - """Fetch a Release by ID.""" - return models.Release(self, {'id': id}) - - def master(self, id): - """Fetch a Master by ID.""" - return models.Master(self, {'id': id}) - - def label(self, id): - """Fetch a Label by ID.""" - return models.Label(self, {'id': id}) - - def list(self, id): - """Fetch a List by ID.""" - return models.List(self, {'id': id}) - - def user(self, username): - """Fetch a User by username.""" - return models.User(self, {'username': username}) - - def listing(self, id): - """Fetch a Marketplace Listing by ID.""" - return models.Listing(self, {'id': id}) - - def order(self, id): - """Fetch an Order by ID.""" - return models.Order(self, {'id': id}) - - def fee_for(self, price, currency='USD'): - """Calculate the fee for selling an item on the Marketplace.""" - resp = self._get('{0}/marketplace/fee/{1:.4f}/{2}'.format(self._base_url, price, currency)) - return models.Price(self, {'value': resp['value'], 'currency': resp['currency']}) - - def identity(self): - """Return a User object representing the OAuth-authorized user.""" - resp = self._get(self._base_url + '/oauth/identity') - return models.User(self, resp) diff --git a/build/lib/discogs_client/exceptions.py b/build/lib/discogs_client/exceptions.py deleted file mode 100644 index 38ba85a..0000000 --- a/build/lib/discogs_client/exceptions.py +++ /dev/null @@ -1,32 +0,0 @@ -from __future__ import absolute_import, division, print_function, unicode_literals - - -class DiscogsAPIError(Exception): - """Root Exception class for Discogs API errors.""" - pass - - -class ConfigurationError(DiscogsAPIError): - """Exception class for problems with the configuration of this client.""" - def __init__(self, msg): - self.msg = msg - - def __str__(self): - return self.msg - - -class HTTPError(DiscogsAPIError): - """Exception class for HTTP errors.""" - def __init__(self, message, code): - self.status_code = code - self.msg = '{0}: {1}'.format(code, message) - - def __str__(self): - return self.msg - - -class AuthorizationError(HTTPError): - """The server rejected the client's credentials.""" - def __init__(self, message, code, response): - super(AuthorizationError, self).__init__(message, code) - self.msg = '{0} Response: {1!r}'.format(self.msg, response) diff --git a/build/lib/discogs_client/fetchers.py b/build/lib/discogs_client/fetchers.py deleted file mode 100644 index ecf3d25..0000000 --- a/build/lib/discogs_client/fetchers.py +++ /dev/null @@ -1,192 +0,0 @@ -from __future__ import absolute_import, division, print_function, unicode_literals -import requests -from requests.api import request -from oauthlib import oauth1 -import json -import os -import re -try: - # python2 - from urlparse import parse_qsl -except ImportError: - # python3 - from urllib.parse import parse_qsl - - -class Fetcher(object): - """ - Base class for Fetchers, which wrap and normalize the APIs of various HTTP - libraries. - - (It's a slightly leaky abstraction designed to make testing easier.) - """ - def fetch(self, client, method, url, data=None, headers=None, json=True): - """Fetch the given request - - Returns - ------- - content : str (python2) or bytes (python3) - status_code : int - """ - raise NotImplementedError() - - -class LoggingDelegator(object): - """Wraps a fetcher and logs all requests.""" - def __init__(self, fetcher): - self.fetcher = fetcher - self.requests = [] - - @property - def last_request(self): - return self.requests[-1] if self.requests else None - - def fetch(self, client, method, url, data=None, headers=None, json=True): - self.requests.append((method, url, data, headers)) - return self.fetcher.fetch(client, method, url, data, headers, json) - - -class RequestsFetcher(Fetcher): - """Fetches via HTTP from the Discogs API.""" - def fetch(self, client, method, url, data=None, headers=None, json=True): - resp = requests.request(method, url, data=data, headers=headers) - self.rate_limit = resp.headers.get( - 'X-Discogs-Ratelimit') - self.rate_limit_used = resp.headers.get( - 'X-Discogs-Ratelimit-Used') - self.rate_limit_remaining = resp.headers.get( - 'X-Discogs-Ratelimit-Remaining') - return resp.content, resp.status_code - - -class UserTokenRequestsFetcher(Fetcher): - """Fetches via HTTP from the Discogs API using user_token authentication""" - def __init__(self, user_token): - self.user_token = user_token - - def fetch(self, client, method, url, data=None, headers=None, json=True): - resp = requests.request(method, url, params={'token':self.user_token}, - data=data, headers=headers) - self.rate_limit = resp.headers.get( - 'X-Discogs-Ratelimit') - self.rate_limit_used = resp.headers.get( - 'X-Discogs-Ratelimit-Used') - self.rate_limit_remaining = resp.headers.get( - 'X-Discogs-Ratelimit-Remaining') - return resp.content, resp.status_code - - -class OAuth2Fetcher(Fetcher): - """Fetches via HTTP + OAuth 1.0a from the Discogs API.""" - def __init__(self, consumer_key, consumer_secret, token=None, secret=None): - self.client = oauth1.Client(consumer_key, client_secret=consumer_secret) - self.store_token(token, secret) - - def store_token_from_qs(self, query_string): - token_dict = dict(parse_qsl(query_string)) - token = token_dict[b'oauth_token'].decode('utf8') - secret = token_dict[b'oauth_token_secret'].decode('utf8') - self.store_token(token, secret) - return token, secret - - def forget_token(self): - self.store_token(None, None) - - def store_token(self, token, secret): - self.client.resource_owner_key = token - self.client.resource_owner_secret = secret - - def set_verifier(self, verifier): - self.client.verifier = verifier - - def fetch(self, client, method, url, data=None, headers=None, json_format=True): - body = json.dumps(data) if json_format and data else data - uri, headers, body = self.client.sign(url, http_method=method, - body=data, headers=headers) - - resp = request(method, uri, headers=headers, data=body) - self.rate_limit = resp.headers.get( - 'X-Discogs-Ratelimit') - self.rate_limit_used = resp.headers.get( - 'X-Discogs-Ratelimit-Used') - self.rate_limit_remaining = resp.headers.get( - 'X-Discogs-Ratelimit-Remaining') - return resp.content, resp.status_code - - -class FilesystemFetcher(Fetcher): - """Fetches from a directory of files.""" - default_response = json.dumps({'message': 'Resource not found.'}).encode('utf8'), 404 - path_with_params = re.compile('(?P(\w+/)+)(?P\w+)\?(?P.*)') - - def __init__(self, base_path): - self.base_path = base_path - - def fetch(self, client, method, url, data=None, headers=None, json=True): - url = url.replace(client._base_url, '') - - if json: - base_name = ''.join((url[1:], '.json')) - else: - base_name = url[1:] - - path = os.path.join(self.base_path, base_name) - - # The exact path might not exist, but check for files with different - # permutations of query parameters. - if not os.path.exists(path): - base_name = self.check_alternate_params(base_name, json) - path = os.path.join(self.base_path, base_name) - - try: - path = path.replace('?', '_') # '?' is illegal in file names on Windows - with open(path, 'r') as f: - content = f.read().encode('utf8') # return bytes not unicode - return content, 200 - except: - return self.default_response - - def check_alternate_params(self, base_name, json): - """ - parse_qs() result is non-deterministic - a different file might be - requested, making the tests fail randomly, depending on the order of parameters in the query. - This fixes it by checking for matching file names with a different permutations of the parameters. - """ - match = self.path_with_params.match(base_name) - - # No parameters in query - no match. Nothing to do. - if not match: - return base_name - - ext = '.json' if json else '' - - # The base name consists of one or more path elements (directories), - # a query (discogs.com endpoint), query parameters, and possibly an extension like 'json'. - # Extract these. - base_dir = os.path.join(self.base_path, match.group('dir')) - query = match.group('query') # we'll need this to only check relevant filenames - params_str = match.group('params')[:-len(ext)] # strip extension if any - params = set(params_str.split('&')) - - # List files that match the same query, possibly with different parameters - filenames = [f for f in os.listdir(base_dir) if f.startswith(query)] - for f in filenames: - # Strip the query, the '?' sign (or its replacement) and the extension, if any - params2_str = f[len(query) + 1:-len(ext)] - params2 = set(params2_str.split('&')) - if params == params2: - return base_name.replace(params_str, params2_str) - - # No matching alternatives found - revert to original. - return base_name - - -class MemoryFetcher(Fetcher): - """Fetches from a dict of URL -> (content, status_code).""" - default_response = json.dumps({'message': 'Resource not found.'}).encode('utf8'), 404 - - def __init__(self, responses): - self.responses = responses - - def fetch(self, client, method, url, data=None, headers=None, json=True): - return self.responses.get(url, self.default_response) diff --git a/build/lib/discogs_client/models.py b/build/lib/discogs_client/models.py deleted file mode 100644 index 310fa3a..0000000 --- a/build/lib/discogs_client/models.py +++ /dev/null @@ -1,774 +0,0 @@ -from __future__ import absolute_import, division, print_function, unicode_literals -import sys - -from six import with_metaclass - -from discogs_client.exceptions import HTTPError -from discogs_client.utils import parse_timestamp, update_qs, omit_none - - -class SimpleFieldDescriptor(object): - """ - An attribute that determines its value using the object's fetch() method. - - If transform is a callable, the value will be passed through transform when - read. Useful for strings that should be ints, parsing timestamps, etc. - - Shorthand for: - - @property - def foo(self): - return self.fetch('foo') - """ - def __init__(self, name, writable=False, transform=None): - self.name = name - self.writable = writable - self.transform = transform - - def __get__(self, instance, owner): - if instance is None: - return self - value = instance.fetch(self.name) - if self.transform: - value = self.transform(value) - return value - - def __set__(self, instance, value): - if self.writable: - instance.changes[self.name] = value - return - raise AttributeError("can't set attribute") - - -class ObjectFieldDescriptor(object): - """ - An attribute that determines its value using the object's fetch() method, - and passes the resulting value through an APIObject. - - If optional = True, the value will be None (rather than an APIObject - instance) if the key is missing from the response. - - If as_id = True, the value is treated as an ID for the new APIObject rather - than a partial dict of the APIObject. - - Shorthand for: - - @property - def baz(self): - return BazClass(self.client, self.fetch('baz')) - """ - def __init__(self, name, class_name, optional=False, as_id=False): - self.name = name - self.class_name = class_name - self.optional = optional - self.as_id = as_id - - def __get__(self, instance, owner): - if instance is None: - return self - wrapper_class = CLASS_MAP[self.class_name.lower()] - response_dict = instance.fetch(self.name) - if self.optional and not response_dict: - return None - if self.as_id: - # Response_dict wasn't really a dict. Make it so. - response_dict = {'id': response_dict} - return wrapper_class(instance.client, response_dict) - - def __set__(self, instance, value): - raise AttributeError("can't set attribute") - - -class ListFieldDescriptor(object): - """ - An attribute that determines its value using the object's fetch() method, - and passes each item in the resulting list through an APIObject. - - Shorthand for: - - @property - def bar(self): - return [BarClass(self.client, d) for d in self.fetch('bar', [])] - """ - def __init__(self, name, class_name): - self.name = name - self.class_name = class_name - - def __get__(self, instance, owner): - if instance is None: - return self - wrapper_class = CLASS_MAP[self.class_name.lower()] - return [wrapper_class(instance.client, d) for d in instance.fetch(self.name, [])] - - def __set__(self, instance, value): - raise AttributeError("can't set attribute") - - -class ObjectCollectionDescriptor(object): - """ - An attribute that determines its value by fetching a URL to a paginated - list of related objects, and passes each item in the resulting list through - an APIObject. - - Shorthand for: - - @property - def frozzes(self): - return PaginatedList(self.client, self.fetch('frozzes_url'), 'frozzes', FrozClass) - """ - def __init__(self, name, class_name, url_key=None, list_class=None): - self.name = name - self.class_name = class_name - - if url_key is None: - url_key = name + '_url' - self.url_key = url_key - - if list_class is None: - list_class = PaginatedList - self.list_class = list_class - - def __get__(self, instance, owner): - if instance is None: - return self - wrapper_class = CLASS_MAP[self.class_name.lower()] - return self.list_class(instance.client, instance.fetch(self.url_key), self.name, wrapper_class) - - def __set__(self, instance, value): - raise AttributeError("can't set attribute") - - -class Field(object): - """ - A placeholder for a descriptor. Is transformed into a descriptor by the - APIObjectMeta metaclass when the APIObject classes are created. - """ - _descriptor_class = None - - def __init__(self, *args, **kwargs): - self.key = kwargs.pop('key', None) - self.args = args - self.kwargs = kwargs - - def to_descriptor(self, attr_name): - return self._descriptor_class(self.key or attr_name, *self.args, **self.kwargs) - - -class SimpleField(Field): - """A field that just returns the value of a given JSON key.""" - _descriptor_class = SimpleFieldDescriptor - - -class ListField(Field): - """A field that returns a list of APIObjects.""" - _descriptor_class = ListFieldDescriptor - - -class ObjectField(Field): - """A field that returns a single APIObject.""" - _descriptor_class = ObjectFieldDescriptor - - -class ObjectCollection(Field): - """A field that returns a paginated list of APIObjects.""" - _descriptor_class = ObjectCollectionDescriptor - - -class APIObjectMeta(type): - def __new__(cls, name, bases, dict_): - for k, v in dict_.items(): - if isinstance(v, Field): - dict_[k] = v.to_descriptor(k) - return super(APIObjectMeta, cls).__new__(cls, name, bases, dict_) - - -class APIObject(with_metaclass(APIObjectMeta, object)): - def repr_str(self, string): - if sys.version_info < (3,): - return string.encode('utf-8') - return string - - -class PrimaryAPIObject(APIObject): - """A first-order API object that has a canonical endpoint of its own.""" - def __init__(self, client, dict_): - self.data = dict_ - self.client = client - self._known_invalid_keys = [] - self.changes = {} - - def __eq__(self, other): - if isinstance(other, self.__class__): - return self.id == other.id - return NotImplemented - - def __ne__(self, other): - equal = self.__eq__(other) - return NotImplemented if equal is NotImplemented else not equal - - def refresh(self): - if self.data.get('resource_url'): - data = self.client._get(self.data['resource_url']) - self.data.update(data) - self.changes = {} - - def save(self): - if self.data.get('resource_url'): - # TODO: This should be PATCH - self.client._post(self.data['resource_url'], self.changes) - - # Refresh the object, in case there were side-effects - self.refresh() - - def delete(self): - if self.data.get('resource_url'): - self.client._delete(self.data['resource_url']) - - def fetch(self, key, default=None): - if key in self._known_invalid_keys: - return default - - try: - # First, look in the cache of pending changes - return self.changes[key] - except KeyError: - pass - - try: - # Next, look in the potentially incomplete local cache - return self.data[key] - except KeyError: - pass - - # Now refresh the object from its resource_url. - # The key might exist but not be in our cache. - self.refresh() - - try: - return self.data[key] - except: - self._known_invalid_keys.append(key) - return default - - -# This is terribly cheesy, but makes the client API more consistent -class SecondaryAPIObject(APIObject): - """ - An object that wraps parts of a response and doesn't have its own - endpoint. - """ - def __init__(self, client, dict_): - self.client = client - self.data = dict_ - - def fetch(self, key, default=None): - return self.data.get(key, default) - - -class BasePaginatedResponse(object): - """Base class for lists of objects spread across many URLs.""" - def __init__(self, client, url): - self.client = client - self.url = url - self._num_pages = None - self._num_items = None - self._pages = {} - self._per_page = 50 - self._list_key = 'items' - self._sort_key = None - self._sort_order = 'asc' - self._filters = {} - - @property - def per_page(self): - return self._per_page - - @per_page.setter - def per_page(self, value): - self._per_page = value - self._invalidate() - - def _invalidate(self): - self._pages = {} - self._num_pages = None - self._num_items = None - - def _load_pagination_info(self): - data = self.client._get(self._url_for_page(1)) - self._pages[1] = [ - self._transform(item) for item in data[self._list_key] - ] - self._num_pages = data['pagination']['pages'] - self._num_items = data['pagination']['items'] - - def _url_for_page(self, page): - base_qs = { - 'page': page, - 'per_page': self._per_page, - } - - if self._sort_key is not None: - base_qs.update({ - 'sort': self._sort_key, - 'sort_order': self._sort_order, - }) - - base_qs.update(self._filters) - - return update_qs(self.url, base_qs) - - def sort(self, key, order='asc'): - if order not in ('asc', 'desc'): - raise ValueError("Order must be one of 'asc', 'desc'") - self._sort_key = key - self._sort_order = order - self._invalidate() - return self - - def filter(self, **kwargs): - self._filters = kwargs - self._invalidate() - return self - - @property - def pages(self): - if self._num_pages is None: - self._load_pagination_info() - return self._num_pages - - @property - def count(self): - if self._num_items is None: - self._load_pagination_info() - return self._num_items - - def page(self, index): - if index not in self._pages: - data = self.client._get(self._url_for_page(index)) - self._pages[index] = [ - self._transform(item) for item in data[self._list_key] - ] - return self._pages[index] - - def _transform(self, item): - return item - - def __getitem__(self, index): - page_index = index // self.per_page + 1 - offset = index % self.per_page - - try: - page = self.page(page_index) - except HTTPError as e: - if e.status_code == 404: - raise IndexError(e.msg) - else: - raise - - return page[offset] - - def __len__(self): - return self.count - - def __iter__(self): - for i in range(1, self.pages + 1): - page = self.page(i) - for item in page: - yield item - - -class PaginatedList(BasePaginatedResponse): - """A paginated list of objects of a particular class.""" - def __init__(self, client, url, key, class_): - super(PaginatedList, self).__init__(client, url) - self._list_key = key - self.class_ = class_ - - def _transform(self, item): - return self.class_(self.client, item) - - -class Wantlist(PaginatedList): - def add(self, release, notes=None, notes_public=None, rating=None): - release_id = release.id if isinstance(release, Release) else release - data = { - 'release_id': str(release_id), - 'notes': notes, - 'notes_public': notes_public, - 'rating': rating, - } - self.client._put(self.url + '/' + str(release_id), omit_none(data)) - self._invalidate() - - def remove(self, release): - release_id = release.id if isinstance(release, Release) else release - self.client._delete(self.url + '/' + str(release_id)) - self._invalidate() - -class OrderMessagesList(PaginatedList): - def add(self, message=None, status=None, email_buyer=True, email_seller=False): - data = { - 'message': message, - 'status': status, - 'email_buyer': email_buyer, - 'email_seller': email_seller, - } - self.client._post(self.url, omit_none(data)) - self._invalidate() - - -class MixedPaginatedList(BasePaginatedResponse): - """A paginated list of objects identified by their type parameter.""" - def __init__(self, client, url, key): - super(MixedPaginatedList, self).__init__(client, url) - self._list_key = key - - def _transform(self, item): - # In some cases, we want to map the 'title' key we get back in search - # results to 'name'. This way, you can repr() a page of search results - # without making 50 requests. - if item['type'] in ('label', 'artist'): - item['name'] = item['title'] - - return CLASS_MAP[item['type']](self.client, item) - - -class Artist(PrimaryAPIObject): - id = SimpleField() - name = SimpleField() - real_name = SimpleField(key='realname') - images = SimpleField() - profile = SimpleField() - data_quality = SimpleField() - name_variations = SimpleField(key='namevariations') - url = SimpleField(key='uri') - urls = SimpleField() - aliases = ListField('Artist') - members = ListField('Artist') - groups = ListField('Artist') - - def __init__(self, client, dict_): - super(Artist, self).__init__(client, dict_) - self.data['resource_url'] = '{0}/artists/{1}'.format(client._base_url, dict_['id']) - - @property - def releases(self): - return MixedPaginatedList(self.client, self.fetch('releases_url'), 'releases') - - def __repr__(self): - return self.repr_str(''.format(self.id, self.name)) - - -class Release(PrimaryAPIObject): - id = SimpleField() - title = SimpleField() - year = SimpleField() - thumb = SimpleField() - data_quality = SimpleField() - status = SimpleField() - genres = SimpleField() - images = SimpleField() - country = SimpleField() - notes = SimpleField() - formats = SimpleField() - styles = SimpleField() - url = SimpleField(key='uri') - videos = ListField('Video') - tracklist = ListField('Track') - artists = ListField('Artist') - credits = ListField('Artist', key='extraartists') - labels = ListField('Label') - companies = ListField('Label') - - def __init__(self, client, dict_): - super(Release, self).__init__(client, dict_) - self.data['resource_url'] = '{0}/releases/{1}'.format(client._base_url, dict_['id']) - - @property - def master(self): - master_id = self.fetch('master_id') - if master_id: - return Master(self.client, {'id': master_id}) - else: - return None - - def __repr__(self): - return self.repr_str(''.format(self.id, self.title)) - - -class Master(PrimaryAPIObject): - id = SimpleField() - title = SimpleField() - data_quality = SimpleField() - styles = SimpleField() - genres = SimpleField() - images = SimpleField() - url = SimpleField(key='uri') - videos = ListField('Video') - tracklist = ListField('Track') - main_release = ObjectField('Release', as_id=True) - versions = ObjectCollection('Release') - - def __init__(self, client, dict_): - super(Master, self).__init__(client, dict_) - self.data['resource_url'] = '{0}/masters/{1}'.format(client._base_url, dict_['id']) - - def __repr__(self): - return self.repr_str(''.format(self.id, self.title)) - - -class Label(PrimaryAPIObject): - id = SimpleField() - name = SimpleField() - profile = SimpleField() - urls = SimpleField() - images = SimpleField() - contact_info = SimpleField() - data_quality = SimpleField() - url = SimpleField(key='uri') - sublabels = ListField('Label') - parent_label = ObjectField('Label', optional=True) - releases = ObjectCollection('Release') - - def __init__(self, client, dict_): - super(Label, self).__init__(client, dict_) - self.data['resource_url'] = '{0}/labels/{1}'.format(client._base_url, dict_['id']) - - def __repr__(self): - return self.repr_str('