from __future__ import annotations
from typing import TYPE_CHECKING
from ..._shared import TTLCache
from ._shared import DiscogsResourceAPI
if TYPE_CHECKING:
from datetime import datetime
from typing import Any
[docs]
class MarketplaceAPI(DiscogsResourceAPI):
"""
Marketplace API endpoints for the Discogs API.
.. important::
This class is managed by
:class:`~minim.api.discogs.DiscogsAPIClient` and should not be
instantiated directly.
"""
_LISTING_SORT_FIELDS = {
"artist",
"audio",
"catno",
"item",
"label",
"listed",
"location",
"price",
"status",
}
_USER_LISTING_STATUSES = {"Draft", "For Sale"}
_FILTER_LISTING_STATUSES = {
"All",
"Deleted",
"Expired",
"Sold",
"Suspended",
"Violation",
}
_ORDER_SORT_FIELDS = {"id", "buyer", "created", "status", "last_activity"}
_USER_ORDER_STATUSES = {
"New Order",
"Buyer Contacted",
"Invoice Sent",
"Payment Pending",
"Payment Received",
"In Progress",
"Shipped",
"Refund Sent",
"Cancelled (Non-Paying Buyer)",
"Cancelled (Item Unavailable)",
"Cancelled (Per Buyer's Request)",
}
_FILTER_ORDER_STATUSES = {
"All",
"Merged",
"Order Changed",
"Cancelled",
"Cancelled (Refund Received)",
}
__slots__ = ()
def _upsert_listing(
self,
endpoint_suffix: str = "",
/,
*,
allow_offers: bool | None = None,
media_condition: str | None = None,
price: float | None = None,
private_notes: str | None = None,
public_notes: str | None = None,
status: str | None = None,
shipping_count: int | str | None = None,
storage_location: str | None = None,
sleeve_condition: str | None = None,
weight: int | str | None = None,
payload: dict[str, Any] | None = None,
) -> dict[str, Any] | None:
"""
Create or update a marketplace listing.
Parameters
----------
endpoint_suffix : str; positional-only; default: :code:`""`
Endpoint suffix containing the ID of an existing listing.
allow_offers : bool; keyword-only; optional
Whether to accept offers for the listed item.
**API default**: :code:`False`.
media_condition : str; keyword-only; optional
Media condition.
**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)"`, :code:`"Poor (P)"`.
price : float; keyword-only; optional
Listing price.
private_notes : str; keyword-only; optional
Private comments (e.g., external IDs) that are visible to
only the seller.
public_notes : str; keyword-only; optional
Public comments (e.g., item condition) that are displayed to
the buyers.
status : str; keyword-only; optional
Listing status.
**Valid values**: :code:`"Draft"`, :code:`"For Sale"`.
shipping_count : int or str; keyword-only; optional
Number of items the listing counts as for the purpose of
calculating the shipping cost. Use :code:`"auto"` to
automatically estimate the quantity.
storage_location : str; keyword-only; optional
Identifier for the item's physical storage location that is
visible to only the seller.
sleeve_condition : str; keyword-only; optional
Sleeve condition.
**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)"`, :code:`"Poor (P)"`, :code:`"Generic"`,
:code:`"Not Graded"`, :code:`"No Cover"`.
weight : int or str; keyword-only; optional
Weight of the item in grams for the purpose of calculating
the shipping cost. Use :code:`"auto"` to automatically
estimate the value.
payload : dict[str, Any]; keyword-only; optional
JSON payload to include in the request.
Returns
-------
listing : dict[str, Any] or None
Discogs metadata for a newly created listing or :code:`None`
for an updated listing.
.. admonition:: Sample response
:class: response dropdown
.. code-block::
{
"listing_id": <int>,
"resource_url": <str>
}
"""
if payload is None:
payload = {}
if allow_offers is not None:
self._validate_type("allow_offers", allow_offers, bool)
payload["allow_offers"] = allow_offers
if media_condition is not None:
media_condition = self._prepare_string(
"media_condition", media_condition
)
if media_condition not in self._CONDITIONS:
raise ValueError(
f"Invalid media condition {media_condition!r}. "
f"Valid values: {self._join_values(self._CONDITIONS)}."
)
payload["condition"] = media_condition
if price is not None:
self._validate_number("price", price, int | float, 0.01)
payload["price"] = round(price, 2)
if private_notes is not None:
self._validate_type("private_notes", private_notes, str)
payload["external_id"] = private_notes
if public_notes is not None:
self._validate_type("public_notes", public_notes, str)
payload["comments"] = public_notes
if status is not None:
status = self._prepare_string("status", status).capitalize()
if status not in self._USER_LISTING_STATUSES:
raise ValueError(
f"Invalid listing status {status!r}. Valid values: "
f"{self._join_values(self._USER_LISTING_STATUSES)}."
)
payload["status"] = status
if shipping_count is not None:
if shipping_count != "auto":
self._validate_number("shipping_count", shipping_count, int, 0)
payload["format_quantity"] = shipping_count
if storage_location is not None:
self._validate_type("storage_location", storage_location, str)
payload["location"] = storage_location
if sleeve_condition is not None:
sleeve_condition = self._prepare_string(
"sleeve_condition", sleeve_condition
)
if sleeve_condition not in (
conditions := self._CONDITIONS
| self._ADDITIONAL_SLEEVE_CONDITIONS
):
raise ValueError(
f"Invalid sleeve condition {sleeve_condition!r}. "
f"Valid values: {self._join_values(conditions)}."
)
payload["sleeve_condition"] = sleeve_condition
if weight is not None:
if weight != "auto":
self._validate_number("weight", weight, int, 0)
payload["weight"] = weight
resp = self._client._request(
"POST", f"marketplace/listings{endpoint_suffix}", json=payload
)
if not endpoint_suffix:
return resp.json()
[docs]
@TTLCache.cached_method(ttl="user")
def get_user_inventory(
self,
username: str | None = None,
/,
*,
status: str | None = None,
limit: int | None = None,
page: int | None = None,
sort_by: str | None = None,
descending: bool | None = None,
) -> dict[str, Any]:
"""
`Marketplace > Inventory <https://www.discogs.com/developers
/#page:marketplace,header:marketplace-inventory>`_: Get Discogs
resource information for a user's marketplace inventory.
.. admonition:: User authentication
:class: entitlement dropdown
.. tab-set::
.. tab-item:: Optional
User authentication
Access private listings and the :code:`weight`,
:code:`format_quantity`, :code:`external_id`,
:code:`location`, and :code:`quantity` keys if
authenticated as the requested user.
Parameters
----------
username : str; positional-only; optional
Username of the user. If not specified, the username of the
current user is used. Only optional when authenticated.
**Example**: :code:`"rodneyfool"`.
status : str; keyword-only; optional
Listing status to filter by.
**Valid values**: :code:`"All"`, :code:`"Deleted"`,
:code:`"Draft"`, :code:`"Expired"`, :code:`"For Sale"`,
:code:`"Sold"`, :code:`"Suspended"`, :code:`"Violation"`.
limit : int; keyword-only; optional
Maximum number of listings to return.
**Valid range**: :code:`1` to :code:`100`.
**API default**: :code:`50`.
page : int; keyword-only; optional
Page number. Use with `limit` to get the next page of
listings.
**Minimum value**: :code:`1`.
**API default**: :code:`1`.
sort_by : str; keyword-only; optional
Field to sort the returned listings by.
**Valid values**: :code:`"listed"`, :code:`"price"`,
:code:`"item"`, :code:`"artist"`, :code:`"label"`,
:code:`"catno"`, :code:`"audio"`, :code:`"status"`,
:code:`"location"`.
descending : bool; keyword-only; optional
Whether to sort in descending order.
Returns
-------
inventory : dict[str, Any]
Page of Discogs metadata for the user's marketplace
inventory.
.. admonition:: Sample responses
:class: response dropdown
.. code-block::
{
"listings": [
{
"allow_offers": <bool>,
"audio": <bool>,
"comments": <str>,
"condition": <str>,
"id": <int>,
"posted": <str>,
"price": {
"currency": <str>,
"value": <float>
},
"release": {
"artist": <str>,
"catalog_number": <str>,
"description": <str>,
"format": <str>,
"id": <int>,
"resource_url": <str>,
"thumbnail": <str>,
"title": <str>,
"year": <int>
},
"resource_url": <str>,
"seller": {
"id": <int>,
"resource_url": <str>,
"username": <str>
},
"ships_from": <str>,
"sleeve_condition": <str>,
"status": <str>,
"uri": <str>
}
],
"pagination": {
"items": <int>,
"page": <int>,
"pages": <int>,
"per_page": <int>,
"urls": {
"first": <str>,
"last": <str>,
"next": <str>,
"prev": <str>
}
}
}
"""
params = {}
if status is not None:
status = self._prepare_string("status", status).capitalize()
if status not in (
statuses := self._FILTER_LISTING_STATUSES
| self._USER_LISTING_STATUSES
):
raise ValueError(
f"Invalid listing status {status!r}. "
f"Valid values: {self._join_values(statuses)}."
)
if sort_by is not None:
sort_by = self._prepare_string("sort_by", sort_by).lower()
if sort_by not in self._LISTING_SORT_FIELDS:
raise ValueError(
f"Invalid sort field {sort_by!r}. Valid values: "
f"{self._join_values(self._LISTING_SORT_FIELDS)}."
)
if sort_by in {"status", "location"}:
self._client._require_authentication(
"marketplace.get_user_inventory"
)
params["sort"] = sort_by
if descending is not None:
self._validate_type("descending", descending, bool)
params["sort_order"] = "desc" if descending else "asc"
return self._get_paginated_resources(
f"users/{self._resolve_username(username)}/inventory",
limit=limit,
page=page,
params=params,
)
[docs]
@TTLCache.cached_method(ttl="user")
def get_listing(
self,
listing_id: int | str,
/,
*,
currency: str | None = None,
) -> dict[str, Any]:
"""
`Marketplace > Listing > Get Listing <https://www.discogs.com
/developers/#page:marketplace,header:marketplace-listing>`_: Get
Discogs resource information for a marketplace listing.
.. admonition:: User authentication
:class: entitlement dropdown
.. tab-set::
.. tab-item:: Optional
User authentication
Access the :code:`in_cart` key if authenticated, and
the :code:`weight`, :code:`format_quantity`,
:code:`external_id`, :code:`location`, and
:code:`quantity` keys if authenticated as the
listing's owner.
Parameters
----------
listing_id : int or str; positional-only; optional
Discogs ID of the listing.
**Examples**: :code:`172723812`, :code:`"2983532888"`.
currency : str; keyword-only; optional
Currency 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"`, :code:`"ZAR"`.
Returns
-------
listing : dict[str, Any]
Discogs metadata for the listing.
.. admonition:: Sample response
:class: response dropdown
.. code-block::
{
"allow_offers": <bool>,
"audio": <bool>,
"comments": <str>,
"condition": <str>,
"id": <int>,
"original_price": {
"curr_abbr": <str>,
"curr_id": <int>,
"formatted": <str>,
"value": <float>
},
"original_shipping_price": {
"curr_abbr": <str>,
"curr_id": <int>,
"formatted": <str>,
"value": <float>
},
"posted": <str>,
"price": {
"currency": <str>,
"value": <float>
},
"release": {
"catalog_number": <str>,
"description": <str>,
"id": <int>,
"resource_url": <str>,
"thumbnail": <str>,
"year": <int>
},
"resource_url": <str>,
"seller": {
"avatar_url": <str>,
"payment": <str>,
"resource_url": <str>,
"shipping": <str>,
"stats": {
"rating": <str>,
"stars": <float>,
"total": <int>
},
"url": <str>,
"username": <str>
},
"shipping_price": {
"currency": <str>,
"value": <float>
},
"ships_from": <str>,
"sleeve_condition": <str>,
"status": <str>,
"uri": <str>
}
"""
self._validate_numeric("listing_id", listing_id, int, 1)
params = {}
if currency is not None:
currency = self._prepare_string("currency", currency).upper()
if currency not in self._CURRENCIES:
raise ValueError(
f"Invalid currency {currency!r}. "
f"Valid values: {self._join_values(self._CURRENCIES)}."
)
params["curr_abbr"] = currency
return self._client._request(
"GET", f"marketplace/listings/{listing_id}", params=params
).json()
[docs]
def create_listing(
self,
release_id: int | str,
/,
media_condition: str,
price: float,
*,
allow_offers: bool | None = None,
private_notes: str | None = None,
public_notes: str | None = None,
status: str | None = None,
shipping_count: int | str | None = None,
storage_location: str | None = None,
sleeve_condition: str | None = None,
weight: int | str | None = 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: entitlement
.. tab-set::
.. tab-item:: Required
User authentication
Access protected endpoints.
Parameters
----------
release_id : int or str; positional-only
Discogs ID of the release.
**Examples**: :code:`772347`, :code:`"7781525"`.
media_condition : str
Media condition.
**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)"`, :code:`"Poor (P)"`.
price : float
Listing price.
allow_offers : bool; keyword-only; optional
Whether to accept offers for the listed item.
**API default**: :code:`False`.
private_notes : str; keyword-only; optional
Private comments (e.g., external IDs) that are visible to
only the seller.
public_notes : str; keyword-only; optional
Public comments (e.g., item condition) that are displayed to
the buyers.
status : str; keyword-only; optional
Listing status.
**Valid values**: :code:`"Draft"`, :code:`"For Sale"`.
**API default**: :code:`"For Sale"`.
shipping_count : int or str; keyword-only; optional
Number of items the listing counts as for the purpose of
calculating the shipping cost. Use :code:`"auto"` to
automatically estimate the quantity.
**API default**: :code:`"auto"`.
storage_location : str; keyword-only; optional
Identifier for the item's physical storage location that is
visible to only the seller.
sleeve_condition : str; keyword-only; optional
Sleeve condition.
**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)"`, :code:`"Poor (P)"`, :code:`"Generic"`,
:code:`"Not Graded"`, :code:`"No Cover"`.
weight : int or str; keyword-only; optional
Weight of the item in grams for the purpose of calculating
the shipping cost. Use :code:`"auto"` to automatically
estimate the value.
**API default**: :code:`"auto"`.
Returns
-------
listing : dict[str, Any]
Discogs metadata for the newly created listing.
.. admonition:: Sample response
:class: response dropdown
.. code-block::
{
"listing_id": <int>,
"resource_url": <str>
}
"""
self._client._require_authentication("marketplace.create_listing")
self._validate_numeric("release_id", release_id, int, 1)
return self._upsert_listing(
allow_offers=allow_offers,
media_condition=media_condition,
price=price,
private_notes=private_notes,
public_notes=public_notes,
status=status,
shipping_count=shipping_count,
storage_location=storage_location,
sleeve_condition=sleeve_condition,
weight=weight,
payload={"release_id": release_id},
)
[docs]
def update_listing(
self,
listing_id: int | str,
/,
*,
allow_offers: bool | None = None,
media_condition: str | None = None,
price: float | None = None,
private_notes: str | None = None,
public_notes: str | None = None,
status: str | None = None,
shipping_count: int | str | None = None,
storage_location: str | None = None,
sleeve_condition: str | None = None,
weight: int | str | None = None,
) -> None:
"""
`Marketplace > Listing > Edit a Listing <https://www.discogs.com
/developers/#page:marketplace,
header:marketplace-listing-post>`_: Update a marketplace
listing.
.. admonition:: User authentication
:class: entitlement
.. tab-set::
.. tab-item:: Required
User authentication
Access protected endpoints.
Parameters
----------
listing_id : int or str; positional-only
Discogs ID of the listing.
**Examples**: :code:`172723812`, :code:`"2983532888"`.
allow_offers : bool; keyword-only; optional
Whether to accept offers for the listed item.
**API default**: :code:`False`.
media_condition : str; keyword-only; optional
Media condition.
**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)"`, :code:`"Poor (P)"`.
price : float; keyword-only; optional
Listing price.
private_notes : str; keyword-only; optional
Private comments (e.g., external IDs) that are visible to
only the seller.
public_notes : str; keyword-only; optional
Public comments (e.g., item condition) that are displayed to
the buyers.
status : str; keyword-only; optional
Listing status.
**Valid values**: :code:`"Draft"`, :code:`"For Sale"`.
shipping_count : int or str; keyword-only; optional
Number of items the listing counts as for the purpose of
calculating the shipping cost. Use :code:`"auto"` to
automatically estimate the quantity.
storage_location : str; keyword-only; optional
Identifier for the item's physical storage location that is
visible to only the seller.
sleeve_condition : str; keyword-only; optional
Sleeve condition.
**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)"`, :code:`"Poor (P)"`, :code:`"Generic"`,
:code:`"Not Graded"`, :code:`"No Cover"`.
weight : int or str; keyword-only; optional
Weight of the item in grams for the purpose of calculating
the shipping cost. Use :code:`"auto"` to automatically
estimate the value.
"""
self._client._require_authentication("marketplace.update_listing")
self._validate_numeric("listing_id", listing_id, int, 1)
return self._upsert_listing(
f"/{listing_id}",
allow_offers=allow_offers,
media_condition=media_condition,
price=price,
private_notes=private_notes,
public_notes=public_notes,
status=status,
shipping_count=shipping_count,
storage_location=storage_location,
sleeve_condition=sleeve_condition,
weight=weight,
)
[docs]
def delete_listing(self, listing_id: int | str, /) -> None:
"""
`Marketplace > Listing > Delete a Listing
<https://www.discogs.com/developers/#page:marketplace,
header:marketplace-listing-delete>`_: Delete a marketplace
listing.
.. admonition:: User authentication
:class: entitlement
.. tab-set::
.. tab-item:: Required
User authentication
Access protected endpoints.
Parameters
----------
listing_id : int or str; positional-only
Discogs ID of the listing.
**Examples**: :code:`172723812`, :code:`"2983532888"`.
"""
self._client._require_authentication("marketplace.delete_listing")
self._validate_numeric("listing_id", listing_id, int, 1)
self._client._request("DELETE", f"marketplace/listings/{listing_id}")
[docs]
@TTLCache.cached_method(ttl="user")
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
Discogs resource information for a marketplace order.
.. admonition:: User authentication
:class: entitlement
.. tab-set::
.. tab-item:: Required
User authentication
Access protected endpoints.
Parameters
----------
order_id : str; positional-only
Discogs ID of the order.
**Example**: :code:`"1-1"`.
Returns
-------
order : dict[str, Any]
Discogs metadata for the order.
.. admonition:: Sample response
:class: response dropdown
.. code-block::
{
"additional_instructions": <str>,
"archived": <bool>,
"buyer": {
"id": <int>,
"resource_url": <str>,
"username": <str>
},
"created": <str>,
"fee": {
"currency": <str>,
"value": <float>
},
"id": <str>,
"items": [
{
"id": <int>,
"media_condition": <str>,
"price": {
"currency": <str>,
"value": <float>
},
"release": {
"description": <str>,
"id": <int>
},
"sleeve_condition": <str>
}
],
"last_activity": <str>,
"messages_url": <str>,
"next_status": <list[str]>,
"resource_url": <str>,
"seller": {
"id": <int>,
"resource_url": <str>,
"username": <str>
},
"shipping": {
"currency": <str>,
"method": <str>,
"value": <float>
},
"shipping_address": <str>,
"status": <str>,
"total": {
"currency": <str>,
"value": <float>
},
"uri": <str>
}
"""
self._client._require_authentication("marketplace.get_order")
return self._client._request(
"GET",
f"marketplace/orders/{self._prepare_string('order_id', order_id)}",
).json()
[docs]
def update_order(
self,
order_id: str,
/,
*,
status: str | None = None,
shipping_fee: float | None = None,
) -> dict[str, Any]:
"""
`Marketplace > Order > Edit an Order <https://www.discogs.com
/developers/#page:marketplace,header:marketplace-order-post>`_:
Update a marketplace order.
.. admonition:: User authentication
:class: entitlement
.. tab-set::
.. tab-item:: Required
User authentication
Access protected endpoints.
.. important::
Exactly one of `status` or `shipping_fee` must be provided.
.. note::
Calling this method will send the buyer a message with the
following content:
.. code-block:: none
Seller changed status from {old_status} to {new_status}
.. seealso::
:meth:`add_order_message` – Simultaneously add an order
message and change the order status.
Parameters
----------
order_id : str; positional-only
Discogs ID of the order.
**Example**: :code:`"1-1"`.
status : str; keyword-only; optional
Order status.
**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)"`,
:code:`"Cancelled (Per Buyer's Request)"`.
shipping_fee : float; keyword-only; optional
Shipping fee. If specified, the buyer is invoiced and the
order status is set to :code:`Invoice Sent`.
Returns
-------
order : dict[str, Any]
Discogs metadata for the updated order.
.. admonition:: Sample response
:class: response dropdown
.. code-block::
{
"additional_instructions": <str>,
"archived": <bool>,
"buyer": {
"id": <int>,
"resource_url": <str>,
"username": <str>
},
"created": <str>,
"fee": {
"currency": <str>,
"value": <float>
},
"id": <str>,
"items": [
{
"id": <int>,
"media_condition": <str>,
"price": {
"currency": <str>,
"value": <float>
},
"release": {
"description": <str>,
"id": <int>
},
"sleeve_condition": <str>
}
],
"last_activity": <str>,
"messages_url": <str>,
"next_status": <list[str]>,
"resource_url": <str>,
"seller": {
"id": <int>,
"resource_url": <str>,
"username": <str>
},
"shipping": {
"currency": <str>,
"method": <str>,
"value": <float>
},
"shipping_address": <str>,
"status": <str>,
"total": {
"currency": <str>,
"value": <float>
},
"uri": <str>
}
"""
self._client._require_authentication("marketplace.update_order")
payload = {}
if status is not None:
status = self._prepare_string("status", status).capitalize()
if status not in self._USER_ORDER_STATUSES:
raise ValueError(
f"Invalid order status {status!r}. Valid values: "
f"{self._join_values(self._USER_ORDER_STATUSES)}."
)
payload["status"] = status
if shipping_fee is not None:
self._validate_number("shipping_fee", shipping_fee, int | float, 0)
payload["shipping"] = shipping_fee
if len(payload) != 1:
raise ValueError(
"Exactly one of `status` or `shipping_fee` must be provided."
)
return self._client._request(
"POST",
f"marketplace/orders/{self._prepare_string('order_id', order_id)}",
json=payload,
).json()
[docs]
@TTLCache.cached_method(ttl="user")
def get_my_orders(
self,
*,
status: str | None = None,
created_after: str | datetime | None = None,
created_before: str | datetime | None = None,
archived: bool | None = None,
limit: int | None = None,
page: int | None = None,
sort_by: str | None = None,
descending: bool | None = None,
) -> dict[str, Any]:
"""
`Marketplace > List Orders <https://www.discogs.com/developers
/#page:marketplace,header:marketplace-list-orders>`_: Get
Discogs resource information for the current user's marketplace
orders.
.. admonition:: User authentication
:class: entitlement
.. tab-set::
.. tab-item:: Required
User authentication
Access protected endpoints.
Parameters
----------
status : str; keyword-only; optional
Order status to filter by.
**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 (Non-Paying Buyer)"`,
:code:`"Cancelled (Item Unavailable)"`,
:code:`"Cancelled (Per Buyer's Request)"`,
:code:`"Cancelled (Refund Received)"`.
created_after : str or datetime.datetime; keyword-only; optional
Only return orders created after this date, in
:code:`YYYY-MM-DDTHH:MM:SSZ` format.
created_before : str or datetime.datetime; keyword-only; \
optional
Only return orders created before this date, in
:code:`YYYY-MM-DDTHH:MM:SSZ` format.
archived : bool; keyword-only; optional
Whether to only include archived orders.
limit : int; keyword-only; optional
Maximum number of orders to return.
**Valid range**: :code:`1` to :code:`100`.
**API default**: :code:`50`.
page : int; keyword-only; optional
Page number. Use with `limit` to get the next page of
orders.
**Minimum value**: :code:`1`.
**API default**: :code:`1`.
sort_by : str; keyword-only; optional
Field to sort the returned orders by.
**Valid values**: :code:`"id"`, :code:`"buyer"`,
:code:`"created"`, :code:`"status"`,
:code:`"last_activity"`.
descending : bool; keyword-only; optional
Whether to sort in descending order.
Returns
-------
orders : dict[str, Any]
Page of Discogs metadata for the current user's orders.
.. admonition:: Sample response
:class: response dropdown
.. code-block::
{
"orders": [
{
"additional_instructions": <str>,
"archived": <bool>,
"buyer": {
"id": <int>,
"resource_url": <str>,
"username": <str>
},
"created": <str>,
"fee": {
"currency": <str>,
"value": <float>
},
"id": <str>,
"items": [
{
"id": <int>,
"media_condition": <str>,
"price": {
"currency": <str>,
"value": <float>
},
"release": {
"description": <str>,
"id": <int>
},
"sleeve_condition": <str>
}
],
"last_activity": <str>,
"messages_url": <str>,
"next_status": <list[str]>,
"resource_url": <str>,
"seller": {
"id": <int>,
"resource_url": <str>,
"username": <str>
},
"shipping": {
"currency": <str>,
"method": <str>,
"value": <float>
},
"shipping_address": <str>,
"status": <str>,
"total": {
"currency": <str>,
"value": <float>
},
"uri": <str>
}
],
"pagination": {
"items": <int>,
"page": <int>,
"pages": <int>,
"per_page": <int>,
"urls": {
"first": <str>,
"last": <str>,
"next": <str>,
"prev": <str>
}
}
}
"""
self._client._require_authentication("marketplace.get_my_orders")
params = {}
if status is not None:
status = self._prepare_string("status", status).capitalize()
if status not in (
order_statuses := self._USER_ORDER_STATUSES
| self._FILTER_ORDER_STATUSES
):
raise ValueError(
f"Invalid order status {status!r}. Valid values: "
f"{self._join_values(order_statuses)}."
)
params["status"] = status
if created_after is not None:
params["created_after"] = self._prepare_datetime(
created_after, "%Y-%m-%dT%H:%M:%SZ"
)
if created_before is not None:
params["created_before"] = self._prepare_datetime(
created_before, "%Y-%m-%dT%H:%M:%SZ"
)
if archived is not None:
self._validate_type("archived", archived, bool)
params["archived"] = archived
if sort_by is not None:
sort_by = self._prepare_string("sort_by", sort_by).lower()
if sort_by not in self._ORDER_SORT_FIELDS:
raise ValueError(
f"Invalid sort field {sort_by!r}. Valid values: "
f"{self._join_values(self._ORDER_SORT_FIELDS)}."
)
params["sort"] = sort_by
if descending is not None:
self._validate_type("descending", descending, bool)
params["sort_order"] = "desc" if descending else "asc"
return self._get_paginated_resources(
"marketplace/orders", limit=limit, page=page, params=params
)
[docs]
@TTLCache.cached_method(ttl="user")
def get_order_messages(
self,
order_id: str,
/,
*,
limit: int | None = None,
page: int | None = None,
) -> dict[str, Any]:
"""
`Marketplace > List Order Messages > List Order Messages
<https://www.discogs.com/developers/#page:marketplace,
header:marketplace-list-order-messages-get>`_: Get the
communication history for a marketplace order.
.. admonition:: User authentication
:class: entitlement
.. tab-set::
.. tab-item:: Required
User authentication
Access protected endpoints.
Parameters
----------
order_id : str; positional-only
Discogs ID of the order.
**Example**: :code:`"1-1"`.
limit : int; keyword-only; optional
Maximum number of messages to return.
**Valid range**: :code:`1` to :code:`100`.
**API default**: :code:`50`.
page : int; keyword-only; optional
Page number. Use with `limit` to get the next page of
messages.
**Minimum value**: :code:`1`.
**API default**: :code:`1`.
Returns
-------
messages : dict[str, Any]
Page of Discogs metadata for the order's messages.
.. admonition:: Sample response
:class: response dropdown
.. code-block::
{
"from": {
"resource_url": <str>,
"username": <str>
},
"message": <str>,
"order": {
"id": <str>,
"resource_url": <str>
},
"subject": <str>,
"timestamp": <str>
}
"""
self._client._require_authentication("marketplace.get_order_messages")
return self._get_paginated_resources(
"marketplace/orders"
f"/{self._prepare_string('order_id', order_id)}/messages",
limit=limit,
page=page,
)
[docs]
def post_order_message(
self,
order_id: str,
message: str | None = None,
/,
*,
status: str | None = None,
) -> dict[str, Any]:
"""
`Marketplace > List Order Messages > Add New Message
<https://www.discogs.com/developers/#page:marketplace,
header:marketplace-list-order-messages-post>`_: Post a new
message to and/or update the status for a marketplace order.
.. admonition:: User authentication
:class: entitlement
.. tab-set::
.. tab-item:: Required
User authentication
Access protected endpoints.
.. important::
At least one of `message` or `status` must be provided.
Parameters
----------
order_id : str; positional-only
Discogs ID of the order.
**Example**: :code:`"1-1"`.
message : str; positional-only; optional
Order message.
status : str; keyword-only; optional
Order status.
.. note::
If specified, `message` will be prepended with:
.. code-block::
f"Seller changed status from {old_status} to {status}"
**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)"`,
:code:`"Cancelled (Per Buyer's Request)"`.
Returns
-------
message : dict[str, Any]
Discogs metadata for the order's new message.
.. admonition:: Sample response
:class: response dropdown
.. code-block::
{
"from": {
"resource_url": <str>,
"username": <str>
},
"message": <str>,
"order": {
"id": <str>,
"resource_url": <str>
},
"subject": <str>,
"timestamp": <str>
}
"""
self._client._require_authentication("marketplace.post_order_message")
payload = {}
if message is not None:
self._validate_type("message", message, str)
payload["message"] = message
if status is not None:
status = self._prepare_string("status", status).capitalize()
if status not in self._USER_ORDER_STATUSES:
raise ValueError(
f"Invalid order status {status!r}. Valid values: "
f"{self._join_values(self._USER_ORDER_STATUSES)}."
)
payload["status"] = status
if not payload:
raise RuntimeError(
"At least one of `message` or `status` must be provided."
)
return self._client._request(
"POST",
f"marketplace/order/{self._prepare_string('order_id', order_id)}/messages",
json=payload,
).json()
[docs]
@TTLCache.cached_method(ttl="static")
def get_selling_fee(
self, price: float, /, currency: str | None = None
) -> dict[str, Any]:
"""
`Marketplace > Fee <https://www.discogs.com/developers
/#page:marketplace,header:marketplace-fee>`_: Get the
marketplace selling fee for a specified price․
`Marketplace > Fee with Currency <https://www.discogs.com
/developers/#page:marketplace,
header:marketplace-fee-with-currency>`_: Get the marketplace
selling fee in a particular currency for a specified price.
Parameters
----------
price : float; positional-only
Item price.
currency : str; optional
Fee 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"`, :code:`"ZAR"`.
Returns
-------
fee : dict[str, Any]
Calculated marketplace selling fee.
**Sample response**:
:code:`{"currency": <str>, "value": <float>}`.
"""
self._validate_number("price", price, int | float, 0)
if currency is None:
return self._client._request(f"marketplace/fee/{price}").json()
currency = self._prepare_string("currency", currency).upper()
if currency not in self._CURRENCIES:
raise ValueError(
f"Invalid currency {currency!r}. "
f"Valid values: {self._join_values(self._CURRENCIES)}."
)
return self._client._request(
f"marketplace/fee/{price}/{currency}"
).json()
[docs]
@TTLCache.cached_method(ttl="popularity")
def get_release_price_suggestions(
self, release_id: int | str, /
) -> dict[str, Any]:
"""
`Marketplace > Price Suggestions <https://www.discogs.com
/developers/#page:marketplace,
header:marketplace-price-suggestions>`_: Get suggested pricing
information for a release.
.. admonition:: User authentication
:class: entitlement
.. tab-set::
.. tab-item:: Required
User authentication
Access protected endpoints.
Parameters
----------
release_id : int or str; positional-only
Discogs ID of the release.
**Examples**: :code:`772347`, :code:`"7781525"`.
Returns
-------
price_suggestions : dict[str, Any]
Release's suggested prices by media condition.
.. admonition:: Sample response
:class: response dropdown
.. code-block::
{
"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._client._require_authentication(
"marketplace.get_release_price_suggestions"
)
self._validate_numeric("release_id", release_id, int, 1)
return self._client._request(
"GET", f"marketplace/price_suggestions/{release_id}"
).json()
[docs]
@TTLCache.cached_method(ttl="popularity")
def get_release_marketplace_stats(
self, release_id: int | str, /, *, currency: str | None = None
) -> dict[str, Any]:
"""
`Marketplace > Release Statistics <https://www.discogs.com
/developers/#page:marketplace,
header:marketplace-release-statistics>`_: Get marketplace
statistics for a release.
Parameters
----------
release_id : int or str; positional-only
Discogs ID of the release.
**Examples**: :code:`772347`, :code:`"7781525"`.
currency : str; keyword-only; optional
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"`, :code:`"ZAR"`.
Returns
-------
marketplace_stats : dict[str, Any]
Release's marketplace statistics.
.. admonition:: Sample response
:class: response dropdown
.. code-block::
{
"blocked_from_sale": <bool>,
"lowest_price": {
"currency": <str>,
"value": <float>
},
"num_for_sale": <int>
}
"""
params = {}
if currency is not None:
currency = self._prepare_string("currency", currency).upper()
if currency not in self._CURRENCIES:
raise ValueError(
f"Invalid currency {currency!r}. "
f"Valid values: {self._join_values(self._CURRENCIES)}."
)
params["curr_abbr"] = currency
return self._client._request(
f"marketplace/stats/{release_id}", params=params
).json()