pypricecharting 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pypricecharting/__init__.py +66 -0
- pypricecharting/_auth.py +57 -0
- pypricecharting/asyncpricecharting.py +313 -0
- pypricecharting/error.py +25 -0
- pypricecharting/models.py +82 -0
- pypricecharting/pricecharting.py +306 -0
- pypricecharting/py.typed +0 -0
- pypricecharting/requests.py +107 -0
- pypricecharting/responses.py +302 -0
- pypricecharting/route.py +30 -0
- pypricecharting/types.py +439 -0
- pypricecharting-0.0.1.dist-info/METADATA +130 -0
- pypricecharting-0.0.1.dist-info/RECORD +15 -0
- pypricecharting-0.0.1.dist-info/WHEEL +4 -0
- pypricecharting-0.0.1.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
from pypricecharting.asyncpricecharting import AsyncPriceCharting
|
|
2
|
+
from pypricecharting.error import APIError, ParameterError, PriceChartingError
|
|
3
|
+
from pypricecharting.models import Buyer, Offer, PriceChartingModel, ProductSummary
|
|
4
|
+
from pypricecharting.pricecharting import PriceCharting
|
|
5
|
+
from pypricecharting.requests import (
|
|
6
|
+
OfferPublishRequestParams,
|
|
7
|
+
OffersRequestParams,
|
|
8
|
+
OffersSort,
|
|
9
|
+
OffersStatus,
|
|
10
|
+
)
|
|
11
|
+
from pypricecharting.responses import (
|
|
12
|
+
ErrorResponse,
|
|
13
|
+
OfferDetailsResponse,
|
|
14
|
+
OfferEndResponse,
|
|
15
|
+
OfferFeedbackResponse,
|
|
16
|
+
OfferPublishResponse,
|
|
17
|
+
OfferRefundResponse,
|
|
18
|
+
OfferShipResponse,
|
|
19
|
+
OffersResponse,
|
|
20
|
+
ProductResponse,
|
|
21
|
+
ProductsResponse,
|
|
22
|
+
)
|
|
23
|
+
from pypricecharting.types import (
|
|
24
|
+
Condition,
|
|
25
|
+
Console,
|
|
26
|
+
Genre,
|
|
27
|
+
OfferStatus,
|
|
28
|
+
RateLimit,
|
|
29
|
+
Rating,
|
|
30
|
+
State,
|
|
31
|
+
Status,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
__all__ = [
|
|
35
|
+
"APIError",
|
|
36
|
+
"AsyncPriceCharting",
|
|
37
|
+
"Buyer",
|
|
38
|
+
"Condition",
|
|
39
|
+
"Console",
|
|
40
|
+
"ErrorResponse",
|
|
41
|
+
"Genre",
|
|
42
|
+
"Offer",
|
|
43
|
+
"OfferDetailsResponse",
|
|
44
|
+
"OfferEndResponse",
|
|
45
|
+
"OfferFeedbackResponse",
|
|
46
|
+
"OfferPublishRequestParams",
|
|
47
|
+
"OfferPublishResponse",
|
|
48
|
+
"OfferRefundResponse",
|
|
49
|
+
"OfferShipResponse",
|
|
50
|
+
"OfferStatus",
|
|
51
|
+
"OffersRequestParams",
|
|
52
|
+
"OffersResponse",
|
|
53
|
+
"OffersSort",
|
|
54
|
+
"OffersStatus",
|
|
55
|
+
"ParameterError",
|
|
56
|
+
"PriceCharting",
|
|
57
|
+
"PriceChartingError",
|
|
58
|
+
"PriceChartingModel",
|
|
59
|
+
"ProductResponse",
|
|
60
|
+
"ProductSummary",
|
|
61
|
+
"ProductsResponse",
|
|
62
|
+
"RateLimit",
|
|
63
|
+
"Rating",
|
|
64
|
+
"State",
|
|
65
|
+
"Status",
|
|
66
|
+
]
|
pypricecharting/_auth.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import re
|
|
3
|
+
from collections.abc import Generator
|
|
4
|
+
from urllib.parse import urlencode
|
|
5
|
+
|
|
6
|
+
from httpx2 import Auth, Request, Response
|
|
7
|
+
from pydantic import SecretStr
|
|
8
|
+
|
|
9
|
+
_TOKEN_PARAM = re.compile(r"([?&]t=)[^&\s\"']+")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class _TokenAuth(Auth):
|
|
13
|
+
"""Add the PriceCharting token as `t` in the query string (GET) or form body (POST)."""
|
|
14
|
+
|
|
15
|
+
requires_request_body = True
|
|
16
|
+
|
|
17
|
+
def __init__(self, token: SecretStr):
|
|
18
|
+
self._token = token
|
|
19
|
+
|
|
20
|
+
def auth_flow(self, request: Request) -> Generator[Request, Response]:
|
|
21
|
+
"""Attach the token to the outgoing request."""
|
|
22
|
+
token = self._token.get_secret_value()
|
|
23
|
+
|
|
24
|
+
if request.method == "GET":
|
|
25
|
+
request.url = request.url.copy_set_param("t", token)
|
|
26
|
+
yield request
|
|
27
|
+
return
|
|
28
|
+
|
|
29
|
+
param = urlencode({"t": token}).encode()
|
|
30
|
+
body = request.content + b"&" + param if request.content else param
|
|
31
|
+
headers = request.headers.copy()
|
|
32
|
+
headers.pop("Content-Length", None) # forces httpx2 to recalculate length
|
|
33
|
+
headers["Content-Type"] = "application/x-www-form-urlencoded"
|
|
34
|
+
|
|
35
|
+
yield Request(
|
|
36
|
+
request.method,
|
|
37
|
+
request.url,
|
|
38
|
+
headers=headers,
|
|
39
|
+
content=body,
|
|
40
|
+
extensions=request.extensions,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class _RedactTokenFilter(logging.Filter):
|
|
45
|
+
"""Replace the `t` query parameter in httpx2 log records with `REDACTED`."""
|
|
46
|
+
|
|
47
|
+
def filter(self, record: logging.LogRecord) -> bool:
|
|
48
|
+
"""Redact the token in place and always let the record through."""
|
|
49
|
+
message = record.getMessage()
|
|
50
|
+
redacted = _TOKEN_PARAM.sub(r"\1REDACTED", message)
|
|
51
|
+
if redacted != message:
|
|
52
|
+
record.msg = redacted
|
|
53
|
+
record.args = None
|
|
54
|
+
return True
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
_REDACT_FILTER = _RedactTokenFilter()
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
from types import TracebackType
|
|
4
|
+
from typing import Self
|
|
5
|
+
|
|
6
|
+
from httpx2 import AsyncBaseTransport, AsyncClient
|
|
7
|
+
from pydantic import SecretStr
|
|
8
|
+
|
|
9
|
+
from pypricecharting._auth import _REDACT_FILTER, _TokenAuth
|
|
10
|
+
from pypricecharting.error import ParameterError
|
|
11
|
+
from pypricecharting.requests import OfferPublishRequestParams, OffersRequestParams
|
|
12
|
+
from pypricecharting.responses import (
|
|
13
|
+
OfferDetailsResponse,
|
|
14
|
+
OfferEndResponse,
|
|
15
|
+
OfferFeedbackResponse,
|
|
16
|
+
OfferPublishResponse,
|
|
17
|
+
OfferRefundResponse,
|
|
18
|
+
OfferShipResponse,
|
|
19
|
+
OffersResponse,
|
|
20
|
+
ProductResponse,
|
|
21
|
+
ProductsResponse,
|
|
22
|
+
_parse,
|
|
23
|
+
)
|
|
24
|
+
from pypricecharting.route import BASE_URL, Route
|
|
25
|
+
from pypricecharting.types import Rating
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class AsyncPriceCharting:
|
|
29
|
+
"""Asynchronous PriceCharting API client.
|
|
30
|
+
|
|
31
|
+
Reuse one client for all requests and close it when done (`async with` or `aclose()`).
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
TOKEN_KEY = "PRICECHARTING_TOKEN"
|
|
35
|
+
"""Environment variable the token is read from when none is passed."""
|
|
36
|
+
|
|
37
|
+
def __init__(
|
|
38
|
+
self,
|
|
39
|
+
token: str | None = None,
|
|
40
|
+
base_url: str = BASE_URL,
|
|
41
|
+
transport: AsyncBaseTransport | None = None,
|
|
42
|
+
):
|
|
43
|
+
"""Create a client authenticated with a PriceCharting API token.
|
|
44
|
+
|
|
45
|
+
The token is redacted from `httpx2` request logs.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
token: The 40-character PriceCharting API token. If omitted, it is read from the
|
|
49
|
+
`PRICECHARTING_TOKEN` environment variable.
|
|
50
|
+
base_url: API base URL. Override it to use a mock server.
|
|
51
|
+
transport: Custom `httpx2` async transport, such as `httpx2.MockTransport` in tests.
|
|
52
|
+
|
|
53
|
+
Raises:
|
|
54
|
+
ValueError: If the token is missing or not 40 characters.
|
|
55
|
+
|
|
56
|
+
"""
|
|
57
|
+
if token is not None and len(token) != 40:
|
|
58
|
+
raise ValueError("PriceCharting API token is not 40 characters")
|
|
59
|
+
token = token or os.environ.get(self.TOKEN_KEY)
|
|
60
|
+
|
|
61
|
+
if token is None:
|
|
62
|
+
raise ValueError("PriceCharting API token not found")
|
|
63
|
+
if len(token) != 40:
|
|
64
|
+
raise ValueError("PriceCharting API token is not 40 characters")
|
|
65
|
+
|
|
66
|
+
self._token = SecretStr(token)
|
|
67
|
+
self._client = AsyncClient(
|
|
68
|
+
base_url=base_url, auth=_TokenAuth(self._token), transport=transport
|
|
69
|
+
)
|
|
70
|
+
logging.getLogger("httpx2").addFilter(_REDACT_FILTER)
|
|
71
|
+
|
|
72
|
+
async def aclose(self):
|
|
73
|
+
"""Close the underlying HTTP client."""
|
|
74
|
+
await self._client.aclose()
|
|
75
|
+
|
|
76
|
+
async def __aenter__(self) -> Self:
|
|
77
|
+
"""Enter the async context and return the client."""
|
|
78
|
+
return self
|
|
79
|
+
|
|
80
|
+
async def __aexit__(
|
|
81
|
+
self,
|
|
82
|
+
exc_type: type[BaseException] | None,
|
|
83
|
+
exc_value: BaseException | None,
|
|
84
|
+
traceback: TracebackType | None,
|
|
85
|
+
) -> None:
|
|
86
|
+
"""Close the underlying HTTP client on exit."""
|
|
87
|
+
await self.aclose()
|
|
88
|
+
|
|
89
|
+
async def product(
|
|
90
|
+
self, id: str | None = None, upc: str | None = None, q: str | None = None
|
|
91
|
+
) -> ProductResponse:
|
|
92
|
+
"""Get a single product by exactly one of `id`, `upc`, or `q`.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
id: PriceCharting product ID.
|
|
96
|
+
upc: UPC barcode.
|
|
97
|
+
q: Full text search on title and/or console. Only the best match is returned.
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
The matching product.
|
|
101
|
+
|
|
102
|
+
Raises:
|
|
103
|
+
ParameterError: If not exactly one of `id`, `upc`, or `q` is given.
|
|
104
|
+
APIError: If the API returns an error.
|
|
105
|
+
|
|
106
|
+
"""
|
|
107
|
+
params = {
|
|
108
|
+
key: value for key, value in {"id": id, "upc": upc, "q": q}.items() if value is not None
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if len(params) == 0:
|
|
112
|
+
raise ParameterError("Invalid parameters. Either id, upc, or q are required.")
|
|
113
|
+
if len(params) > 1:
|
|
114
|
+
raise ParameterError("Invalid parameters. Only one of id, upc, or q is allowed.")
|
|
115
|
+
|
|
116
|
+
response = await self._client.get(Route.Prices.PRODUCT, params=params)
|
|
117
|
+
return _parse(response, ProductResponse)
|
|
118
|
+
|
|
119
|
+
async def products(self, q: str) -> ProductsResponse:
|
|
120
|
+
"""Search for products by title and/or console.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
q: Full text search on title and/or console. Surrounding whitespace is stripped.
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
Up to 20 matching products.
|
|
127
|
+
|
|
128
|
+
Raises:
|
|
129
|
+
ParameterError: If `q` is empty or only whitespace.
|
|
130
|
+
APIError: If the API returns an error.
|
|
131
|
+
|
|
132
|
+
"""
|
|
133
|
+
q = q.strip()
|
|
134
|
+
|
|
135
|
+
if len(q) == 0:
|
|
136
|
+
raise ParameterError("Invalid parameter; Query string q was empty.")
|
|
137
|
+
|
|
138
|
+
response = await self._client.get(Route.Prices.PRODUCTS, params={"q": q})
|
|
139
|
+
return _parse(response, ProductsResponse)
|
|
140
|
+
|
|
141
|
+
async def offers(self, params: OffersRequestParams) -> OffersResponse:
|
|
142
|
+
"""List marketplace offers matching the given filters.
|
|
143
|
+
|
|
144
|
+
The same request can be made at most once every 5 minutes.
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
params: Offer filters. Only `status` is required.
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
The matching offers.
|
|
151
|
+
|
|
152
|
+
Raises:
|
|
153
|
+
APIError: If the API returns an error.
|
|
154
|
+
|
|
155
|
+
"""
|
|
156
|
+
response = await self._client.get(
|
|
157
|
+
Route.Marketplace.OFFERS,
|
|
158
|
+
params=params.model_dump(mode="json", by_alias=True, exclude_none=True),
|
|
159
|
+
)
|
|
160
|
+
return _parse(response, OffersResponse)
|
|
161
|
+
|
|
162
|
+
async def offer_details(self, offer_id: str) -> OfferDetailsResponse:
|
|
163
|
+
"""Get the full details of one marketplace offer.
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
offer_id: The offer ID. Surrounding whitespace is stripped.
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
The offer, including the buyer's shipping details once it has sold.
|
|
170
|
+
|
|
171
|
+
Raises:
|
|
172
|
+
ParameterError: If `offer_id` is empty or only whitespace.
|
|
173
|
+
APIError: If the API returns an error.
|
|
174
|
+
|
|
175
|
+
"""
|
|
176
|
+
offer_id = offer_id.strip()
|
|
177
|
+
|
|
178
|
+
if len(offer_id) == 0:
|
|
179
|
+
raise ParameterError("Invalid parameter; offer_id was empty.")
|
|
180
|
+
|
|
181
|
+
response = await self._client.get(
|
|
182
|
+
Route.Marketplace.OFFER_DETAILS, params={"offer-id": offer_id}
|
|
183
|
+
)
|
|
184
|
+
return _parse(response, OfferDetailsResponse)
|
|
185
|
+
|
|
186
|
+
async def offer_publish(self, params: OfferPublishRequestParams) -> OfferPublishResponse:
|
|
187
|
+
"""Publish a new marketplace offer, or edit an existing one.
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
params: The offer to publish. Set `offer_id` to edit an existing offer.
|
|
191
|
+
|
|
192
|
+
Returns:
|
|
193
|
+
The published offer.
|
|
194
|
+
|
|
195
|
+
Raises:
|
|
196
|
+
APIError: If the API returns an error.
|
|
197
|
+
|
|
198
|
+
"""
|
|
199
|
+
response = await self._client.post(
|
|
200
|
+
Route.Marketplace.OFFER_PUBLISH,
|
|
201
|
+
data=params.model_dump(mode="json", by_alias=True, exclude_none=True),
|
|
202
|
+
)
|
|
203
|
+
return _parse(response, OfferPublishResponse)
|
|
204
|
+
|
|
205
|
+
async def offer_feedback(
|
|
206
|
+
self, offer_id: str, rating: Rating, comment: str | None = None
|
|
207
|
+
) -> OfferFeedbackResponse:
|
|
208
|
+
"""Leave feedback on a marketplace offer.
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
offer_id: The offer ID. Surrounding whitespace is stripped.
|
|
212
|
+
rating: How the transaction went.
|
|
213
|
+
comment: Text explaining the rating.
|
|
214
|
+
|
|
215
|
+
Returns:
|
|
216
|
+
The feedback left.
|
|
217
|
+
|
|
218
|
+
Raises:
|
|
219
|
+
ParameterError: If `offer_id` is empty or only whitespace.
|
|
220
|
+
APIError: If the API returns an error.
|
|
221
|
+
|
|
222
|
+
"""
|
|
223
|
+
offer_id = offer_id.strip()
|
|
224
|
+
|
|
225
|
+
if len(offer_id) == 0:
|
|
226
|
+
raise ParameterError("Invalid parameter; offer_id was empty.")
|
|
227
|
+
|
|
228
|
+
data: dict[str, str | int] = {"offer-id": offer_id, "rating": rating.value}
|
|
229
|
+
if comment is not None:
|
|
230
|
+
data["comment"] = comment
|
|
231
|
+
|
|
232
|
+
response = await self._client.post(Route.Marketplace.OFFER_FEEDBACK, data=data)
|
|
233
|
+
return _parse(response, OfferFeedbackResponse)
|
|
234
|
+
|
|
235
|
+
async def offer_ship(
|
|
236
|
+
self, offer_id: str, tracking_number: str | None = None
|
|
237
|
+
) -> OfferShipResponse:
|
|
238
|
+
"""Mark a sold marketplace offer as shipped.
|
|
239
|
+
|
|
240
|
+
Args:
|
|
241
|
+
offer_id: The offer ID. Surrounding whitespace is stripped.
|
|
242
|
+
tracking_number: USPS, FedEx, UPS, or other tracking number. A valid one can earn
|
|
243
|
+
positive feedback if the buyer leaves none.
|
|
244
|
+
|
|
245
|
+
Returns:
|
|
246
|
+
The shipped offer.
|
|
247
|
+
|
|
248
|
+
Raises:
|
|
249
|
+
ParameterError: If `offer_id` is empty or only whitespace.
|
|
250
|
+
APIError: If the API returns an error.
|
|
251
|
+
|
|
252
|
+
"""
|
|
253
|
+
offer_id = offer_id.strip()
|
|
254
|
+
|
|
255
|
+
if len(offer_id) == 0:
|
|
256
|
+
raise ParameterError("Invalid parameter; offer_id was empty.")
|
|
257
|
+
|
|
258
|
+
data = {"offer-id": offer_id}
|
|
259
|
+
if tracking_number is not None:
|
|
260
|
+
data["tracking-number"] = tracking_number
|
|
261
|
+
|
|
262
|
+
response = await self._client.post(Route.Marketplace.OFFER_SHIP, data=data)
|
|
263
|
+
return _parse(response, OfferShipResponse)
|
|
264
|
+
|
|
265
|
+
async def offer_end(self, offer_id: str) -> OfferEndResponse:
|
|
266
|
+
"""End a marketplace offer so it can no longer be purchased.
|
|
267
|
+
|
|
268
|
+
For a collection item, this removes it from the collection.
|
|
269
|
+
|
|
270
|
+
Args:
|
|
271
|
+
offer_id: The offer ID. Surrounding whitespace is stripped.
|
|
272
|
+
|
|
273
|
+
Returns:
|
|
274
|
+
The ended offer.
|
|
275
|
+
|
|
276
|
+
Raises:
|
|
277
|
+
ParameterError: If `offer_id` is empty or only whitespace.
|
|
278
|
+
APIError: If the API returns an error.
|
|
279
|
+
|
|
280
|
+
"""
|
|
281
|
+
offer_id = offer_id.strip()
|
|
282
|
+
|
|
283
|
+
if len(offer_id) == 0:
|
|
284
|
+
raise ParameterError("Invalid parameter; offer_id was empty.")
|
|
285
|
+
|
|
286
|
+
response = await self._client.post(Route.Marketplace.OFFER_END, data={"offer-id": offer_id})
|
|
287
|
+
return _parse(response, OfferEndResponse)
|
|
288
|
+
|
|
289
|
+
async def offer_refund(self, offer_id: str) -> OfferRefundResponse:
|
|
290
|
+
"""Refund a purchased marketplace offer.
|
|
291
|
+
|
|
292
|
+
Payment is returned immediately from the seller's PayPal account to the buyer.
|
|
293
|
+
|
|
294
|
+
Args:
|
|
295
|
+
offer_id: The offer ID. Surrounding whitespace is stripped.
|
|
296
|
+
|
|
297
|
+
Returns:
|
|
298
|
+
The refunded offer.
|
|
299
|
+
|
|
300
|
+
Raises:
|
|
301
|
+
ParameterError: If `offer_id` is empty or only whitespace.
|
|
302
|
+
APIError: If the API returns an error.
|
|
303
|
+
|
|
304
|
+
"""
|
|
305
|
+
offer_id = offer_id.strip()
|
|
306
|
+
|
|
307
|
+
if len(offer_id) == 0:
|
|
308
|
+
raise ParameterError("Invalid parameter; offer_id was empty.")
|
|
309
|
+
|
|
310
|
+
response = await self._client.post(
|
|
311
|
+
Route.Marketplace.OFFER_REFUND, data={"offer-id": offer_id}
|
|
312
|
+
)
|
|
313
|
+
return _parse(response, OfferRefundResponse)
|
pypricecharting/error.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
class PriceChartingError(Exception):
|
|
2
|
+
"""Base class for all PriceCharting errors"""
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class ParameterError(PriceChartingError, ValueError):
|
|
6
|
+
"""Raised when an API method is called with invalid parameters
|
|
7
|
+
|
|
8
|
+
Raised before any request is sent.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class APIError(PriceChartingError):
|
|
13
|
+
"""Raised when the API returns an error response
|
|
14
|
+
|
|
15
|
+
Attributes:
|
|
16
|
+
message: The API's `error-message`, or the HTTP reason phrase if it has none.
|
|
17
|
+
status_code: The HTTP status code of the response.
|
|
18
|
+
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(self, message: str, status_code: int):
|
|
22
|
+
"""Store the API error message and HTTP status code."""
|
|
23
|
+
super().__init__(f"{status_code}: {message}")
|
|
24
|
+
self.message = message
|
|
25
|
+
self.status_code = status_code
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
from datetime import date
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, ConfigDict
|
|
4
|
+
|
|
5
|
+
from pypricecharting.types import OfferStatus
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def to_kebab(name: str) -> str:
|
|
9
|
+
"""Convert a snake_case field name to the API's kebab-case key."""
|
|
10
|
+
return name.replace("_", "-")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class PriceChartingModel(BaseModel):
|
|
14
|
+
"""Base model that maps snake_case fields to the API's kebab-case keys."""
|
|
15
|
+
|
|
16
|
+
model_config = ConfigDict(
|
|
17
|
+
alias_generator=to_kebab,
|
|
18
|
+
populate_by_name=True,
|
|
19
|
+
use_attribute_docstrings=True,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ProductSummary(PriceChartingModel):
|
|
24
|
+
"""A product as listed in /api/products search results.
|
|
25
|
+
|
|
26
|
+
https://www.pricecharting.com/api-documentation#api-products
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
id: str | None = None
|
|
30
|
+
"""PriceCharting product ID."""
|
|
31
|
+
product_name: str | None = None
|
|
32
|
+
console_name: str | None = None
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class Offer(PriceChartingModel):
|
|
36
|
+
"""An offer as listed in /api/offers results.
|
|
37
|
+
|
|
38
|
+
https://www.pricecharting.com/api-documentation#offers
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
offer_id: str | None = None
|
|
42
|
+
"""Unique offer ID, also shown in the offer URL."""
|
|
43
|
+
offer_status: OfferStatus | None = None
|
|
44
|
+
offer_url: str | None = None
|
|
45
|
+
"""Relative URL of the offer page. Prepend `route.BASE_URL` for a full link."""
|
|
46
|
+
id: int | None = None
|
|
47
|
+
"""PriceCharting product ID, not the offer ID."""
|
|
48
|
+
product_name: str | None = None
|
|
49
|
+
console_name: str | None = None
|
|
50
|
+
condition_string: str | None = None
|
|
51
|
+
"""Condition shown to buyers, such as "Normal Wear"."""
|
|
52
|
+
include_string: str | None = None
|
|
53
|
+
"""What comes with the item, such as game only or game and box."""
|
|
54
|
+
price: int | None = None
|
|
55
|
+
"""Current price in pennies, or the sale price if sold. Includes any shipping premium."""
|
|
56
|
+
is_available: bool | None = None
|
|
57
|
+
is_sold: bool | None = None
|
|
58
|
+
is_ended: bool | None = None
|
|
59
|
+
is_shipped: bool | None = None
|
|
60
|
+
sale_time: date | None = None
|
|
61
|
+
"""Date the offer sold. `0001-01-01` if it hasn't happened."""
|
|
62
|
+
shipped_time: date | None = None
|
|
63
|
+
"""Date the seller told PriceCharting the offer shipped. `0001-01-01` if it hasn't happened."""
|
|
64
|
+
ended_time: date | None = None
|
|
65
|
+
"""Date the offer ended, by sale or by the seller. `0001-01-01` if it hasn't happened."""
|
|
66
|
+
cert_id: str | None = None
|
|
67
|
+
"""Grading company certificate number. Graded offers only."""
|
|
68
|
+
grade_co_id: int | None = None
|
|
69
|
+
"""ID of the grading company. Graded offers only."""
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class Buyer(PriceChartingModel):
|
|
73
|
+
"""The buyer of a sold offer, as returned by /api/offer-details."""
|
|
74
|
+
|
|
75
|
+
buyer_email: str | None = None
|
|
76
|
+
shipping_name: str | None = None
|
|
77
|
+
shipping_line1: str | None = None
|
|
78
|
+
shipping_line2: str | None = None
|
|
79
|
+
shipping_city: str | None = None
|
|
80
|
+
shipping_state: str | None = None
|
|
81
|
+
shipping_zip: str | None = None
|
|
82
|
+
shipping_country: str | None = None
|