Source code for minim.discogs

"""
Discogs
=======
.. moduleauthor:: Benjamin Ye <GitHub: bbye98>

This module contains a complete implementation of the Discogs API.
"""

from http.server import HTTPServer, BaseHTTPRequestHandler
import json
import logging
from multiprocessing import Process
import os
from pathlib import Path
import re
import secrets
import time
from typing import Any, Union
import urllib
import warnings
import webbrowser

import requests

from . import (
    FOUND_FLASK,
    FOUND_PLAYWRIGHT,
    VERSION,
    REPOSITORY_URL,
    DIR_HOME,
    DIR_TEMP,
    _config,
)

if FOUND_FLASK:
    from flask import Flask, request
if FOUND_PLAYWRIGHT:
    from playwright.sync_api import sync_playwright

__all__ = ["API"]


class _DiscogsRedirectHandler(BaseHTTPRequestHandler):
    """
    HTTP request handler for the Discogs OAuth 1.0a flow.
    """

    def do_GET(self):
        """
        Handles an incoming GET request and parses the query string.
        """
        self.server.response = dict(
            urllib.parse.parse_qsl(urllib.parse.urlparse(f"{self.path}").query)
        )
        self.send_response(200)
        self.send_header("Content-Type", "text/html")
        self.end_headers()
        status = "denied" if "denied" in self.server.response else "granted"
        self.wfile.write(
            f"Access {status}. You may close this page now.".encode()
        )


[docs] class API: """ Discogs API client. The Discogs API lets developers build their own Discogs-powered applications for the web, desktop, and mobile devices. It is a RESTful interface to Discogs data and enables accessing JSON- formatted information about artists, releases, and labels, managing user collections and wantlists, creating marketplace listings, and more. .. seealso:: For more information, see the `Discogs API home page <https://www.discogs.com/developers>`_. The Discogs API can be accessed with or without authentication. (client credentials, personal access token, or OAuth access token and access token secret). However, it is recommended that users at least provide client credentials to enjoy higher rate limits and access to image URLs. The consumer key and consumer secret can either be provided to this class's constructor as keyword arguments or be stored as :code:`DISCOGS_CONSUMER_KEY` and :code:`DISCOGS_CONSUMER_SECRET` in the operating system's environment variables. .. seealso:: To get client credentials, see the Registration section of the `Authentication page <https://www.discogs.com/developers /#page:authentication>`_ of the Discogs API website. To take advantage of Minim's automatic access token retrieval functionality for the OAuth 1.0a flow, the redirect URI should be in the form :code:`http://localhost:{port}/callback`, where :code:`{port}` is an open port on :code:`localhost`. To view and make changes to account information and resources, users must either provide a personal access token to this class's constructor as a keyword argument or undergo the OAuth 1.0a flow, which require valid client credentials, using Minim. If an existing OAuth access token/secret pair is available, it can be provided to this class's constructor as keyword arguments to bypass the access token retrieval process. .. tip:: The authorization flow and access token can be changed or updated at any time using :meth:`set_flow` and :meth:`set_access_token`, respectively. Minim also stores and manages access tokens and their properties. When the OAuth 1.0a flow is used to acquire an access token/secret pair, it is automatically saved to the Minim configuration file to be loaded on the next instantiation of this class. This behavior can be disabled if there are any security concerns, like if the computer being used is a shared device. Parameters ---------- consumer_key : `str`, keyword-only, optional Consumer key. Required for the OAuth 1.0a flow, and can be used in the Discogs authorization flow alongside a consumer secret. If it is not stored as :code:`DISCOGS_CONSUMER_KEY` in the operating system's environment variables or found in the Minim configuration file, it can be provided here. consumer_secret : `str`, keyword-only, optional Consumer secret. Required for the OAuth 1.0a flow, and can be used in the Discogs authorization flow alongside a consumer key. If it is not stored as :code:`DISCOGS_CONSUMER_SECRET` in the operating system's environment variables or found in the Minim configuration file, it can be provided here. flow : `str`, keyword-only, optional Authorization flow. If :code:`None` and no access token is provided, no user authentication will be performed and client credentials will not be attached to requests, even if found or provided. .. container:: **Valid values**: * :code:`None` for no user authentication. * :code:`"discogs"` for the Discogs authentication flow. * :code:`"oauth"` for the OAuth 1.0a flow. browser : `bool`, keyword-only, default: :code:`False` Determines whether a web browser is automatically opened for the OAuth 1.0a flow. If :code:`False`, users will have to manually open the authorization URL and provide the full callback URI via the terminal. web_framework : `str`, keyword-only, optional Determines which web framework to use for the OAuth 1.0a flow. .. container:: **Valid values**: * :code:`"http.server"` for the built-in implementation of HTTP servers. * :code:`"flask"` for the Flask framework. * :code:`"playwright"` for the Playwright framework by Microsoft. port : `int` or `str`, keyword-only, default: :code:`8888` Port on :code:`localhost` to use for the OAuth 1.0a flow with the :code:`http.server` and Flask frameworks. Only used if `redirect_uri` is not specified. redirect_uri : `str`, keyword-only, optional Redirect URI for the OAuth 1.0a flow. If not on :code:`localhost`, the automatic request access token retrieval functionality is not available. access_token : `str`, keyword-only, optional Personal or OAuth access token. If provided here or found in the Minim configuration file, the authentication process is bypassed. access_token_secret : `str`, keyword-only, optional OAuth access token secret accompanying `access_token`. overwrite : `bool`, keyword-only, default: :code:`False` Determines whether to overwrite an existing access token in the Minim configuration file. save : `bool`, keyword-only, default: :code:`True` Determines whether newly obtained access tokens and their associated properties are stored to the Minim configuration file. Attributes ---------- API_URL : `str` Base URL for the Discogs API. ACCESS_TOKEN_URL : `str` URL for the OAuth 1.0a access token endpoint. AUTH_URL : `str` URL for the OAuth 1.0a authorization endpoint. REQUEST_TOKEN_URL : `str` URL for the OAuth 1.0a request token endpoint. session : `requests.Session` Session used to send requests to the Discogs API. """ _FLOWS = {"discogs", "oauth"} _NAME = f"{__module__}.{__qualname__}" API_URL = "https://api.discogs.com" ACCESS_TOKEN_URL = f"{API_URL}/oauth/access_token" AUTH_URL = "https://www.discogs.com/oauth/authorize" REQUEST_TOKEN_URL = f"{API_URL}/oauth/request_token" def __init__( self, *, consumer_key: str = None, consumer_secret: str = None, flow: str = None, browser: bool = False, web_framework: str = None, port: Union[int, str] = 8888, redirect_uri: str = None, access_token: str = None, access_token_secret: str = None, overwrite: bool = False, save: bool = True, ) -> None: """ Create a Discogs API client. """ self.session = requests.Session() self.session.headers["User-Agent"] = ( f"Minim/{VERSION} +{REPOSITORY_URL}" ) if ( access_token is None and _config.has_section(self._NAME) and not overwrite ): flow = _config.get(self._NAME, "flow") access_token = _config.get(self._NAME, "access_token") access_token_secret = _config.get( self._NAME, "access_token_secret" ) consumer_key = _config.get(self._NAME, "consumer_key") consumer_secret = _config.get(self._NAME, "consumer_secret") elif flow is None and access_token is not None: flow = "discogs" if access_token_secret is None else "oauth" self.set_flow( flow, consumer_key=consumer_key, consumer_secret=consumer_secret, browser=browser, web_framework=web_framework, port=port, redirect_uri=redirect_uri, save=save, ) self.set_access_token(access_token, access_token_secret) def _check_authentication(self, endpoint: str, token: bool = True) -> None: """ Check if the user is authenticated for the desired endpoint. Parameters ---------- endpoint : `str` Discogs API endpoint. token : `bool`, default: :code:`True` Specifies whether a personal access token or OAuth access token is required for the endpoint. If :code:`False`, only client credentials are required. """ if token and ( self._flow != "oauth" or self._flow == "discogs" and "token" not in self.session.headers["Authorization"] ): emsg = f"{self._NAME}.{endpoint}() requires user authentication." raise RuntimeError(emsg) elif self._flow is None: emsg = f"{self._NAME}.{endpoint}() requires client credentials." raise RuntimeError(emsg) def _get_json(self, url: str, **kwargs) -> dict: """ Send a GET request and return the JSON-encoded content of the response. Parameters ---------- url : `str` URL for the GET request. **kwargs Keyword arguments to pass to :meth:`requests.request`. Returns ------- resp : `dict` JSON-encoded content of the response. """ return self._request("get", url, **kwargs).json() def _request( self, method: str, url: str, *, oauth: dict[str, Any] = None, **kwargs ) -> requests.Response: """ Construct and send a request with status code checking. Parameters ---------- method : `str` Method for the request. url : `str` URL for the request. oauth : `dict`, keyword-only, optional OAuth-related values to be included in the authorization header. **kwargs Keyword arguments passed to :meth:`requests.request`. Returns ------- resp : `requests.Response` Response to the request. """ if "headers" not in kwargs: kwargs["headers"] = {} if self._flow == "oauth" and "Authorization" not in kwargs["headers"]: if oauth is None: oauth = {} oauth = ( self._oauth | { "oauth_nonce": secrets.token_hex(32), "oauth_timestamp": f"{time.time():.0f}", } | oauth ) kwargs["headers"]["Authorization"] = "OAuth " + ", ".join( f'{k}="{v}"' for k, v in oauth.items() ) r = self.session.request(method, url, **kwargs) if r.status_code not in range(200, 299): j = r.json() emsg = f"{r.status_code}: {j['message']}" if "detail" in j["message"]: emsg += f"\n{json.dumps(j['detail'], indent=2)}" raise RuntimeError(emsg) return r
[docs] def set_access_token( self, access_token: str = None, access_token_secret: str = None ) -> None: """ Set the Discogs API personal or OAuth access token (and secret). Parameters ---------- access_token : `str`, optional Personal or OAuth access token. access_token_secret : `str`, optional OAuth access token secret. """ if self._flow == "oauth": self._oauth = { "oauth_consumer_key": self._consumer_key, "oauth_signature_method": "PLAINTEXT", } if access_token is None: oauth = {"oauth_signature": f"{self._consumer_secret}&"} if self._redirect_uri is not None: oauth["oauth_callback"] = self._redirect_uri r = self._request( "get", self.REQUEST_TOKEN_URL, headers={ "Content-Type": "application/x-www-form-urlencoded" }, oauth=oauth, ) auth_url = f"{self.AUTH_URL}?{r.text}" oauth = dict(urllib.parse.parse_qsl(r.text)) if self._web_framework == "playwright": har_file = DIR_TEMP / "minim_discogs.har" with sync_playwright() as playwright: browser = playwright.firefox.launch(headless=False) context = browser.new_context(record_har_path=har_file) page = context.new_page() page.goto(auth_url, timeout=0) page.wait_for_url( f"{self._redirect_uri}*", wait_until="commit" ) context.close() browser.close() with open(har_file, "r") as f: oauth |= dict( urllib.parse.parse_qsl( urllib.parse.urlparse( re.search( rf'{self._redirect_uri}\?(.*?)"', f.read(), ).group(0) ).query ) ) har_file.unlink() else: if self._browser: webbrowser.open(auth_url) else: print( "To grant Minim access to Discogs data " "and features, open the following link " f"in your web browser:\n\n{auth_url}\n" ) if self._web_framework == "http.server": httpd = HTTPServer( ("", self._port), _DiscogsRedirectHandler ) httpd.handle_request() oauth |= httpd.response elif self._web_framework == "flask": app = Flask(__name__) json_file = DIR_TEMP / "minim_discogs.json" @app.route("/callback", methods=["GET"]) def _callback() -> str: if "error" in request.args: return ( "Access denied. You may close " "this page now." ) with open(json_file, "w") as f: json.dump(request.args, f) return ( "Access granted. You may close this page now." ) server = Process( target=app.run, args=("0.0.0.0", self._port) ) server.start() while not json_file.is_file(): time.sleep(0.1) server.terminate() with open(json_file, "rb") as f: oauth |= json.load(f) json_file.unlink() else: oauth["oauth_verifier"] = input( "After authorizing Minim to access Discogs " "on your behalf, enter the displayed code " "below.\n\nCode: " ) if "denied" in oauth: raise RuntimeError("Authorization failed.") oauth["oauth_signature"] = ( f"{self._consumer_secret}&{oauth['oauth_token_secret']}" ) r = self._request( "post", self.ACCESS_TOKEN_URL, headers={ "Content-Type": "application/x-www-form-urlencoded" }, oauth=oauth, ) access_token, access_token_secret = dict( urllib.parse.parse_qsl(r.text) ).values() if self._save: _config[self._NAME] = { "flow": self._flow, "access_token": access_token, "access_token_secret": access_token_secret, "consumer_key": self._consumer_key, "consumer_secret": self._consumer_secret, } with open(DIR_HOME / "minim.cfg", "w") as f: _config.write(f) self._oauth |= { "oauth_token": access_token, "oauth_signature": self._consumer_secret + f"&{access_token_secret}", } elif self._flow == "discogs": if access_token is None: if self._consumer_key is None or self._consumer_secret is None: emsg = "Discogs API client credentials not provided." raise ValueError(emsg) self.session.headers["Authorization"] = ( f"Discogs key={self._consumer_key}, " f"secret={self._consumer_secret}" ) else: self.session.headers["Authorization"] = ( f"Discogs token={access_token}" ) if ( self._flow == "oauth" or self._flow == "discogs" and "token" in self.session.headers["Authorization"] ): identity = self.get_identity() self._username = identity["username"]
[docs] def set_flow( self, flow: str, *, consumer_key: str = None, consumer_secret: str = None, browser: bool = False, web_framework: str = None, port: Union[int, str] = 8888, redirect_uri: str = None, save: bool = True, ) -> None: """ Set the authorization flow. Parameters ---------- flow : `str` Authorization flow. If :code:`None`, no user authentication will be performed and client credentials will not be attached to requests, even if found or provided. .. container:: **Valid values**: * :code:`None` for no user authentication. * :code:`"discogs"` for the Discogs authentication flow. * :code:`"oauth"` for the OAuth 1.0a flow. consumer_key : `str`, keyword-only, optional Consumer key. Required for the OAuth 1.0a flow, and can be used in the Discogs authorization flow alongside a consumer secret. If it is not stored as :code:`DISCOGS_CONSUMER_KEY` in the operating system's environment variables or found in the Minim configuration file, it can be provided here. consumer_secret : `str`, keyword-only, optional Consumer secret. Required for the OAuth 1.0a flow, and can be used in the Discogs authorization flow alongside a consumer key. If it is not stored as :code:`DISCOGS_CONSUMER_SECRET` in the operating system's environment variables or found in the Minim configuration file, it can be provided here. browser : `bool`, keyword-only, default: :code:`False` Determines whether a web browser is automatically opened for the OAuth 1.0a flow. If :code:`False`, users will have to manually open the authorization URL and provide the full callback URI via the terminal. web_framework : `str`, keyword-only, optional Determines which web framework to use for the OAuth 1.0a flow. .. container:: **Valid values**: * :code:`"http.server"` for the built-in implementation of HTTP servers. * :code:`"flask"` for the Flask framework. * :code:`"playwright"` for the Playwright framework by Microsoft. port : `int` or `str`, keyword-only, default: :code:`8888` Port on :code:`localhost` to use for the OAuth 1.0a flow with the :code:`http.server` and Flask frameworks. Only used if `redirect_uri` is not specified. redirect_uri : `str`, keyword-only, optional Redirect URI for the OAuth 1.0a flow. If not on :code:`localhost`, the automatic request access token retrieval functionality is not available. save : `bool`, keyword-only, default: :code:`True` Determines whether newly obtained access tokens and their associated properties are stored to the Minim configuration file. """ if flow and flow not in self._FLOWS: emsg = ( f"Invalid authorization flow ({flow=}). " f"Valid values: {', '.join(self._FLOWS)}." ) raise ValueError(emsg) self._flow = flow self._save = save self._consumer_key = consumer_key or os.environ.get( "DISCOGS_CONSUMER_KEY" ) self._consumer_secret = consumer_secret or os.environ.get( "DISCOGS_CONSUMER_SECRET" ) if flow == "oauth": self._browser = browser if redirect_uri: self._redirect_uri = redirect_uri if "localhost" in redirect_uri: self._port = re.search( r"localhost:(\d+)", redirect_uri ).group(1) elif web_framework: wmsg = ( "The redirect URI is not on localhost, " "so automatic authorization code " "retrieval is not available." ) logging.warning(wmsg) web_framework = None elif port: self._port = port self._redirect_uri = f"http://localhost:{port}/callback" else: self._port = self._redirect_uri = None self._web_framework = ( web_framework if web_framework is None or web_framework == "http.server" or globals()[f"FOUND_{web_framework.upper()}"] else None ) if self._web_framework is None and web_framework: wmsg = ( f"The {web_framework.capitalize()} web " "framework was not found, so automatic " "authorization code retrieval is not " "available." ) warnings.warn(wmsg)
### DATABASE ##############################################################
[docs] def get_release( self, release_id: Union[int, str], *, curr_abbr: str = None ) -> dict[str, Any]: """ `Database > Release <https://www.discogs.com /developers/#page:database,header:database-release-get>`_: Get a release (physical or digital object released by one or more artists). Parameters ---------- release_id : `int` or `str` The release ID. **Example**: :code:`249504`. curr_abbr : `str`, keyword-only, optional Currency abbreviation for marketplace data. Defaults to the authenticated user's currency. **Valid values**: :code:`"USD"`, :code:`"GBP"`, :code:`"EUR"`, :code:`"CAD"`, :code:`"AUD"`, :code:`"JPY"`, :code:`"CHF"`, :code:`"MXN"`, :code:`"BRL"`, :code:`"NZD"`, :code:`"SEK"`, and :code:`"ZAR"`. Returns ------- release : `dict` Discogs database information for a single release. .. admonition:: Sample response :class: dropdown .. code:: { "title": <str>, "id": <int>, "artists": [ { "anv": <str>, "id": <int>, "join": <str>, "name": <str>, "resource_url": <str>, "role": <str>, "tracks": <str> } ], "data_quality": <str>, "thumb": <str>, "community": { "contributors": [ { "resource_url": <str>, "username": <str> } ], "data_quality": <str>, "have": <int>, "rating": { "average": <float>, "count": <int> }, "status": <str>, "submitter": { "resource_url": <str>, "username": <str> }, "want": <int> }, "companies": [ { "catno": <str>, "entity_type": <str>, "entity_type_name": <str>, "id": <int>, "name": <str>, "resource_url": <str> } ], "country": <str>, "date_added": <str>, "date_changed": <str>, "estimated_weight": <int>, "extraartists": [ { "anv": <str>, "id": <int>, "join": <str>, "name": <str>, "resource_url": <str>, "role": <str>, "tracks": <str> } ], "format_quantity": <int>, "formats": [ { "descriptions": [<str>], "name": <str>, "qty": <str> } ], "genres": [<str>], "identifiers": [ { "type": <str>, "value": <str> }, ], "images": [ { "height": <int>, "resource_url": <str>, "type": <str>, "uri": <str>, "uri150": <str>, "width": <int> } ], "labels": [ { "catno": <str>, "entity_type": <str>, "id": <int>, "name": <str>, "resource_url": <str> } ], "lowest_price": <float>, "master_id": <int>, "master_url": <str>, "notes": <str>, "num_for_sale": <int>, "released": <str>, "released_formatted": <str>, "resource_url": <str>, "series": [], "status": <str>, "styles": [<str>], "tracklist": [ { "duration": <str>, "position": <str>, "title": <str>, "type_": <str> } ], "uri": <str>, "videos": [ { "description": <str>, "duration": <int>, "embed": <bool>, "title": <str>, "uri": <str> }, ], "year": <int> } """ if curr_abbr and curr_abbr not in ( CURRENCIES := { "USD", "GBP", "EUR", "CAD", "AUD", "JPY", "CHF", "MXN", "BRL", "NZD", "SEK", "ZAR", } ): emsg = ( f"Invalid currency abbreviation ({curr_abbr=}). " f"Valid values: {', '.join(CURRENCIES)}." ) raise ValueError(emsg) return self._get_json( f"{self.API_URL}/releases/{release_id}", params={"curr_abbr": curr_abbr}, )
[docs] def get_user_release_rating( self, release_id: Union[int, str], username: str = None ) -> dict[str, Any]: """ `Database > Release Rating By User > Get Release Rating By User <https://www.discogs.com/developers /#page:database,header:database-release-get>`_: Retrieves the release's rating for a given user. Parameters ---------- release_id : `int` or `str` The release ID. **Example**: :code:`249504`. username : `str`, optional The username of the user whose rating you are requesting. If not specified, the username of the authenticated user is used. **Example**: :code:`"memory"`. Returns ------- rating : `dict` Rating for the release by the given user. .. admonition:: Sample response :class: dropdown .. code:: { "username": <str>, "release_id": <int>, "rating": <int> } """ if username is None: if hasattr(self, "_username"): username = self._username else: raise ValueError("No username provided.") return self._get_json( f"{self.API_URL}/releases/{release_id}/rating/{username}" )
[docs] def update_user_release_rating( self, release_id: Union[int, str], rating: int, username: str = None ) -> dict[str, Any]: """ `Database > Release Rating By User > Update Release Rating By User <https://www.discogs.com/developers /#page:database,header:database-release-rating-by-user-put>`_: Updates the release's rating for a given user. .. admonition:: User authentication :class: warning Requires user authentication with a personal access token or via the OAuth 1.0a flow. Parameters ---------- release_id : `int` or `str` The release ID. **Example**: :code:`249504`. rating : `int` The new rating for a release between :math:`1` and :math:`5`. username : `str`, optional The username of the user whose rating you are requesting. If not specified, the username of the authenticated user is used. **Example**: :code:`"memory"`. Returns ------- rating : `dict` Updated rating for the release by the given user. .. admonition:: Sample response :class: dropdown .. code:: { "username": <str>, "release_id": <int>, "rating": <int> } """ self._check_authentication("update_user_release_rating") if username is None: if hasattr(self, "_username"): username = self._username else: raise ValueError("No username provided.") return self._request( "put", f"{self.API_URL}/releases/{release_id}/rating/{username}", json={"rating": rating}, )
[docs] def delete_user_release_rating( self, release_id: Union[int, str], username: str = None ) -> None: """ `Database > Release Rating By User > Delete Release Rating By User <https://www.discogs.com/developers /#page:database,header:database-release-rating-by-user-delete>`_: Deletes the release's rating for a given user. .. admonition:: User authentication :class: warning Requires user authentication with a personal access token or via the OAuth 1.0a flow. Parameters ---------- release_id : `int` or `str` The release ID. **Example**: :code:`249504`. username : `str`, optional The username of the user whose rating you are requesting. If not specified, the username of the authenticated user is used. **Example**: :code:`"memory"`. """ self._check_authentication("delete_user_release_rating") if username is None: if hasattr(self, "_username"): username = self._username else: raise ValueError("No username provided.") return self._request( "delete", f"{self.API_URL}/releases/{release_id}/rating/{username}" )
[docs] def get_community_release_rating( self, release_id: Union[int, str] ) -> dict[str, Any]: """ `Database > Community Release Rating <https://www.discogs.com /developers/#page:database,header :database-community-release-rating-get>`_: Retrieves the community release rating average and count. Parameters ---------- release_id : `int` or `str` The release ID. **Example**: :code:`249504`. Returns ------- rating : `dict` Community release rating average and count. .. admonition:: Sample response :class: dropdown .. code:: { "rating": { "count": <int>, "average": <float> }, "release_id": <int> } """ return self._get_json(f"{self.API_URL}/releases/{release_id}/rating")
[docs] def get_release_stats(self, release_id: Union[int, str]) -> dict[str, Any]: """ `Database > Release Stats <https://www.discogs.com/developers /#page:database,header:database-release-stats-get>`_: Retrieves the release's "have" and "want" counts. .. attention:: This endpoint does not appear to be working correctly. Currently, the response will be of the form .. code:: { "is_offense": <bool> } Parameters ---------- release_id : `int` or `str` The release ID. **Example**: :code:`249504`. Returns ------- stats : `dict` Release "have" and "want" counts. .. admonition:: Sample response :class: dropdown .. code:: { "num_have": <int>, "num_want": <int> } """ return self._get_json(f"{self.API_URL}/releases/{release_id}/stats")
[docs] def get_master_release(self, master_id: Union[int, str]) -> dict[str, Any]: """ `Database > Master Release <https://www.discogs.com/developers /#page:database,header:database-master-release-get>`_: Get a master release. Parameters ---------- master_id : `int` or `str` The master release ID. **Example**: :code:`1000`. Returns ------- master_release : `dict` Discogs database information for a single master release. .. admonition:: Sample response :class: dropdown .. code:: { "styles": [<str>], "genres": [<str>], "videos": [ { "duration": <int>, "description": <str>, "embed": <bool>, "uri": <str>, "title": <str> } ], "title": <str>, "main_release": <int>, "main_release_url": <str>, "uri": <str>, "artists": [ { "join": <str>, "name": <str>, "anv": <str>, "tracks": <str>, "role": <str>, "resource_url": <str>, "id": <int> } ], "versions_url": <str>, "year": <int>, "images": [ { "height": <int>, "resource_url": <str>, "type": <str>, "uri": <str>, "uri150": <str>, "width": <int> } ], "resource_url": <str>, "tracklist": [ { "duration": <str>, "position": <str>, "type_": <str>, "extraartists": [ { "join": <str>, "name": <str>, "anv": <str>, "tracks": <str>, "role": <str>, "resource_url": <str>, "id": <int> } ], "title": <str> } ], "id": <int>, "num_for_sale": <int>, "lowest_price": <float>, "data_quality": <str> } """ return self._get_json(f"{self.API_URL}/masters/{master_id}")
[docs] def get_master_release_versions( self, master_id: Union[int, str], *, country: str = None, format: str = None, label: str = None, released: str = None, page: Union[int, str] = None, per_page: Union[int, str] = None, sort: str = None, sort_order: str = None, ) -> dict[str, Any]: """ `Database > Master Release Versions <https://www.discogs.com /developers/#page:database,header :database-master-release-versions-get>`_: Retrieves a list of all releases that are versions of this master. Parameters ---------- master_id : `int` or `str` The master release ID. **Example**: :code:`1000`. country : `str`, keyword-only, optional The country to filter for. **Example**: :code:`"Belgium"`. format : `str`, keyword-only, optional The format to filter for. **Example**: :code:`"Vinyl"`. label : `str`, keyword-only, optional The label to filter for. **Example**: :code:`"Scorpio Music"`. released : `str`, keyword-only, optional The release year to filter for. **Example**: :code:`"1992"`. page : `int` or `str`, keyword-only, optional The page you want to request. **Example**: :code:`3`. per_page : `int` or `str`, keyword-only, optional The number of items per page. **Example**: :code:`25`. sort : `str`, keyword-only, optional Sort items by this field. **Valid values**: :code:`"released"`, :code:`"title"`, :code:`"format"`, :code:`"label"`, :code:`"catno"`, and :code:`"country"`. sort_order : `str`, keyword-only, optional Sort items in a particular order. **Valid values**: :code:`"asc"` and :code:`"desc"`. Returns ------- versions : `dict` Discogs database information for all releases that are versions of the specified master. .. admonition:: Sample response :class: dropdown .. code:: { "pagination": { "items": <int>, "page": <int>, "pages": <int>, "per_page": <int>, "urls": { "last": <str>, "next": <str> } }, "versions": [ { "status": <str>, "stats": { "user": { "in_collection": <int>, "in_wantlist": <int> }, "community": { "in_collection": <int>, "in_wantlist": <int> } }, "thumb": <str>, "format": <str>, "country": <str>, "title": <str>, "label": <str>, "released": <str>, "major_formats": [<str>], "catno": <str>, "resource_url": <str>, "id": <int> } ] } """ return self._get_json( f"{self.API_URL}/masters/{master_id}/versions", params={ "country": country, "format": format, "label": label, "released": released, "page": page, "per_page": per_page, "sort": sort, "sort_order": sort_order, }, )
[docs] def get_artist(self, artist_id: Union[int, str]) -> dict[str, Any]: """ `Database > Artist <https://www.discogs.com/developers /#page:database,header:database-artist-get>`_: Get an artist. Parameters ---------- artist_id : `int` or `str` The artist ID. **Example**: :code:`108713`. Returns ------- artist : `dict` Discogs database information for a single artist. .. admonition:: Sample response :class: dropdown .. code:: { "namevariations": [<str>], "profile": <str>, "releases_url": <str>, "resource_url": <str>, "uri": <str>, "urls": [<str>], "data_quality": <str>, "id": <int>, "images": [ { "height": <int>, "resource_url": <str>, "type": <str>, "uri": <str>, "uri150": <str>, "width": <int> } ], "members": [ { "active": <bool>, "id": <int>, "name": <str>, "resource_url": <str> } ] } """ return self._get_json(f"{self.API_URL}/artists/{artist_id}")
[docs] def get_artist_releases( self, artist_id: Union[int, str], *, page: Union[int, str] = None, per_page: Union[int, str] = None, sort: str = None, sort_order: str = None, ) -> dict[str, Any]: """ `Database > Artist Releases <https://www.discogs.com/developers /#page:database,header:database-artist-releases-get>`_: Get an artist's releases and masters. Parameters ---------- artist_id : `int` or `str` The artist ID. **Example**: :code:`108713`. page : `int` or `str`, keyword-only, optional The page you want to request. **Example**: :code:`3`. per_page : `int` or `str`, keyword-only, optional The number of items per page. **Example**: :code:`25`. sort : `str`, keyword-only, optional Sort results by this field. **Valid values**: :code:`"year"`, :code:`"title"`, and :code:`"format"`. sort_order : `str`, keyword-only, optional Sort results in a particular order. **Valid values**: :code:`"asc"` and :code:`"desc"`. Returns ------- releases : `dict` Discogs database information for all releases by the specified artist. .. admonition:: Sample response :class: dropdown .. code:: { "pagination": { "items": <int>, "page": <int>, "pages": <int>, "per_page": <int>, "urls": { "last": <str>, "next": <str> } }, "releases": [ { "artist": <str>, "id": <int>, "main_release": <int>, "resource_url": <str>, "role": <str>, "thumb": <str>, "title": <str>, "type": <str>, "year": <int> } ] } """ return self._get_json( f"{self.API_URL}/artists/{artist_id}/releases", params={ "page": page, "per_page": per_page, "sort": sort, "sort_order": sort_order, }, )
[docs] def get_label(self, label_id: Union[int, str]) -> dict[str, Any]: """ `Database > Label <https://www.discogs.com/developers /#page:database,header:database-label-get>`_: Get a label, company, recording studio, locxation, or other entity involved with artists and releases. Parameters ---------- label_id : `int` or `str` The label ID. **Example**: :code:`1`. Returns ------- label : `dict` Discogs database information for a single label. .. admonition:: Sample response :class: dropdown .. code:: { "profile": <str>, "releases_url": <str>, "name": <str>, "contact_info": <str>, "uri": <str>, "sublabels": [ { "resource_url": <str>, "id": <int>, "name": <str> } ], "urls": [<str>], "images": [ { "height": <int>, "resource_url": <str>, "type": <str>, "uri": <str>, "uri150": <str>, "width": <int> } ], "resource_url": <str>, "id": <int>, "data_quality": <str> } """ return self._get_json(f"{self.API_URL}/labels/{label_id}")
[docs] def get_label_releases( self, label_id: Union[int, str], *, page: Union[int, str] = None, per_page: Union[int, str] = None, ) -> dict[str, Any]: """ `Database > Label Releases <https://www.discogs.com/developers /#page:database,header:database-all-label-releases-get>`_: Get a list of releases associated with the label. Parameters ---------- label_id : `int` or `str` The label ID. **Example**: :code:`1`. page : `int` or `str`, keyword-only, optional The page you want to request. **Example**: :code:`3`. per_page : `int` or `str`, keyword-only, optional The number of items per page. **Example**: :code:`25`. Returns ------- releases : `dict` Discogs database information for all releases by the specified label. .. admonition:: Sample response :class: dropdown .. code:: { "pagination": { "items": <int>, "page": <int>, "pages": <int>, "per_page": <int>, "urls": { "last": <str>, "next": <str> } }, "releases": [ { "artist": <str>, "catno": <str>, "format": <str>, "id": <int>, "resource_url": <str>, "status": <str>, "thumb": <str>, "title": <str>, "year": <int> } ] } """ return self._get_json( f"{self.API_URL}/labels/{label_id}/releases", params={"page": page, "per_page": per_page}, )
[docs] def search( self, query: str = None, *, type: str = None, title: str = None, release_title: str = None, credit: str = None, artist: str = None, anv: str = None, label: str = None, genre: str = None, style: str = None, country: str = None, year: str = None, format: str = None, catno: str = None, barcode: str = None, track: str = None, submitter: str = None, contributor: str = None, ) -> dict[str, Any]: """ `Database > Search <https://www.discogs.com/developers /#page:database,header:database-search-get>`_: Issue a search query to the Discogs database. .. admonition:: Authentication :class: warning Requires authentication with consumer credentials, with a personal access token, or via the OAuth 1.0a flow. Parameters ---------- query : `str`, optional The search query. **Example**: :code:`"Nirvana"`. type : `str`, keyword-only, optional The type of item to search for. **Valid values**: :code:`"release"`, :code:`"master"`, :code:`"artist"`, and :code:`"label"`. title : `str`, keyword-only, optional Search by combined :code:`"<artist name> - <release title>"` title field. **Example**: :code:`"Nirvana - Nevermind"`. release_title : `str`, keyword-only, optional Search release titles. **Example**: :code:`"Nevermind"`. credit : `str`, keyword-only, optional Search release credits. **Example**: :code:`"Kurt"`. artist : `str`, keyword-only, optional Search artist names. **Example**: :code:`"Nirvana"`. anv : `str`, keyword-only, optional Search artist name variations (ANV). **Example**: :code:`"Nirvana"`. label : `str`, keyword-only, optional Search labels. **Example**: :code:`"DGC"`. genre : `str`, keyword-only, optional Search genres. **Example**: :code:`"Rock"`. style : `str`, keyword-only, optional Search styles. **Example**: :code:`"Grunge"`. country : `str`, keyword-only, optional Search release country. **Example**: :code:`"Canada"`. year : `str`, keyword-only, optional Search release year. **Example**: :code:`"1991"`. format : `str`, keyword-only, optional Search formats. **Example**: :code:`"Album"`. catno : `str`, keyword-only, optional Search catalog number. **Example**: :code:`"DGCD-24425"`. barcode : `str`, keyword-only, optional Search barcode. **Example**: :code:`"720642442524"`. track : `str`, keyword-only, optional Search track. **Example**: :code:`"Smells Like Teen Spirit"`. submitter : `str`, keyword-only, optional Search submitter username. **Example**: :code:`"milKt"`. contributor : `str`, keyword-only, optional Search contributor username. **Example**: :code:`"jerome99"`. Returns ------- results : `dict` Search results. .. admonition:: Sample response :class: dropdown .. code:: { "pagination": { "items": <int>, "page": <int>, "pages": <int>, "per_page": <int>, "urls": { "last": <str>, "next": <str> } }, "results": [ { "style": [<str>], "thumb": <str>, "title": <str>, "country": <str>, "format": [<str>], "uri": <str>, "community": { "want": <int>, "have": <int> }, "label": [<str>], "catno": <str>, "year": <str>, "genre": [<str>], "resource_url": <str>, "type": <str>, "id": <int> } ] } """ self._check_authentication("search", False) return self._get_json( f"{self.API_URL}/database/search", params={ "q": query, "type": type, "title": title, "release_title": release_title, "credit": credit, "artist": artist, "anv": anv, "label": label, "genre": genre, "style": style, "country": country, "year": year, "format": format, "catno": catno, "barcode": barcode, "track": track, "submitter": submitter, "contributor": contributor, }, )
### MARKETPLACE ###########################################################
[docs] def get_inventory( self, username: str = None, *, status: str = None, page: Union[int, str] = None, per_page: Union[int, str] = None, sort: str = None, sort_order: str = None, ) -> dict[str, Any]: """ `Marketplace > Inventory <https://www.discogs.com/developers /#page:marketplace,header:marketplace-inventory-get>`_: Get a seller's inventory. .. admonition:: User authentication :class: dropdown warning If you are authenticated as the inventory owner, additional fields will be returned in the response, such as :code:`"weight"`, :code:`"format_quantity"`, :code:`"external_id"`, :code:`"location"`, and :code:`"quantity"`. Parameters ---------- username : `str` The username of the inventory owner. If not specified, the username of the authenticated user is used. **Example**: :code:`"360vinyl"`. status : `str`, keyword-only, optional The status of the listings to return. **Valid values**: :code:`"For Sale"`, :code:`"Draft"`, :code:`"Expired"`, :code:`"Sold"`, and :code:`"Deleted"`. page : `int` or `str`, keyword-only, optional The page you want to request. **Example**: :code:`3`. per_page : `int` or `str`, keyword-only, optional The number of items per page. **Example**: :code:`25`. sort : `str`, keyword-only, optional Sort items by this field. **Valid values**: :code:`"listed"`, :code:`"price"`, :code:`"item"`, :code:`"artist"`, :code:`"label"`, :code:`"catno"`, :code:`"audio"`, :code:`"status"`, and :code:`"location"`. sort_order : `str`, keyword-only, optional Sort items in a particular order. **Valid values**: :code:`"asc"` and :code:`"desc"`. Returns ------- inventory : `dict` The seller's inventory. .. admonition:: Sample response :class: dropdown .. code:: { "pagination": { "page": <int>, "pages": <int>, "per_page": <int>, "items": <int>, "urls": {} }, "listings": [ { "status": <str>, "price": { "currency": <str>, "value": <float> }, "allow_offers": <bool>, "sleeve_condition": <str>, "id": <int>, "condition": <str>, "posted": <str>, "ships_from": <str>, "uri": <str>, "comments": <str>, "seller": { "username": <str>, "resource_url": <str>, "id": <int> }, "release": { "catalog_number": <str>, "resource_url": <str>, "year": <int>, "id": <int>, "description": <str>, "artist": <str>, "title": <str>, "format": <str>, "thumbnail": <str> }, "resource_url": <str>, "audio": <bool> } ] } """ if username is None: if hasattr(self, "_username"): username = self._username else: raise ValueError("No username provided.") return self._get_json( f"{self.API_URL}/users/{username}/inventory", params={ "status": status, "page": page, "per_page": per_page, "sort": sort, "sort_order": sort_order, }, )
[docs] def get_listing( self, listing_id: Union[int, str], *, curr_abbr: str = None ) -> dict[str, Any]: """ `Marketplace > Listing <https://www.discogs.com/developers /#page:marketplace,header:marketplace-listing-get>`_: View marketplace listings. Parameters ---------- listing_id : `int` or `str` The ID of the listing you are fetching. **Example**: :code:`172723812`. curr_abbr : `str`, keyword-only, optional Currency abbreviation for marketplace listings. Defaults to the authenticated user's currency. **Valid values**: :code:`"USD"`, :code:`"GBP"`, :code:`"EUR"`, :code:`"CAD"`, :code:`"AUD"`, :code:`"JPY"`, :code:`"CHF"`, :code:`"MXN"`, :code:`"BRL"`, :code:`"NZD"`, :code:`"SEK"`, and :code:`"ZAR"`. Returns ------- listing : `dict` The marketplace listing. .. admonition:: Sample response :class: dropdown .. code:: { "status": <str>, "price": { "currency": <str>, "value": <int> }, "original_price": { "curr_abbr": <str>, "curr_id": <int>, "formatted": <str>, "value": <float> }, "allow_offers": <bool>, "sleeve_condition": <str>, "id": <int>, "condition": <str>, "posted": <str>, "ships_from": <str>, "uri": <str>, "comments": <str>, "seller": { "username": <str>, "avatar_url": <str>, "resource_url": <str>, "url": <str>, "id": <int>, "shipping": <str>, "payment": <str>, "stats": { "rating": <str>, "stars": <float>, "total": <int> } }, "shipping_price": { "currency": <str>, "value": <float> }, "original_shipping_price": { "curr_abbr": <str>, "curr_id": <int>, "formatted": <str>, "value": <float> }, "release": { "catalog_number": <str>, "resource_url": <str>, "year": <int>, "id": <int>, "description": <str>, "thumbnail": <str>, }, "resource_url": <str>, "audio": <bool> } """ return self._get_json( f"{self.API_URL}/marketplace/listings/{listing_id}", params={"curr_abbr": curr_abbr}, )
[docs] def create_listing( self, release_id: Union[int, str], condition: str, price: float, status: str = "For Sale", *, sleeve_condition: str = None, comments: str = None, allow_offers: bool = None, external_id: str = None, location: str = None, weight: float = None, format_quantity: int = None, ) -> dict[str, Any]: """ `Marketplace > New Listing <https://www.discogs.com/developers /#page:marketplace,header:marketplace-new-listing>`_: Create a marketplace listing. .. admonition:: User authentication :class: warning Requires user authentication with a personal access token or via the OAuth 1.0a flow. Parameters ---------- release_id : `int` or `str` The ID of the release you are posting. **Example**: :code:`249504`. condition : `str` The condition of the release you are posting. **Valid values**: :code:`"Mint (M)"`, :code:`"Near Mint (NM or M-)"`, :code:`"Very Good Plus (VG+)"`, :code:`"Very Good (VG)"`, :code:`"Good Plus (G+)"`, :code:`"Good (G)"`, :code:`"Fair (F)"`, and :code:`"Poor (P)"`. price : `float` The price of the item (in the seller's currency). **Example**: :code:`10.00`. status : `str`, default: :code:`"For Sale"` The status of the listing. **Valid values**: :code:`"For Sale"` (the listing is ready to be shwon on the marketplace) and :code:`"Draft"` (the listing is not ready for public display). sleeve_condition : `str`, optional The condition of the sleeve of the item you are posting. **Valid values**: :code:`"Mint (M)"`, :code:`"Near Mint (NM or M-)"`, :code:`"Very Good Plus (VG+)"`, :code:`"Very Good (VG)"`, :code:`"Good Plus (G+)"`, :code:`"Good (G)"`, :code:`"Fair (F)"`, and :code:`"Poor (P)"`. comments : `str`, optional Any remarks about the item that will be displated to buyers. allow_offers : `bool`, optional Whether or not to allow buyers to make offers on the item. **Default**: :code:`False`. external_id : `str`, optional A freeform field that can be used for the seller's own reference. Information stored here will not be displayed to anyone other than the seller. This field is called “Private Comments” on the Discogs website. location : `str`, optional A freeform field that is intended to help identify an item's physical storage location. Information stored here will not be displayed to anyone other than the seller. This field will be visible on the inventory management page and will be available in inventory exports via the website. weight : `float`, optional The weight, in grams, of this listing, for the purpose of calculating shipping. Set this field to :code:`"auto"` to have the weight automatically estimated for you. format_quantity : `int`, optional The number of items this listing counts as, for the purpose of calculating shipping. This field is called "Counts As" on the Discogs website. Set this field to :code:`"auto"` to have the quantity automatically estimated for you. """ self._check_authentication("create_listing") return self._request( "post", f"{self.API_URL}/marketplace/listings", params={ "release_id": release_id, "condition": condition, "price": price, "status": status, "sleeve_condition": sleeve_condition, "comments": comments, "allow_offers": allow_offers, "external_id": external_id, "location": location, "weight": weight, "format_quantity": format_quantity, }, ).json()
[docs] def edit_listing( self, listing_id: Union[int, str], release_id: Union[int, str], condition: str, price: float, status: str = "For Sale", *, sleeve_condition: str = None, comments: str = None, allow_offers: bool = None, external_id: str = None, location: str = None, weight: float = None, format_quantity: int = None, ) -> None: """ `Marketplace > Listing > Edit Listing <https://www.discogs.com /developers/#page:marketplace,header:marketplace-listing-post>`_: Edit the data associated with a listing. If the listing's status is not :code:`"For Sale"`, :code:`"Draft"`, or :code:`"Expired"`, it cannot be modified—only deleted. To re-list a :code:`"Sold"` listing, a new listing must be created. .. admonition:: User authentication :class: warning Requires user authentication with a personal access token or via the OAuth 1.0a flow. Parameters ---------- listing_id : `int` or `str` The ID of the listing you are fetching. **Example**: :code:`172723812`. release_id : `int` or `str` The ID of the release you are posting. **Example**: :code:`249504`. condition : `str` The condition of the release you are posting. **Valid values**: :code:`"Mint (M)"`, :code:`"Near Mint (NM or M-)"`, :code:`"Very Good Plus (VG+)"`, :code:`"Very Good (VG)"`, :code:`"Good Plus (G+)"`, :code:`"Good (G)"`, :code:`"Fair (F)"`, and :code:`"Poor (P)"`. price : `float` The price of the item (in the seller's currency). **Example**: :code:`10.00`. status : `str`, default: :code:`"For Sale"` The status of the listing. **Valid values**: :code:`"For Sale"` (the listing is ready to be shwon on the marketplace) and :code:`"Draft"` (the listing is not ready for public display). sleeve_condition : `str`, optional The condition of the sleeve of the item you are posting. **Valid values**: :code:`"Mint (M)"`, :code:`"Near Mint (NM or M-)"`, :code:`"Very Good Plus (VG+)"`, :code:`"Very Good (VG)"`, :code:`"Good Plus (G+)"`, :code:`"Good (G)"`, :code:`"Fair (F)"`, and :code:`"Poor (P)"`. comments : `str`, optional Any remarks about the item that will be displated to buyers. allow_offers : `bool`, optional Whether or not to allow buyers to make offers on the item. **Default**: :code:`False`. external_id : `str`, optional A freeform field that can be used for the seller's own reference. Information stored here will not be displayed to anyone other than the seller. This field is called “Private Comments” on the Discogs website. location : `str`, optional A freeform field that is intended to help identify an item's physical storage location. Information stored here will not be displayed to anyone other than the seller. This field will be visible on the inventory management page and will be available in inventory exports via the website. weight : `float`, optional The weight, in grams, of this listing, for the purpose of calculating shipping. Set this field to :code:`"auto"` to have the weight automatically estimated for you. format_quantity : `int`, optional The number of items this listing counts as, for the purpose of calculating shipping. This field is called "Counts As" on the Discogs website. Set this field to :code:`"auto"` to have the quantity automatically estimated for you. """ self._check_authentication("edit_listing") self._request( "post", f"{self.API_URL}/marketplace/listings/{listing_id}", json={ "release_id": release_id, "condition": condition, "price": price, "status": status, "sleeve_condition": sleeve_condition, "comments": comments, "allow_offers": allow_offers, "external_id": external_id, "location": location, "weight": weight, "format_quantity": format_quantity, }, )
[docs] def delete_listing(self, listing_id: Union[int, str]) -> None: """ `Marketplace > Listing > Delete Listing <https://www.discogs.com /developers/#page:marketplace,header :marketplace-listing-delete>`_: Permanently remove a listing from the marketplace. .. admonition:: User authentication :class: warning Requires user authentication with a personal access token or via the OAuth 1.0a flow. Parameters ---------- listing_id : `int` or `str` The ID of the listing you are fetching. **Example**: :code:`172723812`. """ self._check_authentication("delete_listing") self._request( "delete", f"{self.API_URL}/marketplace/listings/{listing_id}" )
[docs] def get_order(self, order_id: str) -> dict[str, Any]: """ `Marketplace > Order > Get Order <https://www.discogs.com/developers #page:marketplace,header:marketplace-order-get>`_: View the data associated with an order. .. admonition:: User authentication :class: warning Requires user authentication with a personal access token or via the OAuth 1.0a flow. Parameters ---------- order_id : `str` The ID of the order you are fetching. **Example**: :code:`1-1`. Returns ------- order : `dict` The marketplace order. .. admonition:: Sample response :class: dropdown .. code:: { "id": <str>, "resource_url": <str>, "messages_url": <str>, "uri": <str>, "status": <str>, "next_status": [<str>], "fee": { "currency": <str>, "value": <float> }, "created": <str>, "items": [ { "release": { "id": <int>, "description": <str>, }, "price": { "currency": <str>, "value": <int> }, "media_condition": <str>, "sleeve_condition": <str>, "id": <int> } ], "shipping": { "currency": <str>, "method": <str>, "value": <int> }, "shipping_address": <str>, "additional_instructions": <str>, "archived": <bool>, "seller": { "resource_url": <str>, "username": <str>, "id": <int> }, "last_activity": <str>, "buyer": { "resource_url": <str>, "username": <str>, "id": <int> }, "total": { "currency": <str>, "value": <int> } } """ self._check_authentication("get_order") return self._get_json(f"{self.API_URL}/marketplace/orders/{order_id}")
[docs] def edit_order( self, order_id: str, status: str, *, shipping: float = None ) -> dict[str, Any]: """ `Marketplace > Order > Edit Order <https://www.discogs.com/developers #page:marketplace,header:marketplace-order-post>`_: Edit the data associated with an order. The response contains a :code:`"next_status"` key—an array of valid next statuses for this order. Changing the order status using this resource will always message the buyer with Seller changed status from [...] to [...] and does not provide a facility for including a custom message along with the change. For more fine-grained control, use the :meth:`add_order_message` method, which allows you to simultaneously add a message and change the order status. If the order status is not :code:`"Cancelled"`, :code:`"Payment Received"`, or :code:`"Shipped"`, you can change the shipping. Doing so will send an invoice to the buyer and set the order status to :code:`"Invoice Sent"`. (For that reason, you cannot set the shipping and the order status in the same request.) .. admonition:: User authentication :class: warning Requires user authentication with a personal access token or via the OAuth 1.0a flow. Parameters ---------- order_id : `str` The ID of the order you are fetching. **Example**: :code:`1-1`. status : `str` The status of the order you are updating. The new status must be present in the order's :code:`"next_status"` list. **Valid values**: :code:`"New Order"`, :code:`"Buyer Contacted"`, :code:`"Invoice Sent"`, :code:`"Payment Pending"`, :code:`"Payment Received"`, :code:`"In Progress"`, :code:`"Shipped"`, :code:`"Refund Sent"`, :code:`"Cancelled (Non-Paying Buyer)"`, :code:`"Cancelled (Item Unavailable)"`, and :code:`"Cancelled (Per Buyer's Request)"`. shipping : `float`, optional The order shipping amount. As a side effect of setting this value, the buyer is invoiced and the order status is set to :code:`"Invoice Sent"`. **Example**: :code:`5.00`. Returns ------- order : `dict` The marketplace order. .. admonition:: Sample response :class: dropdown .. code:: { "id": <str>, "resource_url": <str>, "messages_url": <str>, "uri": <str>, "status": <str>, "next_status": [<str>], "fee": { "currency": <str>, "value": <float> }, "created": <str>, "items": [ { "release": { "id": <int>, "description": <str>, }, "price": { "currency": <str>, "value": <int> }, "media_condition": <str>, "sleeve_condition": <str>, "id": <int> } ], "shipping": { "currency": <str>, "method": <str>, "value": <int> }, "shipping_address": <str>, "additional_instructions": <str>, "archived": <bool>, "seller": { "resource_url": <str>, "username": <str>, "id": <int> }, "last_activity": <str>, "buyer": { "resource_url": <str>, "username": <str>, "id": <int> }, "total": { "currency": <str>, "value": <int> } } """ self._check_authentication("edit_order") return self._request( "post", f"{self.API_URL}/marketplace/orders/{order_id}", json={"status": status, "shipping": shipping}, ).json()
[docs] def get_user_orders( self, *, status: str = None, created_after: str = None, created_before: str = None, archived: bool = None, page: Union[int, str] = None, per_page: Union[int, str] = None, sort: str = None, sort_order: str = None, ) -> dict[str, Any]: """ `Marketplace > List Orders <https://www.discogs.com/developers /#page:marketplace,header:marketplace-list-orders-get>`_: Returns a list of the authenticated user's orders. .. admonition:: User authentication :class: warning Requires user authentication with a personal access token or via the OAuth 1.0a flow. Parameters ---------- status : `str`, keyword-only, optional Only show orders with this status. **Valid values**: :code:`"All"`, :code:`"New Order"`, :code:`"Buyer Contacted"`, :code:`"Invoice Sent"`, :code:`"Payment Pending"`, :code:`"Payment Received"`, :code:`"In Progress"`, :code:`"Shipped"`, :code:`"Merged"`, :code:`"Order Changed"`, :code:`"Refund Sent"`, :code:`"Cancelled"`, :code:`"Cancelled (Non-Paying Buyer)"`, :code:`"Cancelled (Item Unavailable)"`, :code:`"Cancelled (Per Buyer's Request)"`, and :code:`"Cancelled (Refund Received)"`. created_after : `str`, keyword-only, optional Only show orders created after this ISO 8601 timestamp. **Example**: :code:`"2019-06-24T20:58:58Z"`. created_before : `str`, keyword-only, optional Only show orders created before this ISO 8601 timestamp. **Example**: :code:`"2019-06-24T20:58:58Z"`. archived : `bool`, keyword-only, optional Only show orders with a specific archived status. If no key is provided, both statuses are returned. page : `int` or `str`, keyword-only, optional The page you want to request. **Example**: :code:`3`. per_page : `int`, keyword-only, optional The number of items per page. **Example**: :code:`25`. sort : `str`, keyword-only, optional Sort items by this field. **Valid values**: :code:`"id"`, :code:`"buyer"`, :code:`"created"`, :code:`"status"`, and :code:`"last_activity"`. sort_order : `str`, keyword-only, optional Sort items in a particular order. **Valid values**: :code:`"asc"` and :code:`"desc"`. Returns ------- orders : `dict` The authenticated user's orders. .. admonition:: Sample response :class: dropdown .. code:: { "pagination": { "page": <int>, "pages": <int>, "per_page": <int>, "items": <int>, "urls": {} }, "orders": [ { "id": <str>, "resource_url": <str>, "messages_url": <str>, "uri": <str>, "status": <str>, "next_status": [<str>], "fee": { "currency": <str>, "value": <float> }, "created": <str>, "items": [ { "release": { "id": <int>, "description": <str>, }, "price": { "currency": <str>, "value": <int> }, "media_condition": <str>, "sleeve_condition": <str>, "id": <int> } ], "shipping": { "currency": <str>, "method": <str>, "value": <int> }, "shipping_address": <str>, "additional_instructions": <str>, "archived": <bool>, "seller": { "resource_url": <str>, "username": <str>, "id": <int> }, "last_activity": <str>, "buyer": { "resource_url": <str>, "username": <str>, "id": <int> }, "total": { "currency": <str>, "value": <int> } } ] } """ self._check_authentication("get_user_orders") return self._get_json( f"{self.API_URL}/marketplace/orders", params={ "status": status, "created_after": created_after, "created_before": created_before, "archived": archived, "page": page, "per_page": per_page, "sort": sort, "sort_order": sort_order, }, )
[docs] def get_order_messages( self, order_id: str, *, page: Union[int, str] = None, per_page: Union[int, str] = None, ) -> dict[str, Any]: """ `Marketplace > List Order Messages > List Order Messages <https://www.discogs.com/developers/ #page:marketplace,header:marketplace-list-order-messages-get>`_: Returns a list of the order's messages with the most recent first. .. admonition:: User authentication :class: warning Requires user authentication with a personal access token or via the OAuth 1.0a flow. Parameters ---------- order_id : `str` The ID of the order you are fetching. **Example**: :code:`1-1`. page : `int` or `str`, keyword-only, optional The page you want to request. **Example**: :code:`3`. per_page : `int` or `str`, keyword-only, optional The number of items per page. **Example**: :code:`25`. Returns ------- messages : `dict` The order's messages. .. admonition:: Sample response :class: dropdown .. code:: { "pagination": { "per_page": <int>, "items": <int>, "page": <int>, "urls": { "last": <str>, "next": <str> }, "pages": <int> }, "messages": [ { "refund": { "amount": <int>, "order": { "resource_url": <str>, "id": <str> } }, "timestamp": <str>, "message": <str>, "type": <str>, "order": { "resource_url": <str>, "id": <str>, }, "subject": <str> } ] } """ self._check_authentication("get_order_messages") return self._get_json( f"{self.API_URL}/marketplace/orders/{order_id}/messages", params={"page": page, "per_page": per_page}, )
[docs] def add_order_message( self, order_id: str, message: str = None, status: str = None ) -> dict[str, Any]: """ `Marketplace > List Order Messages > Add New Message <https://www.discogs.com/developers/ #page:marketplace,header:marketplace-list-order-messages-post>`_: Adds a new message to the order's message log. When posting a new message, you can simultaneously change the order status. IF you do, the message will automatically be prepended with: Seller changed status from [...] to [...] While `message` and `status` are each optional, one or both must be present. .. admonition:: User authentication :class: warning Requires user authentication with a personal access token or via the OAuth 1.0a flow. Parameters ---------- order_id : `str` The ID of the order you are fetching. **Example**: :code:`1-1`. message : `str`, optional The message you are posting. **Example**: :code:`"hello world"` status : `str`, optional The status of the order you are updating. **Valid values**: :code:`"New Order"`, :code:`"Buyer Contacted"`, :code:`"Invoice Sent"`, :code:`"Payment Pending"`, :code:`"Payment Received"`, :code:`"In Progress"`, :code:`"Shipped"`, :code:`"Refund Sent"`, :code:`"Cancelled (Non-Paying Buyer)"`, :code:`"Cancelled (Item Unavailable)"`, and :code:`"Cancelled (Per Buyer's Request)"`. Returns ------- message : `dict` The order's message. .. admonition:: Sample response :class: dropdown .. code:: { "from": { "username": <str>, "resource_url": <str> }, "message": <str>, "order": { "resource_url": <str>, "id": <str> }, "timestamp": <str>, "subject": <str> } """ self._check_authentication("add_order_message") if message is None and status is None: emsg = "Either 'message' or 'status' must be provided." raise ValueError(emsg) return self._request( "post", f"{self.API_URL}/marketplace/orders/{order_id}/messages", json={"message": message, "status": status}, ).json()
[docs] def get_fee( self, price: float, *, currency: str = "USD" ) -> dict[str, Any]: """ `Marketplace > Fee with currency <https://www.discogs.com/developers/#page:marketplace,header :marketplace-fee-with-currency-get>`_: Calculates the fee for selling an item on the marketplace given a particular currency. Parameters ---------- price : `float` The price of the item (in the seller's currency). **Example**: :code:`10.00`. currency : `str`, keyword-only, default: :code:`"USD"` The currency abbreviation for the fee calculation. **Valid values**: :code:`"USD"`, :code:`"GBP"`, :code:`"EUR"`, :code:`"CAD"`, :code:`"AUD"`, :code:`"JPY"`, :code:`"CHF"`, :code:`"MXN"`, :code:`"BRL"`, :code:`"NZD"`, :code:`"SEK"`, and :code:`"ZAR"`. Returns ------- fee : `dict` The fee for selling an item on the marketplace. .. admonition:: Sample response :class: dropdown .. code:: { "value": <float>, "currency": <str>, } """ return self._get_json( f"{self.API_URL}/marketplace/fee/{price}/{currency}" )
[docs] def get_price_suggestions( self, release_id: Union[int, str] ) -> dict[str, Any]: """ `Marketplace > Price Suggestions <https://www.discogs.com /developers/#page:marketplace,header :marketplace-price-suggestions>`_: Retrieve price suggestions in the user's selling currency for the provided release ID. If no suggestions are available, an empty object will be returned. .. admonition:: User authentication :class: warning Requires user authentication with a personal access token or via the OAuth 1.0a flow. Parameters ---------- release_id : `int` or `str` The ID of the release you are fetching. **Example**: :code:`249504`. Returns ------- prices : `dict` The price suggestions for the release. .. admonition:: Sample response :class: dropdown .. code:: { "Very Good (VG)": { "currency": <str>, "value": <float> }, "Good Plus (G+)": { "currency": <str>, "value": <float> }, "Near Mint (NM or M-)": { "currency": <str>, "value": <float> }, "Good (G)": { "currency": <str>, "value": <float> }, "Very Good Plus (VG+)": { "currency": <str>, "value": <float> }, "Mint (M)": { "currency": <str>, "value": <float> }, "Fair (F)": { "currency": <str>, "value": <float> }, "Poor (P)": { "currency": <str>, "value": <float> } } """ self._check_authentication("get_price_suggestions") return self._get_json( f"{self.API_URL}/marketplace/price_suggestions/{release_id}" )
[docs] def get_release_marketplace_stats( self, release_id: Union[int, str], *, curr_abbr: str = None ) -> dict[str, Any]: """ `Marketplace > Release Statistics <https://www.discogs.com /developers/#page:marketplace,header :marketplace-release-statistics-get>`_: Retrieve marketplace statistics for the provided release ID. These statistics reflect the state of the release in the marketplace currently, and include the number of items currently for sale, lowest listed price of any item for sale, and whether the item is blocked for sale in the marketplace. Releases that have no items or are blocked for sale in the marketplace will return a body with null data in the :code:`"lowest_price"` and :code:`"num_for_sale"` keys. .. admonition:: User authentication :class: dropdown warning Authentication is optional. Authenticated users will by default have the lowest currency expressed in their own buyer currency, configurable in buyer settings, in the absence of the `curr_abbr` query parameter to specify the currency. Unauthenticated users will have the price expressed in US Dollars, if no `curr_abbr` is provided. Parameters ---------- release_id : `int` or `str` The ID of the release you are fetching. **Example**: :code:`249504`. curr_abbr : `str`, keyword-only, optional Currency abbreviation for marketplace data. **Valid values**: :code:`"USD"`, :code:`"GBP"`, :code:`"EUR"`, :code:`"CAD"`, :code:`"AUD"`, :code:`"JPY"`, :code:`"CHF"`, :code:`"MXN"`, :code:`"BRL"`, :code:`"NZD"`, :code:`"SEK"`, and :code:`"ZAR"`. Returns ------- stats : `dict` The marketplace statistics for the release. .. admonition:: Sample response :class: dropdown .. code:: { "lowest_price": { "currency": <str>, "value": <float> }, "num_for_sale": <int>, "blocked_from_sale": <bool> } """ return self._get_json( f"{self.API_URL}/marketplace/stats/{release_id}", params={"curr_abbr": curr_abbr}, )
### INVENTORY EXPORT ######################################################
[docs] def export_inventory( self, *, download: bool = True, filename: str = None, path: str = None ) -> str: """ `Inventory Export > Export Your Inventory <https://www.discogs.com /developers/#page:inventory-export,header :inventory-export-export-your-inventory-post>`_: Request an export of your inventory as a CSV. .. admonition:: User authentication :class: warning Requires user authentication with a personal access token or via the OAuth 1.0a flow. Parameters ---------- download : `bool`, keyword-only, default: :code:`True` Specifies whether to download the CSV file. If :code:`False`, the export ID is returned. filename : `str`, optional Filename of the exported CSV file. A :code:`.csv` extension will be appended if not present. If not specified, the CSV file is saved as :code:`<username>-inventory-<date>-<number>.csv`. path : `str`, optional Path to save the exported CSV file. If not specified, the file is saved in the current working directory. Returns ------- path_or_id : `str` Full path to the exported CSV file (:code:`download=True`) or the export ID (:code:`download=False`). """ self._check_authentication("export_inventory") r = self._request("post", f"{self.API_URL}/inventory/export") if download: return self.download_inventory_export( r.headers["Location"].split("/")[-1], filename=filename, path=path, ) return r.headers["Location"]
[docs] def get_inventory_exports( self, *, page: int = None, per_page: int = None ) -> dict[str, Any]: """ `Inventory Export > Get Recent Exports <https://www.discogs.com /developers/#page:inventory-export,header :inventory-export-get-recent-exports-get>`_: Get all recent exports of your inventory. .. admonition:: User authentication :class: warning Requires user authentication with a personal access token or via the OAuth 1.0a flow. Parameters ---------- page : `int`, keyword-only, optional The page you want to request. **Example**: :code:`3`. per_page : `int`, keyword-only, optional The number of items per page. **Example**: :code:`25`. Returns ------- exports : `dict` The authenticated user's inventory exports. .. admonition:: Sample response :class: dropdown .. code:: { "items": [ { "status": <str>, "created_ts": <str>, "url": <str>, "finished_ts": <str>, "download_url": <str>, "filename": <str>, "id": <int> } ], "pagination": { "per_page": <int>, "items": <int>, "page": <int>, "urls": { "last": <str>, "next": <str> }, "pages": <int> } } """ self._check_authentication("get_inventory_exports") return self._get_json( f"{self.API_URL}/inventory/export", json={"page": page, "per_page": per_page}, )
[docs] def get_inventory_export( self, export_id: int ) -> dict[str, Union[int, str]]: """ `Inventory Export > Get An Export <https://www.discogs.com /developers/#page:inventory-export,header :inventory-export-get-an-export-get>`_: Get details about the status of an inventory export. .. admonition:: User authentication :class: warning Requires user authentication with a personal access token or via the OAuth 1.0a flow. Parameters ---------- export_id : `int` ID of the export. Returns ------- export : `dict` Details about the status of the inventory export. .. admonition:: Sample response :class: dropdown .. code:: { "status": <str>, "created_ts": <str>, "url": <str>, "finished_ts": <str>, "download_url": <str>, "filename": <str>, "id": <int> } """ self._check_authentication("get_inventory_export") return self._get_json(f"{self.API_URL}/inventory/export/{export_id}")
[docs] def download_inventory_export( self, export_id: int, *, filename: str = None, path: str = None ) -> str: """ `Inventory Export > Download An Export <https://www.discogs.com /developers/#page:inventory-export,header :inventory-export-download-an-export-get>`_: Download the results of an inventory export. .. admonition:: User authentication :class: warning Requires user authentication with a personal access token or via the OAuth 1.0a flow. Parameters ---------- export_id : `int` ID of the export. filename : `str`, optional Filename of the exported CSV file. A :code:`.csv` extension will be appended if not present. If not specified, the CSV file is saved as :code:`<username>-inventory-<date>-<number>.csv`. path : `str`, optional Path to save the exported CSV file. If not specified, the file is saved in the current working directory. Returns ------- path : `str` Full path to the exported CSV file. """ self._check_authentication("download_inventory_export") while True: r = self.get_inventory_export(export_id) if r["status"] == "success": break time.sleep(1) r = self._request( "get", f"{self.API_URL}/inventory/export/{export_id}/download" ) if filename is None: filename = r.headers["Content-Disposition"].split("=")[1] else: if not filename.endswith(".csv"): filename += ".csv" with open( path := os.path.join(path or os.getcwd(), filename), "w" ) as f: f.write(r.text) return path
### INVENTORY UPLOAD ######################################################
[docs] def upload_inventory_additions( self, inventory_csv: Union[bytes, str, Path] ) -> str: """ `Inventory Upload > Add Inventory <https://www.discogs.com /developers/#page:inventory-upload, header:inventory-upload-add-inventory>`_: Upload a CSV of listings to add to your inventory. .. admonition:: User authentication :class: warning Requires user authentication with a personal access token or via the OAuth 1.0a flow. .. note:: Listings are marked as "For Sale". Currency information is pulled from the current user's marketplace settings. Parameters ---------- inventory_csv : `bytes`, `str`, or `pathlib.Path`, \ positional-only Path to, name of, or contents of a CSV file containing the listings to add. **Required fields**: :code:`release_id`, :code:`price`, :code:`media_condition`. **Optional fields**: :code:`sleeve_condition`, :code:`comments`, :code:`accept_offer`, :code:`location`, :code:`external_id`, :code:`weight`, :code:`format_quantity`. Returns ------- upload_url : str Request URL to get information for the inventory upload. """ self._check_authentication("upload_inventory_additions") if isinstance(inventory_csv, (str, Path)): inventory_csv = open(inventory_csv, "rb") return self._request( "post", f"{self.API_URL}/inventory/upload/add", files={"upload": ("inventory.csv", inventory_csv, "text/csv")}, ).headers["Location"]
[docs] def upload_inventory_updates( self, inventory_csv: Union[bytes, str, Path] ) -> str: """ `Inventory Upload > Change Inventory <https://www.discogs.com /developers/#page:inventory-upload, header:inventory-upload-change-inventory>`_: Upload a CSV of listings to change in your inventory. .. admonition:: User authentication :class: warning Requires user authentication with a personal access token or via the OAuth 1.0a flow. .. note:: Listings are marked as "For Sale". Currency information is pulled from the current user's marketplace settings. Parameters ---------- inventory_csv : `bytes`, `str`, or `pathlib.Path`, \ positional-only Path to, name of, or contents of a CSV file containing the listings to change. **Required field**: :code:`release_id`. **Optional fields**: :code:`price`, :code:`media_condition`, :code:`sleeve_condition`, :code:`comments`, :code:`accept_offer`, :code:`location`, :code:`external_id`, :code:`weight`, :code:`format_quantity`. Returns ------- upload_url : str Request URL to get information for the inventory upload. """ self._check_authentication("upload_inventory_updates") if isinstance(inventory_csv, (str, Path)): inventory_csv = open(inventory_csv, "rb") return self._request( "post", f"{self.API_URL}/inventory/upload/update", files={"upload": ("inventory.csv", inventory_csv, "text/csv")}, ).headers["Location"]
[docs] def upload_inventory_deletions( self, inventory_csv: Union[bytes, str, Path] ) -> str: """ `Inventory Upload > Delete Inventory <https://www.discogs.com /developers/#page:inventory-upload, header:inventory-upload-delete-inventory>`_: Upload a CSV of listings to delete from your inventory. .. admonition:: User authentication :class: warning Requires user authentication with a personal access token or via the OAuth 1.0a flow. .. note:: Listings are marked as "For Sale". Currency information is pulled from the current user's marketplace settings. Parameters ---------- inventory_csv : `bytes`, `str`, or `pathlib.Path`, \ positional-only Path to, name of, or contents of a CSV file containing the listings to delete. **Required field**: :code:`listing_id`. Returns ------- upload_url : str Request URL to get information for the inventory upload. """ self._check_authentication("upload_inventory_deletions") if isinstance(inventory_csv, (str, Path)): inventory_csv = open(inventory_csv, "rb") return self._request( "post", f"{self.API_URL}/inventory/upload/delete", files={"upload": ("inventory.csv", inventory_csv, "text/csv")}, ).headers["Location"]
[docs] def get_inventory_uploads( self, *, page: int = None, per_page: int = None ) -> dict[str, Any]: """ `Inventory Upload > Get Recent Uploads <https://www.discogs.com /developers/#page:inventory-upload, header:inventory-upload-get-recent-uploads>`_: Get all recent inventory uploads. .. admonition:: User authentication :class: warning Requires user authentication with a personal access token or via the OAuth 1.0a flow. Parameters ---------- page : `int`, keyword-only, optional The page you want to request. **Example**: :code:`3`. per_page : `int`, keyword-only, optional The number of items per page. **Example**: :code:`25`. Returns ------- uploads : `dict` The authenticated user's inventory uploads. .. admonition:: Sample response :class: dropdown .. code:: { "items": [ { "created_ts": <str>, "filename": <str>, "finished_ts": <str>, "id": <int>, "results": <str>, "status": <str>, "type": <str> } ], "pagination": { "items": <int>, "page": <int>, "pages": <int>, "per_page": <int>, "urls": { "first": <str>, "last": <str>, "next": <str>, "prev": <str> } } } """ self._check_authentication("get_inventory_uploads") return self._get_json( f"{self.API_URL}/inventory/upload", params={"page": page, "per_page": per_page}, )
[docs] def get_inventory_upload( self, upload_id: Union[int, str] ) -> dict[str, Any]: """ `Inventory Upload > Get an Upload <https://www.discogs.com /developers/#page:inventory-upload, header:inventory-upload-get-an-upload>`_: Get details about the status of an inventory upload. .. admonition:: User authentication :class: warning Requires user authentication with a personal access token or via the OAuth 1.0a flow. Parameters ---------- upload_id : `int` or `str` ID of the upload. Returns ------- upload : `dict` Details about the status of the inventory upload. .. admonition:: Sample response :class: dropdown .. code:: { "created_ts": <str>, "filename": <str>, "finished_ts": <str>, "id": <int>, "results": <str>, "status": <str>, "type": <str> } """ self._check_authentication("get_inventory_upload") return self._get_json(f"{self.API_URL}/inventory/upload/{upload_id}")
### USER IDENTITY #########################################################
[docs] def get_identity(self) -> dict[str, Any]: """ `User Identity > Identity <https://www.discogs.com/developers /#page:user-identity,header:user-identity-identity-get>`_: Retrieve basic information about the authenticated user. .. admonition:: User authentication :class: warning Requires user authentication with a personal access token or via the OAuth 1.0a flow. You can use this resource to find out who you're authenticated as, and it also doubles as a good sanity check to ensure that you're using OAuth correctly. For more detailed information, make another request for the user's profile using :meth:`get_profile`. Returns ------- identity : `dict` Basic information about the authenticated user. .. admonition:: Sample response :class: dropdown .. code:: { "id": <int>, "username": <str>, "resource_url": <str>, "consumer_name": <str> } """ self._check_authentication("get_identity") return self._get_json(f"{self.API_URL}/oauth/identity")
[docs] def get_profile(self, username: str = None) -> dict[str, Any]: """ `User Identity > Profile > Get Profile <https://www.discogs.com/developers /#page:user-identity,header:user-identity-profile-get>`_: Retrieve a user by username. .. admonition:: User authentication :class: dropdown warning If authenticated as the requested user, the :code:`"email"` key will be visible, and the :code:`"num_lists"` count will include the user's private lists. If authenticated as the requested user or the user's collection/wantlist is public, the :code:`"num_collection"`/:code:`"num_wantlist"` keys will be visible. Parameters ---------- username : `str`, optional The username of whose profile you are requesting. If not specified, the username of the authenticated user is used. **Example**: :code:`"rodneyfool"`. Returns ------- profile : `dict` Detailed information about the user. .. admonition:: Sample response :class: dropdown .. code:: { "profile": <str>, "wantlist_url": <str>, "rank": <int>, "num_pending": <int>, "id": <int>, "num_for_sale": <int>, "home_page": <str>, "location": <str>, "collection_folders_url": <str>, "username": <str>, "collection_fields_url": <str>, "releases_contributed": <int>, "registered": <str>, "rating_avg": <float>, "num_collection": <int>, "releases_rated": <int>, "num_lists": <int>, "name": <str>, "num_wantlist": <int>, "inventory_url": <str>, "avatar_url": <str>, "banner_url": <str>, "uri": <str>, "resource_url": <str>, "buyer_rating": <float>, "buyer_rating_stars": <int>, "buyer_num_ratings": <int>, "seller_rating": <float>, "seller_rating_stars": <int>, "seller_num_ratings": <int>, "curr_abbr": <str>, } """ if username is None: if hasattr(self, "_username"): username = self._username else: raise ValueError("No username provided.") return self._get_json(f"{self.API_URL}/users/{username}")
[docs] def edit_profile( self, *, name: str = None, home_page: str = None, location: str = None, profile: str = None, curr_abbr: str = None, ) -> dict[str, Any]: """ `User Identity > Profile > Edit Profile <https://www.discogs.com/developers /#page:user-identity,header:user-identity-profile-post>`_: Edit a user's profile data. .. admonition:: User authentication :class: warning Requires user authentication with a personal access token or via the OAuth 1.0a flow. Parameters ---------- name : `str`, keyword-only, optional The real name of the user. **Example**: :code:`"Nicolas Cage"`. home_page : `str`, keyword-only, optional The user's website. **Example**: :code:`"www.discogs.com"`. location : `str`, keyword-only, optional The geographical location of the user. **Example**: :code:`"Portland"`. profile : `str`, keyword-only, optional Biological information about the user. **Example**: :code:`"I am a Discogs user!"`. curr_abbr : `str`, keyword-only, optional Currency abbreviation for marketplace data. **Valid values**: :code:`"USD"`, :code:`"GBP"`, :code:`"EUR"`, :code:`"CAD"`, :code:`"AUD"`, :code:`"JPY"`, :code:`"CHF"`, :code:`"MXN"`, :code:`"BRL"`, :code:`"NZD"`, :code:`"SEK"`, and :code:`"ZAR"`. Returns ------- profile : `dict` Updated profile. .. admonition:: Sample response :class: dropdown .. code:: { "id": <int><int>, "username": <str>, "name": <str>, "email": <str>, "resource_url": <str>, "inventory_url": <str>, "collection_folders_url": <str>, "collection_fields_url": <str>, "wantlist_url": <str>, "uri": <str>, "profile": <str>, "home_page": <str>, "location": <str>, "registered": <str>, "num_lists": <int>, "num_for_sale": <int>, "num_collection": <int>, "num_wantlist": <int>, "num_pending": <int>, "releases_contributed": <int>, "rank": <int>, "releases_rated": <int>, "rating_avg": <float> } """ self._check_authentication("edit_profile") if ( name is None and home_page is None and location is None and profile is None and curr_abbr is None ): wmsg = "No changes were specified or made to the user profile." warnings.warn(wmsg) return if curr_abbr and curr_abbr not in ( CURRENCIES := { "USD", "GBP", "EUR", "CAD", "AUD", "JPY", "CHF", "MXN", "BRL", "NZD", "SEK", "ZAR", } ): emsg = ( f"Invalid currency abbreviation ({curr_abbr=}). " f"Valid values: {', '.join(CURRENCIES)}." ) raise ValueError(emsg) return self._request( "post", f"{self.API_URL}/users/{self._username}", json={ "name": name, "home_page": home_page, "location": location, "profile": profile, "curr_abbr": curr_abbr, }, ).json()
[docs] def get_user_submissions( self, username: str = None, *, page: Union[int, str] = None, per_page: Union[int, str] = None, ) -> dict[str, Any]: """ `User Identity > User Submissions <https://www.discogs.com /developers/#page:user-identity,header :user-identity-user-submissions-get>`_: Retrieve a user's submissions (edits made to releases, labels, and artists) by username. Parameters ---------- username : `str`, optional The username of the submissions you are trying to fetch. If not specified, the username of the authenticated user is used. **Example**: :code:`"shooezgirl"`. page : `int` or `str`, keyword-only, optional The page you want to request. **Example**: :code:`3`. per_page : `int` or `str`, keyword-only, optional The number of items per page. **Example**: :code:`25`. Returns ------- submissions : `dict` Submissions made by the user. .. admonition:: Sample response :class: dropdown .. code:: { "pagination": { "items": <int>, "page": <int>, "pages": <int>, "per_page": <int>, "urls": { "last": <str>, "next": <str> } }, "submissions": { "artists": [ { "data_quality": <str>, "id": <int>, "name": <str>, "namevariations": [<str>], "releases_url": <str>, "resource_url": <str>, "uri": <str> } ], "labels": [], "releases": [ { "artists": [ { "anv": <str>, "id": <int>, "join": <str>, "name": <str>, "resource_url": <str>, "role": <str>, "tracks": <str> } ], "community": { "contributors": [ { "resource_url": <str>, "username": <str> } ], "data_quality": <str>, "have": <int>, "rating": { "average": <int>, "count": <int> }, "status": <str>, "submitter": { "resource_url": <str>, "username": <str> }, "want": <int> }, "companies": [], "country": <str>, "data_quality": <str>, "date_added": <str>, "date_changed": <str>, "estimated_weight": <int>, "format_quantity": <int>, "formats": [ { "descriptions": [<str>], "name": <str>, "qty": <str> } ], "genres": [<str>], "id": <int>, "images": [ { "height": <int>, "resource_url": <str>, "type": <str>, "uri": <str>, "uri150": <str>, "width": <int> } ], "labels": [ { "catno": <str>, "entity_type": <str>, "id": <int>, "name": <str>, "resource_url": <str> } ], "master_id": <int>, "master_url": <str>, "notes": <str>, "released": <str>, "released_formatted": <str>, "resource_url": <str>, "series": [], "status": <str>, "styles": [<str>], "thumb": <str>, "title": <str>, "uri": <str>, "videos": [ { "description": <str>, "duration": <int>, "embed": <bool>, "title": <str>, "uri": <str> } ], "year": <int> } ] } } """ if username is None: if hasattr(self, "_username"): username = self._username else: raise ValueError("No username provided.") return self._get_json( f"{self.API_URL}/users/{username}/submissions", params={"page": page, "per_page": per_page}, )
[docs] def get_user_contributions( self, username: str = None, *, page: Union[int, str] = None, per_page: Union[int, str] = None, sort: str = None, sort_order: str = None, ) -> dict[str, Any]: """ `User Identity > User Contributions <https://www.discogs.com /developers/#page:user-identity,header :user-identity-user-contributions-get>`_: Retrieve a user's contributions (releases, labels, artists) by username. Parameters ---------- username : `str`, optional The username of the contributions you are trying to fetch. If not specified, the username of the authenticated user is used. **Example**: :code:`"shooezgirl"`. page : `int` or `str`, keyword-only, optional The page you want to request. **Example**: :code:`3`. per_page : `int` or `str`, keyword-only, optional The number of items per page. **Example**: :code:`25`. sort : `str`, keyword-only, optional Sort items by this field. **Valid values**: :code:`"label"`, :code:`"artist"`, :code:`"title"`, :code:`"catno"`, :code:`"format"`, :code:`"rating"`, :code:`"year"`, and :code:`"added"`. sort_order : `str`, keyword-only, optional Sort items in a particular order. **Valid values**: :code:`"asc"` and :code:`"desc"`. Returns ------- contributions : `dict` Contributions made by the user. .. admonition:: Sample response :class: dropdown .. code:: { "pagination": { "items": <int>, "page": <int>, "pages": <int>, "per_page": <int>, "urls": { "last": <str>, "next": <str> } }, "contributions": [ { "artists": [ { "anv": <str>, "id": <int>, "join": <str>, "name": <str>, "resource_url": <str>, "role": <str>, "tracks": <str> } ], "community": { "contributors": [ { "resource_url": <str>, "username": <str> } ], "data_quality": <str>, "have": <int>, "rating": { "average": <int>, "count": <int> }, "status": <str>, "submitter": { "resource_url": <str>, "username": <str> }, "want": <int> }, "companies": [], "country": <str>, "data_quality": <str>, "date_added": <str>, "date_changed": <str>, "estimated_weight": <int>, "format_quantity": <int>, "formats": [ { "descriptions": [<str>], "name": <str>, "qty": <str> } ], "genres": [<str>], "id": <int>, "images": [ { "height": <int>, "resource_url": <str>, "type": <str>, "uri": <str>, "uri150": <str>, "width": <int> } ], "labels": [ { "catno": <str>, "entity_type": <str>, "id": <int>, "name": <str>, "resource_url": <str> } ], "master_id": <int>, "master_url": <str>, "notes": <str>, "released": <str>, "released_formatted": <str>, "resource_url": <str>, "series": [], "status": <str>, "styles": [<str>], "thumb": <str>, "title": <str>, "uri": <str>, "videos": [ { "description": <str>, "duration": <int>, "embed": <bool>, "title": <str>, "uri": <str> } ], "year": <int> } ] } """ if username is None: if hasattr(self, "_username"): username = self._username else: raise ValueError("No username provided.") return self._get_json( f"{self.API_URL}/users/{username}/contributions", params={ "page": page, "per_page": per_page, "sort": sort, "sort_order": sort_order, }, )
### USER COLLECTION #######################################################
[docs] def get_collection_folders( self, username: str = None ) -> list[dict[str, Any]]: """ `User Collection > Collection > Get Collection Folders <https://www.discogs.com/developers/#page:user-collection,header :user-collection-collection-get>`_: Retrieve a list of folders in a user's collection. .. admonition:: User authentication :class: dropdown warning If the collection has been made private by its owner, authentication as the collection owner is required. If you are not authenticated as the collection owner, only folder ID :code:`0` (the "All" folder) will be visible (if the requested user's collection is public). Parameters ---------- username : `str`, optional The username of the collection you are trying to fetch. If not specified, the username of the authenticated user is used. **Example**: :code:`"rodneyfool"`. Returns ------- folders : `list` A list of folders in the user's collection. .. admonition:: Sample response :class: dropdown .. code:: [ { "id": <int>, "name": <str>, "count": <int>, "resource_url": <str> } ] """ if username is None: if hasattr(self, "_username"): username = self._username else: raise ValueError("No username provided.") return self._get_json( f"{self.API_URL}/users/{username}/collection/folders" )["folders"]
[docs] def create_collection_folder( self, name: str ) -> dict[str, Union[int, str]]: """ `User Collection > Collection > Create Folder <https://www.discogs.com/developers/#page:user-collection,header :user-collection-collection-post>`_: Create a new folder in a user's collection. .. admonition:: User authentication :class: warning Requires user authentication with a personal access token or via the OAuth 1.0a flow. Parameters ---------- name : `str` The name of the newly-created folder. **Example**: :code:`"My favorites"`. Returns ------- folder : `dict` Information about the newly-created folder. .. admonition:: Sample response :class: dropdown .. code:: { "id": <int>, "name": <str>, "count": <int>, "resource_url": <str> } """ self._check_authentication("create_collection_folder") return self._request( "post", f"{self.API_URL}/users/{self._username}/collection/folders", json={"name": name}, ).json()
[docs] def get_collection_folder( self, folder_id: int, *, username: str = None ) -> dict[str, Union[int, str]]: """ `User Collection > Collection Folder > Get Folders <https://www.discogs.com/developers/#page:user-collection,header :user-collection-collection-folder-get>`_: Retrieve metadata about a folder in a user's collection. .. admonition:: User authentication :class: dropdown warning If `folder_id` is not :code:`0`, authentication as the collection owner is required. Parameters ---------- folder_id : `int` The ID of the folder to request. **Example**: :code:`3`. username : `str`, keyword-only, optional The username of the collection you are trying to request. If not specified, the username of the authenticated user is used. **Example**: :code:`"rodneyfool"`. Returns ------- folder : `dict` Metadata about the folder. .. admonition:: Sample response :class: dropdown .. code:: { "id": <int>, "name": <str>, "count": <int>, "resource_url": <str> } """ if username is None: if hasattr(self, "_username"): username = self._username else: raise ValueError("No username provided.") if folder_id != 0: self._check_authentication("get_collection_folder") return self._get_json( f"{self.API_URL}/users/{self._username}" f"/collection/folders/{folder_id}" )
[docs] def rename_collection_folder( self, folder_id: int, name: str, *, username: str = None ) -> dict[str, Union[int, str]]: """ `User Collection > Collection Folder > Edit Folder <https://www.discogs.com/developers/#page:user-collection,header :user-collection-collection-folder-post>`_: Rename a folder. Folders :code:`0` and :code:`1` cannot be renamed. .. admonition:: User authentication :class: warning Requires user authentication with a personal access token or via the OAuth 1.0a flow. Parameters ---------- folder_id : `int` The ID of the folder to modify. **Example**: :code:`3`. name : `str` The new name of the folder. **Example**: :code:`"My favorites"`. username : `str`, keyword-only, optional The username of the collection you are trying to modify. If not specified, the username of the authenticated user is used. **Example**: :code:`"rodneyfool"`. Returns ------- folder : `dict` Information about the edited folder. .. admonition:: Sample response :class: dropdown .. code:: { "id": <int>, "name": <str>, "count": <int>, "resource_url": <str> } """ self._check_authentication("rename_collection_folder") return self._request( "post", f"{self.API_URL}/users/{self._username}" f"/collection/folders/{folder_id}", json={"name": name}, ).json()
[docs] def delete_collection_folder( self, folder_id: int, *, username: str = None ) -> None: """ `User Collection > Collection Folder > Delete Folder <https://www.discogs.com/developers/#page:user-collection,header :user-collection-collection-folder-delete>`_: Delete a folder from a user's collection. A folder must be empty before it can be deleted. .. admonition:: User authentication :class: warning Requires user authentication with a personal access token or via the OAuth 1.0a flow. Parameters ---------- folder_id : `int` The ID of the folder to delete. **Example**: :code:`3`. username : `str`, keyword-only, optional The username of the collection you are trying to delete. If not specified, the username of the authenticated user is used. **Example**: :code:`"rodneyfool"`. """ self._check_authentication("delete_collection_folder") self._request( "delete", f"{self.API_URL}/users/{self._username}" f"/collection/folders/{folder_id}", )
[docs] def get_collection_folders_by_release( self, release_id: Union[int, str], *, username: str = None ) -> dict[str, Any]: """ `User Collection > Collection Items By Release <https://www.discogs.com/developers/#page:user-collection,header :user-collection-collection-items-by-release-get>`_: View the user's collection folders which contain a specified release. This will also show information about each release instance. Parameters ---------- release_id : `int` or `str` The ID of the release to request. **Example**: :code:`7781525`. username : `str`, keyword-only, optional The username of the collection you are trying to view. If not specified, the username of the authenticated user is used. **Example**: :code:`"susan.salkeld"`. Returns ------- releases : `list` A list of releases and their folders. .. admonition:: Sample response :class: dropdown .. code:: { "pagination": { "per_page": <int>, "items": <int>, "page": <int>, "urls": { "last": <str>, "next": <str> }, "pages": <int> }, "releases": [ { "instance_id": <int>, "rating": <int>, "basic_information": { "labels": [ { "name": <str>, "entity_type": <str>, "catno": <str>, "resource_url": <str>, "id": <int>, "entity_type_name": <str> } ], "formats": [ { "descriptions": [<str>], "name": <str>, "qty": <str> } ], "thumb": <str>, "title": <str>, "artists": [ { "join": <str>, "name": <str>, "anv": <str>, "tracks": <str>, "role": <str>, "resource_url": <str>, "id": <int> } ], "resource_url": <str>, "year": <int>, "id": <int>, }, "folder_id": <int>, "date_added": <str>, "id": <int> } ] } """ return self._get_json( f"{self.API_URL}/users/{self._username}/collection" f"/releases/{release_id}" )["folders"]
[docs] def get_collection_folder_releases( self, folder_id: int, *, username: str = None, page: int = None, per_page: int = None, sort: str = None, sort_order: str = None, ) -> dict[str, Any]: """ `User Collection > Collection Items By Folder <https://www.discogs.com/developers/#page:user-collection,header :user-collection-collection-items-by-folder>`_: Returns the items in a folder in a user's collection. Basic information about each release is provided, suitable for display in a list. For detailed information, make another call to fetch the corresponding release. .. admonition:: User authentication :class: dropdown warning If `folder_id` is not :code:`0` or the collection has been made private by its owner, authentication as the collection owner is required. If you are not authenticated as the collection owner, only the public notes fields will be visible. Parameters ---------- folder_id : `int` The ID of the folder to request. **Example**: :code:`3`. username : `str`, keyword-only, optional The username of the collection you are trying to request. If not specified, the username of the authenticated user is used. **Example**: :code:`"rodneyfool"`. page : `int` or `str`, keyword-only, optional The page you want to request. **Example**: :code:`3`. per_page : `int` or `str`, keyword-only, optional The number of items per page. **Example**: :code:`25`. sort : `str`, keyword-only, optional Sort items by this field. **Valid values**: :code:`"label"`, :code:`"artist"`, :code:`"title"`, :code:`"catno"`, :code:`"format"`, :code:`"rating"`, :code:`"year"`, and :code:`"added"`. sort_order : `str`, keyword-only, optional Sort items in a particular order. **Valid values**: :code:`"asc"` and :code:`"desc"`. Returns ------- items : `dict` Items in the folder. .. admonition:: Sample response :class: dropdown .. code:: { "pagination": { "per_page": <int>, "items": <int>, "page": <int>, "urls": { "last": <str>, "next": <str> }, "pages": <int> }, "releases": [ { "instance_id": <int>, "rating": <int>, "basic_information": { "labels": [ { "name": <str>, "entity_type": <str>, "catno": <str>, "resource_url": <str>, "id": <int>, "entity_type_name": <str> } ], "formats": [ { "descriptions": [<str>], "name": <str>, "qty": <str> } ], "thumb": <str>, "title": <str>, "artists": [ { "join": <str>, "name": <str>, "anv": <str>, "tracks": <str>, "role": <str>, "resource_url": <str>, "id": <int> } ], "resource_url": <str>, "year": <int>, "id": <int>, }, "folder_id": <int>, "date_added": <str>, "id": <int> } ] } """ if username is None: if hasattr(self, "_username"): username = self._username else: raise ValueError("No username provided.") if folder_id != 0: self._check_authentication("get_collection_folder_releases") return self._get_json( f"{self.API_URL}/users/{username}/collection" f"/folders/{folder_id}/releases", params={ "page": page, "per_page": per_page, "sort": sort, "sort_order": sort_order, }, )
[docs] def add_collection_folder_release( self, folder_id: int, release_id: int, *, username: str = None ) -> dict[str, Union[int, str]]: """ `User Collection > Add To Collection Folder <https://www.discogs.com/developers/#page:user-collection,header :user-collection-add-to-collection-folder-post>`_: Add a release to a folder in a user's collection. The `folder_id` must be non-zero. You can use :code:`1` for "Uncategorized". .. admonition:: User authentication :class: warning Requires user authentication with a personal access token or via the OAuth 1.0a flow. Parameters ---------- folder_id : `int` The ID of the folder to modify. **Example**: :code:`3`. release_id : `int` The ID of the release you are adding. **Example**: :code:`130076`. username : `str`, keyword-only, optional The username of the collection you are trying to modify. If not specified, the username of the authenticated user is used. **Example**: :code:`"rodneyfool"`. Returns ------- folder : `dict` Information about the folder. .. admonition:: Sample response :class: dropdown .. code:: { "instance_id": <int>, "resource_url": <str> } """ self._check_authentication("add_collection_folder_release") if username is None: if hasattr(self, "_username"): username = self._username else: raise ValueError("No username provided.") return self._request( "post", f"{self.API_URL}/users/{username}/collection" f"/folders/{folder_id}/releases/{release_id}", ).json()
[docs] def edit_collection_folder_release( self, folder_id: int, release_id: int, instance_id: int, *, username: str = None, new_folder_id: int, rating: int = None, ) -> None: """ `User Collection > Change Rating Of Release <https://www.discogs.com/developers#page:user-collection,header :user-collection-change-rating-of-release-post>`_: Change the rating on a release and/or move the instance to another folder. This endpoint potentially takes two folder ID parameters: `folder_id` (which is the folder you are requesting, and is required), and `new_folder_id` (representing the folder you want to move the instance to, which is optional). .. admonition:: User authentication :class: warning Requires user authentication with a personal access token or via the OAuth 1.0a flow. Parameters ---------- folder_id : `int` The ID of the folder to modify. **Example**: :code:`3`. release_id : `int` The ID of the release you are modifying. **Example**: :code:`130076`. instance_id : `int` The ID of the instance. **Example**: :code:`1`. username : `str`, keyword-only, optional The username of the collection you are trying to modify. If not specified, the username of the authenticated user is used. **Example**: :code:`"rodneyfool"`. new_folder_id : `int` The ID of the folder to move the instance to. **Example**: :code:`4`. rating : `int`, keyword-only, optional The rating of the instance you are supplying. **Example**: :code:`5`. """ self._check_authentication("edit_collection_folder_release") self._request( "post", f"{self.API_URL}/users/{username}/collection/folders" f"/{folder_id}/releases/{release_id}/instances/{instance_id}", json={"folder_id": new_folder_id, "rating": rating}, )
[docs] def delete_collection_folder_release( self, folder_id: int, release_id: int, instance_id: int, *, username: str = None, ) -> None: """ `User Collection > Delete Instance From Folder <https://www.discogs.com/developers/#page:user-collection,header :user-collection-delete-instance-from-folder-delete>`_: Remove an instance of a release from a user's collection folder. To move the release to the "Uncategorized" folder instead, use the :meth:`edit_collection_folder_release` method. .. admonition:: User authentication :class: warning Requires user authentication with a personal access token or via the OAuth 1.0a flow. Parameters ---------- folder_id : `int` The ID of the folder to modify. **Example**: :code:`3`. release_id : `int` The ID of the release you are modifying. **Example**: :code:`130076`. instance_id : `int` The ID of the instance. **Example**: :code:`1`. username : `str`, keyword-only, optional The username of the collection you are trying to modify. If not specified, the username of the authenticated user is used. **Example**: :code:`"rodneyfool"`. """ self._check_authentication("delete_collection_folder_release") self._request( "delete", f"{self.API_URL}/users/{username}/collection/folders" f"/{folder_id}/releases/{release_id}/instances/{instance_id}", )
[docs] def get_collection_fields( self, username: str = None ) -> list[dict[str, Any]]: """ `User Collection > Collection Fields <https://www.discogs.com/developers/#page:user-collection,header :user-collection-list-custom-fields-get>`_: Retrieve a list of user-defined collection notes fields. These fields are available on every release in the collection. .. admonition:: User authentication :class: dropdown warning If the collection has been made private by its owner, authentication as the collection owner is required. If you are not authenticated as the collection owner, only fields with public set to true will be visible. Parameters ---------- username : `str`, optional The username of the collection you are trying to fetch. If not specified, the username of the authenticated user is used. **Example**: :code:`"rodneyfool"`. Returns ------- fields : `list` A list of user-defined collection fields. .. admonition:: Sample response :class: dropdown .. code:: [ { "id": <int>, "name": <str>, "options": [<str>], "public": <bool>, "position": <int>, "type": "dropdown" }, { "id": <int>, "name": <str>, "lines": <int>, "public": <bool>, "position": <int>, "type": "textarea" } ] """ if username is None: if hasattr(self, "_username"): username = self._username else: raise ValueError("No username provided.") return self._get_json( f"{self.API_URL}/users/{username}/collection/fields" )["fields"]
[docs] def edit_collection_release_field( self, folder_id: int, release_id: int, instance_id: int, field_id: int, value: str, *, username: str = None, ) -> None: """ `User Collection > Edit Fields Instance <https://www.discogs.com/developers/#page:user-collection,header :user-collection-edit-fields-instance-post>`_: Change the value of a notes field on a particular instance. .. admonition:: User authentication :class: dropdown warning Requires user authentication with a personal access token or via the OAuth 1.0a flow. Parameters ---------- folder_id : `int` The ID of the folder to modify. **Example**: :code:`3`. release_id : `int` The ID of the release you are modifying. **Example**: :code:`130076`. instance_id : `int` The ID of the instance. **Example**: :code:`1`. field_id : `int` The ID of the field you are modifying. **Example**: :code:`1`. value : `str` The new value of the field. If the field's type is :code:`"dropdown"`, `value` must match one of the values in the field's list of options. username : `str`, keyword-only, optional The username of the collection you are trying to modify. If not specified, the username of the authenticated user is used. **Example**: :code:`"rodneyfool"`. """ self._check_authentication("edit_collection_fields") self._request( "post", f"{self.API_URL}/users/{username}/collection/folders" f"/{folder_id}/releases/{release_id}/instances/{instance_id}" f"/fields/{field_id}", params={"value": value}, )
[docs] def get_collection_value(self, username: str = None) -> dict[str, Any]: """ `User Collection > Collection Value <https://www.discogs.com/developers/#page:user-collection,header :user-collection-collection-value-get>`_: Returns the minimum, median, and maximum value of a user's collection. .. admonition:: User authentication :class: warning Requires user authentication with a personal access token or via the OAuth 1.0a flow. Parameters ---------- username : `str`, optional The username of the collection you are trying to fetch. If not specified, the username of the authenticated user is used. **Example**: :code:`"rodneyfool"`. Returns ------- value : `dict` The total minimum value of the user's collection. .. admonition:: Sample response :class: dropdown .. code:: { "maximum": <str>, "median": <str>, "minimum": <str> } """ self._check_authentication("get_collection_value") if username is None: if hasattr(self, "_username"): username = self._username else: raise ValueError("No username provided.") return self._get_json( f"{self.API_URL}/users/{username}/collection/value" )
### USER WANTLIST #########################################################
[docs] def get_wantlist_releases( self, username: str = None, /, *, page: Union[int, str] = None, per_page: Union[int, str] = None, ) -> dict[str, Any]: """ `User Wantlist > Wantlist <https://www.discogs.com/developers /#page:user-wantlist,header:user-wantlist-wantlist>`_: Returns the releases in a user's wantlist. Parameters ---------- username : `str`, optional The username of the wantlist you are trying to fetch. If not specified, the username of the authenticated user is used. **Example**: :code:`"rodneyfool"`. page : `int` or `str`, keyword-only, optional The page you want to request. **Example**: :code:`3`. per_page : `int` or `str`, keyword-only, optional The number of items per page. **Example**: :code:`25`. Returns ------- wantlist : `dict` The releases in the user's wantlist. .. admonition:: Sample response :class: dropdown .. code:: { "pagination": { "items": <int>, "page": <int>, "pages": <int>, "per_page": <int>, "urls": { "first": <str>, "last": <str>, "next": <str>, "prev": <str> } }, "wants": [ { "basic_information": { "artists": [ { "anv": <str>, "id": <int>, "join": <str>, "name": <str>, "resource_url": <str>, "role": <str>, "tracks": <str> } ], "formats": [ { "descriptions": <list[str]>, "name": <str>, "qty": <str>, "text": <str> } ], "genres": <list[str]>, "id": <int>, "labels": [ { "catno": <str>, "entity_type": <str>, "entity_type_name": <str>, "id": <int>, "name": <str>, "resource_url": <str>, } ], "resource_url": <str>, "styles": <list[str]>, "thumb": <str>, "title": <str>, "year": <int>, }, "id": <int>, "notes": <str>, "rating": <int> } ] } """ if username is None: if hasattr(self, "_username"): username = self._username else: raise ValueError("No username provided.") return self._get_json( f"{self.API_URL}/users/{username}/wants", params={"page": page, "per_page": per_page}, )
[docs] def add_wantlist_release( self, release_id: int, *, username: str = None ) -> dict[str, Any]: """ `User Wantlist > Add to Wantlist > Add to Wantlist <https://www.discogs.com/developers/#page:user-wantlist, header:user-wantlist-add-to-wantlist-put>`_: Add a release to a user's wantlist. .. admonition:: User authentication :class: warning Requires user authentication with a personal access token or via the OAuth 1.0a flow. Parameters ---------- release_id : `int` The ID of the release you are adding. **Example**: :code:`130076`. username : `str`, keyword-only, optional The username of the wantlist you are trying to modify. If not specified, the username of the authenticated user is used. **Example**: :code:`"rodneyfool"`. Returns ------- release : `dict` Information about the newly added release. .. admonition:: Sample response :class: dropdown .. code:: { "basic_information": { "artists": [ { "anv": <str>, "id": <int>, "join": <str>, "name": <str>, "resource_url": <str>, "role": <str>, "tracks": <str> } ], "formats": [ { "descriptions": <list[str]>, "name": <str>, "qty": <str>, "text": <str> } ], "genres": <list[str]>, "id": <int>, "labels": [ { "catno": <str>, "entity_type": <str>, "entity_type_name": <str>, "id": <int>, "name": <str>, "resource_url": <str>, } ], "resource_url": <str>, "styles": <list[str]>, "thumb": <str>, "title": <str>, "year": <int>, }, "id": <int>, "notes": <str>, "rating": <int> } """ self._check_authentication("add_wantlist_release") if username is None: if hasattr(self, "_username"): username = self._username else: raise ValueError("No username provided.") return self._request( "post", f"{self.API_URL}/users/{username}/wants/{release_id}", ).json()
[docs] def edit_wantlist_release( self, release_id: int, *, username: str = None, notes: str = None, rating: int = None, ) -> dict[str, Any]: """ `User Wantlist > Add to Wantlist > Edit Release in Wantlist <https://www.discogs.com/developers/#page:user-wantlist, header:user-wantlist-add-to-wantlist-post>`_: Update a release in a user's wantlist. .. admonition:: User authentication :class: warning Requires user authentication with a personal access token or via the OAuth 1.0a flow. Parameters ---------- release_id : `int` The ID of the release you are modifying. **Example**: :code:`130076`. username : `str`, keyword-only, optional The username of the wantlist you are trying to modify. If not specified, the username of the authenticated user is used. **Example**: :code:`"rodneyfool"`. notes : `str`, keyword-only, optional The new notes for the release. **Example**: :code:`"My favorite release"`. rating : `int`, keyword-only, optional The new rating for the release. **Example**: :code:`5`. Returns ------- release : `dict` Information about the edited release. .. admonition:: Sample response :class: dropdown .. code:: { "basic_information": { "artists": [ { "anv": <str>, "id": <int>, "join": <str>, "name": <str>, "resource_url": <str>, "role": <str>, "tracks": <str> } ], "formats": [ { "descriptions": <list[str]>, "name": <str>, "qty": <str>, "text": <str> } ], "genres": <list[str]>, "id": <int>, "labels": [ { "catno": <str>, "entity_type": <str>, "entity_type_name": <str>, "id": <int>, "name": <str>, "resource_url": <str>, } ], "resource_url": <str>, "styles": <list[str]>, "thumb": <str>, "title": <str>, "year": <int>, }, "id": <int>, "notes": <str>, "rating": <int> } """ self._check_authentication("edit_wantlist_release") if username is None: if hasattr(self, "_username"): username = self._username else: raise ValueError("No username provided.") self._request( "post", f"{self.API_URL}/users/{username}/wants/{release_id}", json={"notes": notes, "rating": rating}, )
[docs] def delete_wantlist_release( self, release_id: int, *, username: str = None ) -> None: """ `User Wantlist > Add to Wantlist > Delete Release from Wantlist <https://www.discogs.com/developers/#page:user-wantlist, header:user-wantlist-add-to-wantlist-delete>`_: Remove a release from a user's wantlist. .. admonition:: User authentication :class: warning Requires user authentication with a personal access token or via the OAuth 1.0a flow. Parameters ---------- release_id : `int` The ID of the release you are deleting. **Example**: :code:`130076`. username : `str`, keyword-only, optional The username of the wantlist you are trying to modify. If not specified, the username of the authenticated user is used. **Example**: :code:`"rodneyfool"`. """ self._check_authentication("delete_wantlist_release") if username is None: if hasattr(self, "_username"): username = self._username else: raise ValueError("No username provided.") self._request( "delete", f"{self.API_URL}/users/{username}/wants/{release_id}", )
### USER LISTS ############################################################
[docs] def get_user_lists( self, username: str = None, *, page: int = None, per_page: int = None ) -> dict[str, Any]: """ `User Lists > User Lists <https://www.discogs.com/developers /#page:user-lists,header:user-lists-user-lists>`_: Returns a user's lists. Parameters ---------- username : `str`, optional The username of the lists you are trying to fetch. If not specified, the username of the authenticated user is used. **Example**: :code:`"rodneyfool"`. page : `int` or `str`, keyword-only, optional The page you want to request. **Example**: :code:`3`. per_page : `int` or `str`, keyword-only, optional The number of items per page. **Example**: :code:`25`. Returns ------- lists : `dict` The lists created by the user. .. admonition:: Sample response :class: dropdown .. code:: { "lists": [ { "date_added": <str>, "date_changed": <str>, "description": <str>, "id": <int>, "image_url": <str>, "name": <str>, "public": <bool>, "resource_url": <str>, "uri": <str>, "user": { "avatar_url": <str>, "id": <int>, "resource_url": <str>, "username": <str> } } ], "pagination": { "items": <int>, "page": <int>, "pages": <int>, "per_page": <int>, "urls": { "first": <str>, "last": <str>, "next": <str>, "prev": <str> } } } """ if username is None: if hasattr(self, "_username"): username = self._username else: raise ValueError("No username provided.") return self._get_json( f"{self.API_URL}/users/{username}/lists", params={"page": page, "per_page": per_page}, )
[docs] def get_list(self, list_id: int) -> dict[str, Any]: """ `User Lists > List <https://www.discogs.com/developers /#page:user-lists,header:user-lists-list>`_: Returns the items in a list. Parameters ---------- list_id : `int` The ID of the list to request. **Example**: :code:`3`. Returns ------- list : `dict` The items in the list. .. admonition:: Sample response :class: dropdown .. code:: { "date_added": <str>, "date_changed": <str>, "description": <str>, "id": <int>, "image_url": <str>, "items": [ { "comment": <str>, "display_title": <str>, "id": <int>, "image_url": <str>, "resource_url": <str>, "stats": { "community": { "in_collection": <int>, "in_wantlist": <int> }, "user": { "in_collection": <int>, "in_wantlist": <int> } }, "type": "release", "uri": <str> } ], "name": <str>, "public": <bool>, "resource_url": <str>, "uri": <str>, "user": { "avatar_url": <str>, "id": <int>, "resource_url": <str>, "username": <str> } } """ return self._get_json(f"{self.API_URL}/lists/{list_id}")