tcgapi 0.2.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.
- tcgapi/__init__.py +68 -0
- tcgapi/_transport.py +66 -0
- tcgapi/async_client.py +75 -0
- tcgapi/client.py +79 -0
- tcgapi/errors.py +78 -0
- tcgapi/models.py +211 -0
- tcgapi/resources/__init__.py +0 -0
- tcgapi/resources/_base.py +27 -0
- tcgapi/resources/bulk.py +140 -0
- tcgapi/resources/cards.py +88 -0
- tcgapi/resources/export.py +36 -0
- tcgapi/resources/games.py +48 -0
- tcgapi/resources/keys.py +30 -0
- tcgapi/resources/prices.py +74 -0
- tcgapi/resources/search.py +118 -0
- tcgapi/resources/sets.py +115 -0
- tcgapi/resources/usage.py +30 -0
- tcgapi-0.2.1.dist-info/METADATA +214 -0
- tcgapi-0.2.1.dist-info/RECORD +21 -0
- tcgapi-0.2.1.dist-info/WHEEL +4 -0
- tcgapi-0.2.1.dist-info/licenses/LICENSE +21 -0
tcgapi/resources/bulk.py
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""BulkResource — /bulk/* endpoints. Auto-chunks oversized lists."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING, Literal
|
|
6
|
+
|
|
7
|
+
from ..models import BulkCard, BulkPriceRow, PriceHistoryPoint, Response
|
|
8
|
+
from ._base import parse_response
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from ..async_client import AsyncTCGApi
|
|
12
|
+
from ..client import TCGApi
|
|
13
|
+
|
|
14
|
+
BULK_PRICES_MAX = 500
|
|
15
|
+
BULK_CARDS_MAX = 100
|
|
16
|
+
BULK_HISTORY_MAX = 50
|
|
17
|
+
|
|
18
|
+
RangeLiteral = Literal["month", "quarter", "year", "all"]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _ids(ids: list[int]) -> str:
|
|
22
|
+
if not ids:
|
|
23
|
+
raise ValueError("At least one card ID required")
|
|
24
|
+
return ",".join(str(i) for i in ids)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class BulkResource:
|
|
28
|
+
def __init__(self, client: "TCGApi") -> None:
|
|
29
|
+
self._client = client
|
|
30
|
+
|
|
31
|
+
def prices(self, ids: list[int]) -> Response[list[BulkPriceRow]]:
|
|
32
|
+
if len(ids) <= BULK_PRICES_MAX:
|
|
33
|
+
body = self._client._request("GET", "/bulk/prices", {"ids": _ids(ids)})
|
|
34
|
+
return parse_response(list[BulkPriceRow], body)
|
|
35
|
+
rows: list[BulkPriceRow] = []
|
|
36
|
+
last: Response[list[BulkPriceRow]] | None = None
|
|
37
|
+
for i in range(0, len(ids), BULK_PRICES_MAX):
|
|
38
|
+
chunk = ids[i : i + BULK_PRICES_MAX]
|
|
39
|
+
body = self._client._request("GET", "/bulk/prices", {"ids": _ids(chunk)})
|
|
40
|
+
resp: Response[list[BulkPriceRow]] = parse_response(list[BulkPriceRow], body)
|
|
41
|
+
rows.extend(resp.data)
|
|
42
|
+
last = resp
|
|
43
|
+
return Response[list[BulkPriceRow]](data=rows, rate_limit=last.rate_limit if last else None)
|
|
44
|
+
|
|
45
|
+
def cards(self, ids: list[int]) -> Response[list[BulkCard]]:
|
|
46
|
+
if len(ids) <= BULK_CARDS_MAX:
|
|
47
|
+
body = self._client._request("GET", "/bulk/cards", {"ids": _ids(ids)})
|
|
48
|
+
return parse_response(list[BulkCard], body)
|
|
49
|
+
rows: list[BulkCard] = []
|
|
50
|
+
last: Response[list[BulkCard]] | None = None
|
|
51
|
+
for i in range(0, len(ids), BULK_CARDS_MAX):
|
|
52
|
+
chunk = ids[i : i + BULK_CARDS_MAX]
|
|
53
|
+
body = self._client._request("GET", "/bulk/cards", {"ids": _ids(chunk)})
|
|
54
|
+
resp: Response[list[BulkCard]] = parse_response(list[BulkCard], body)
|
|
55
|
+
rows.extend(resp.data)
|
|
56
|
+
last = resp
|
|
57
|
+
return Response[list[BulkCard]](data=rows, rate_limit=last.rate_limit if last else None)
|
|
58
|
+
|
|
59
|
+
def history(
|
|
60
|
+
self,
|
|
61
|
+
ids: list[int],
|
|
62
|
+
*,
|
|
63
|
+
range: RangeLiteral | None = None,
|
|
64
|
+
printing: str | None = None,
|
|
65
|
+
) -> Response[dict[str, list[PriceHistoryPoint]]]:
|
|
66
|
+
params: dict[str, object | None] = {"range": range, "printing": printing}
|
|
67
|
+
if len(ids) <= BULK_HISTORY_MAX:
|
|
68
|
+
body = self._client._request("GET", "/bulk/history", {**params, "ids": _ids(ids)})
|
|
69
|
+
return parse_response(dict[str, list[PriceHistoryPoint]], body)
|
|
70
|
+
merged: dict[str, list[PriceHistoryPoint]] = {}
|
|
71
|
+
last: Response[dict[str, list[PriceHistoryPoint]]] | None = None
|
|
72
|
+
for i in range(0, len(ids), BULK_HISTORY_MAX):
|
|
73
|
+
chunk = ids[i : i + BULK_HISTORY_MAX]
|
|
74
|
+
body = self._client._request("GET", "/bulk/history", {**params, "ids": _ids(chunk)})
|
|
75
|
+
resp: Response[dict[str, list[PriceHistoryPoint]]] = parse_response(
|
|
76
|
+
dict[str, list[PriceHistoryPoint]], body
|
|
77
|
+
)
|
|
78
|
+
merged.update(resp.data)
|
|
79
|
+
last = resp
|
|
80
|
+
return Response[dict[str, list[PriceHistoryPoint]]](
|
|
81
|
+
data=merged, rate_limit=last.rate_limit if last else None
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class AsyncBulkResource:
|
|
86
|
+
def __init__(self, client: "AsyncTCGApi") -> None:
|
|
87
|
+
self._client = client
|
|
88
|
+
|
|
89
|
+
async def prices(self, ids: list[int]) -> Response[list[BulkPriceRow]]:
|
|
90
|
+
if len(ids) <= BULK_PRICES_MAX:
|
|
91
|
+
body = await self._client._request("GET", "/bulk/prices", {"ids": _ids(ids)})
|
|
92
|
+
return parse_response(list[BulkPriceRow], body)
|
|
93
|
+
rows: list[BulkPriceRow] = []
|
|
94
|
+
last: Response[list[BulkPriceRow]] | None = None
|
|
95
|
+
for i in range(0, len(ids), BULK_PRICES_MAX):
|
|
96
|
+
chunk = ids[i : i + BULK_PRICES_MAX]
|
|
97
|
+
body = await self._client._request("GET", "/bulk/prices", {"ids": _ids(chunk)})
|
|
98
|
+
resp: Response[list[BulkPriceRow]] = parse_response(list[BulkPriceRow], body)
|
|
99
|
+
rows.extend(resp.data)
|
|
100
|
+
last = resp
|
|
101
|
+
return Response[list[BulkPriceRow]](data=rows, rate_limit=last.rate_limit if last else None)
|
|
102
|
+
|
|
103
|
+
async def cards(self, ids: list[int]) -> Response[list[BulkCard]]:
|
|
104
|
+
if len(ids) <= BULK_CARDS_MAX:
|
|
105
|
+
body = await self._client._request("GET", "/bulk/cards", {"ids": _ids(ids)})
|
|
106
|
+
return parse_response(list[BulkCard], body)
|
|
107
|
+
rows: list[BulkCard] = []
|
|
108
|
+
last: Response[list[BulkCard]] | None = None
|
|
109
|
+
for i in range(0, len(ids), BULK_CARDS_MAX):
|
|
110
|
+
chunk = ids[i : i + BULK_CARDS_MAX]
|
|
111
|
+
body = await self._client._request("GET", "/bulk/cards", {"ids": _ids(chunk)})
|
|
112
|
+
resp: Response[list[BulkCard]] = parse_response(list[BulkCard], body)
|
|
113
|
+
rows.extend(resp.data)
|
|
114
|
+
last = resp
|
|
115
|
+
return Response[list[BulkCard]](data=rows, rate_limit=last.rate_limit if last else None)
|
|
116
|
+
|
|
117
|
+
async def history(
|
|
118
|
+
self,
|
|
119
|
+
ids: list[int],
|
|
120
|
+
*,
|
|
121
|
+
range: RangeLiteral | None = None,
|
|
122
|
+
printing: str | None = None,
|
|
123
|
+
) -> Response[dict[str, list[PriceHistoryPoint]]]:
|
|
124
|
+
params: dict[str, object | None] = {"range": range, "printing": printing}
|
|
125
|
+
if len(ids) <= BULK_HISTORY_MAX:
|
|
126
|
+
body = await self._client._request("GET", "/bulk/history", {**params, "ids": _ids(ids)})
|
|
127
|
+
return parse_response(dict[str, list[PriceHistoryPoint]], body)
|
|
128
|
+
merged: dict[str, list[PriceHistoryPoint]] = {}
|
|
129
|
+
last: Response[dict[str, list[PriceHistoryPoint]]] | None = None
|
|
130
|
+
for i in range(0, len(ids), BULK_HISTORY_MAX):
|
|
131
|
+
chunk = ids[i : i + BULK_HISTORY_MAX]
|
|
132
|
+
body = await self._client._request("GET", "/bulk/history", {**params, "ids": _ids(chunk)})
|
|
133
|
+
resp: Response[dict[str, list[PriceHistoryPoint]]] = parse_response(
|
|
134
|
+
dict[str, list[PriceHistoryPoint]], body
|
|
135
|
+
)
|
|
136
|
+
merged.update(resp.data)
|
|
137
|
+
last = resp
|
|
138
|
+
return Response[dict[str, list[PriceHistoryPoint]]](
|
|
139
|
+
data=merged, rate_limit=last.rate_limit if last else None
|
|
140
|
+
)
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""CardsResource — /cards endpoints."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING, Literal
|
|
6
|
+
|
|
7
|
+
from ..models import BulkCard, Card, Price, PriceHistoryPoint, Response
|
|
8
|
+
from ._base import parse_response
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from ..async_client import AsyncTCGApi
|
|
12
|
+
from ..client import TCGApi
|
|
13
|
+
|
|
14
|
+
RangeLiteral = Literal["month", "quarter", "year", "all"]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class CardsResource:
|
|
18
|
+
def __init__(self, client: "TCGApi") -> None:
|
|
19
|
+
self._client = client
|
|
20
|
+
|
|
21
|
+
def get(self, card_id: int) -> Response[Card]:
|
|
22
|
+
body = self._client._request("GET", f"/cards/{card_id}")
|
|
23
|
+
return parse_response(Card, body)
|
|
24
|
+
|
|
25
|
+
def prices(self, card_id: int, *, printing: str | None = None) -> Response[list[Price]]:
|
|
26
|
+
body = self._client._request("GET", f"/cards/{card_id}/prices", {"printing": printing})
|
|
27
|
+
return parse_response(list[Price], body)
|
|
28
|
+
|
|
29
|
+
def by_tcgplayer_id(self, tcgplayer_id: int) -> Response[BulkCard]:
|
|
30
|
+
body = self._client._request("GET", f"/cards/tcgplayer/{tcgplayer_id}")
|
|
31
|
+
return parse_response(BulkCard, body)
|
|
32
|
+
|
|
33
|
+
def history(
|
|
34
|
+
self,
|
|
35
|
+
card_id: int,
|
|
36
|
+
*,
|
|
37
|
+
range: RangeLiteral | None = None,
|
|
38
|
+
printing: str | None = None,
|
|
39
|
+
) -> Response[list[PriceHistoryPoint]]:
|
|
40
|
+
body = self._client._request(
|
|
41
|
+
"GET", f"/cards/{card_id}/history", {"range": range, "printing": printing}
|
|
42
|
+
)
|
|
43
|
+
return parse_response(list[PriceHistoryPoint], body)
|
|
44
|
+
|
|
45
|
+
def history_detailed(
|
|
46
|
+
self, card_id: int, *, printing: str | None = None
|
|
47
|
+
) -> Response[list[PriceHistoryPoint]]:
|
|
48
|
+
body = self._client._request(
|
|
49
|
+
"GET", f"/cards/{card_id}/history/detailed", {"printing": printing}
|
|
50
|
+
)
|
|
51
|
+
return parse_response(list[PriceHistoryPoint], body)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class AsyncCardsResource:
|
|
55
|
+
def __init__(self, client: "AsyncTCGApi") -> None:
|
|
56
|
+
self._client = client
|
|
57
|
+
|
|
58
|
+
async def get(self, card_id: int) -> Response[Card]:
|
|
59
|
+
body = await self._client._request("GET", f"/cards/{card_id}")
|
|
60
|
+
return parse_response(Card, body)
|
|
61
|
+
|
|
62
|
+
async def prices(self, card_id: int, *, printing: str | None = None) -> Response[list[Price]]:
|
|
63
|
+
body = await self._client._request("GET", f"/cards/{card_id}/prices", {"printing": printing})
|
|
64
|
+
return parse_response(list[Price], body)
|
|
65
|
+
|
|
66
|
+
async def by_tcgplayer_id(self, tcgplayer_id: int) -> Response[BulkCard]:
|
|
67
|
+
body = await self._client._request("GET", f"/cards/tcgplayer/{tcgplayer_id}")
|
|
68
|
+
return parse_response(BulkCard, body)
|
|
69
|
+
|
|
70
|
+
async def history(
|
|
71
|
+
self,
|
|
72
|
+
card_id: int,
|
|
73
|
+
*,
|
|
74
|
+
range: RangeLiteral | None = None,
|
|
75
|
+
printing: str | None = None,
|
|
76
|
+
) -> Response[list[PriceHistoryPoint]]:
|
|
77
|
+
body = await self._client._request(
|
|
78
|
+
"GET", f"/cards/{card_id}/history", {"range": range, "printing": printing}
|
|
79
|
+
)
|
|
80
|
+
return parse_response(list[PriceHistoryPoint], body)
|
|
81
|
+
|
|
82
|
+
async def history_detailed(
|
|
83
|
+
self, card_id: int, *, printing: str | None = None
|
|
84
|
+
) -> Response[list[PriceHistoryPoint]]:
|
|
85
|
+
body = await self._client._request(
|
|
86
|
+
"GET", f"/cards/{card_id}/history/detailed", {"printing": printing}
|
|
87
|
+
)
|
|
88
|
+
return parse_response(list[PriceHistoryPoint], body)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""ExportResource — /export/set/{id}."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
from ..models import BulkPriceRow, Response
|
|
8
|
+
from ._base import parse_response
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from ..async_client import AsyncTCGApi
|
|
12
|
+
from ..client import TCGApi
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ExportResource:
|
|
16
|
+
def __init__(self, client: "TCGApi") -> None:
|
|
17
|
+
self._client = client
|
|
18
|
+
|
|
19
|
+
def set_json(self, set_id: int) -> Response[list[BulkPriceRow]]:
|
|
20
|
+
body = self._client._request("GET", f"/export/set/{set_id}", {"format": "json"})
|
|
21
|
+
return parse_response(list[BulkPriceRow], body)
|
|
22
|
+
|
|
23
|
+
def set_csv(self, set_id: int) -> str:
|
|
24
|
+
return self._client._request_raw("GET", f"/export/set/{set_id}", {"format": "csv"})
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class AsyncExportResource:
|
|
28
|
+
def __init__(self, client: "AsyncTCGApi") -> None:
|
|
29
|
+
self._client = client
|
|
30
|
+
|
|
31
|
+
async def set_json(self, set_id: int) -> Response[list[BulkPriceRow]]:
|
|
32
|
+
body = await self._client._request("GET", f"/export/set/{set_id}", {"format": "json"})
|
|
33
|
+
return parse_response(list[BulkPriceRow], body)
|
|
34
|
+
|
|
35
|
+
async def set_csv(self, set_id: int) -> str:
|
|
36
|
+
return await self._client._request_raw("GET", f"/export/set/{set_id}", {"format": "csv"})
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""GamesResource — /games endpoints."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
from ..models import Game, Response, Set
|
|
8
|
+
from ._base import parse_response
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from ..async_client import AsyncTCGApi
|
|
12
|
+
from ..client import TCGApi
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class GamesResource:
|
|
16
|
+
def __init__(self, client: "TCGApi") -> None:
|
|
17
|
+
self._client = client
|
|
18
|
+
|
|
19
|
+
def list(self, *, page: int | None = None, per_page: int | None = None) -> Response[list[Game]]:
|
|
20
|
+
body = self._client._request("GET", "/games", {"page": page, "per_page": per_page})
|
|
21
|
+
return parse_response(list[Game], body)
|
|
22
|
+
|
|
23
|
+
def get(self, slug: str | int) -> Response[Game]:
|
|
24
|
+
body = self._client._request("GET", f"/games/{slug}")
|
|
25
|
+
return parse_response(Game, body)
|
|
26
|
+
|
|
27
|
+
def sets(self, slug: str | int, *, page: int | None = None, per_page: int | None = None) -> Response[list[Set]]:
|
|
28
|
+
body = self._client._request("GET", f"/games/{slug}/sets", {"page": page, "per_page": per_page})
|
|
29
|
+
return parse_response(list[Set], body)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class AsyncGamesResource:
|
|
33
|
+
def __init__(self, client: "AsyncTCGApi") -> None:
|
|
34
|
+
self._client = client
|
|
35
|
+
|
|
36
|
+
async def list(self, *, page: int | None = None, per_page: int | None = None) -> Response[list[Game]]:
|
|
37
|
+
body = await self._client._request("GET", "/games", {"page": page, "per_page": per_page})
|
|
38
|
+
return parse_response(list[Game], body)
|
|
39
|
+
|
|
40
|
+
async def get(self, slug: str | int) -> Response[Game]:
|
|
41
|
+
body = await self._client._request("GET", f"/games/{slug}")
|
|
42
|
+
return parse_response(Game, body)
|
|
43
|
+
|
|
44
|
+
async def sets(
|
|
45
|
+
self, slug: str | int, *, page: int | None = None, per_page: int | None = None
|
|
46
|
+
) -> Response[list[Set]]:
|
|
47
|
+
body = await self._client._request("GET", f"/games/{slug}/sets", {"page": page, "per_page": per_page})
|
|
48
|
+
return parse_response(list[Set], body)
|
tcgapi/resources/keys.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""KeysResource — /keys (session-cookie auth, mostly used from the dashboard)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
from ..models import ApiKeySummary, Response
|
|
8
|
+
from ._base import parse_response
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from ..async_client import AsyncTCGApi
|
|
12
|
+
from ..client import TCGApi
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class KeysResource:
|
|
16
|
+
def __init__(self, client: "TCGApi") -> None:
|
|
17
|
+
self._client = client
|
|
18
|
+
|
|
19
|
+
def list(self) -> Response[list[ApiKeySummary]]:
|
|
20
|
+
body = self._client._request("GET", "/keys")
|
|
21
|
+
return parse_response(list[ApiKeySummary], body)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class AsyncKeysResource:
|
|
25
|
+
def __init__(self, client: "AsyncTCGApi") -> None:
|
|
26
|
+
self._client = client
|
|
27
|
+
|
|
28
|
+
async def list(self) -> Response[list[ApiKeySummary]]:
|
|
29
|
+
body = await self._client._request("GET", "/keys")
|
|
30
|
+
return parse_response(list[ApiKeySummary], body)
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""PricesResource — /prices/top-movers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING, Literal
|
|
6
|
+
|
|
7
|
+
from ..models import PriceMover, Response
|
|
8
|
+
from ._base import parse_response
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from ..async_client import AsyncTCGApi
|
|
12
|
+
from ..client import TCGApi
|
|
13
|
+
|
|
14
|
+
DirectionLiteral = Literal["up", "down"]
|
|
15
|
+
PeriodLiteral = Literal["24h", "7d", "30d"]
|
|
16
|
+
TypeLiteral = Literal["Cards", "Sealed Products"]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class PricesResource:
|
|
20
|
+
def __init__(self, client: "TCGApi") -> None:
|
|
21
|
+
self._client = client
|
|
22
|
+
|
|
23
|
+
def top_movers(
|
|
24
|
+
self,
|
|
25
|
+
*,
|
|
26
|
+
game: str | None = None,
|
|
27
|
+
direction: DirectionLiteral | None = None,
|
|
28
|
+
period: PeriodLiteral | None = None,
|
|
29
|
+
printing: str | None = None,
|
|
30
|
+
type: TypeLiteral | None = None,
|
|
31
|
+
limit: int | None = None,
|
|
32
|
+
) -> Response[list[PriceMover]]:
|
|
33
|
+
body = self._client._request(
|
|
34
|
+
"GET",
|
|
35
|
+
"/prices/top-movers",
|
|
36
|
+
{
|
|
37
|
+
"game": game,
|
|
38
|
+
"direction": direction,
|
|
39
|
+
"period": period,
|
|
40
|
+
"printing": printing,
|
|
41
|
+
"type": type,
|
|
42
|
+
"limit": limit,
|
|
43
|
+
},
|
|
44
|
+
)
|
|
45
|
+
return parse_response(list[PriceMover], body)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class AsyncPricesResource:
|
|
49
|
+
def __init__(self, client: "AsyncTCGApi") -> None:
|
|
50
|
+
self._client = client
|
|
51
|
+
|
|
52
|
+
async def top_movers(
|
|
53
|
+
self,
|
|
54
|
+
*,
|
|
55
|
+
game: str | None = None,
|
|
56
|
+
direction: DirectionLiteral | None = None,
|
|
57
|
+
period: PeriodLiteral | None = None,
|
|
58
|
+
printing: str | None = None,
|
|
59
|
+
type: TypeLiteral | None = None,
|
|
60
|
+
limit: int | None = None,
|
|
61
|
+
) -> Response[list[PriceMover]]:
|
|
62
|
+
body = await self._client._request(
|
|
63
|
+
"GET",
|
|
64
|
+
"/prices/top-movers",
|
|
65
|
+
{
|
|
66
|
+
"game": game,
|
|
67
|
+
"direction": direction,
|
|
68
|
+
"period": period,
|
|
69
|
+
"printing": printing,
|
|
70
|
+
"type": type,
|
|
71
|
+
"limit": limit,
|
|
72
|
+
},
|
|
73
|
+
)
|
|
74
|
+
return parse_response(list[PriceMover], body)
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"""SearchResource — /search."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING, AsyncIterator, Iterator, Literal
|
|
6
|
+
|
|
7
|
+
from ..models import CardWithPrice, Response
|
|
8
|
+
from ._base import parse_response
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from ..async_client import AsyncTCGApi
|
|
12
|
+
from ..client import TCGApi
|
|
13
|
+
|
|
14
|
+
SortLiteral = Literal["relevance", "price_asc", "price_desc", "name"]
|
|
15
|
+
TypeLiteral = Literal["Cards", "Sealed Products"]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _params(
|
|
19
|
+
q: str,
|
|
20
|
+
game: str | None,
|
|
21
|
+
set_id: int | None,
|
|
22
|
+
rarity: str | None,
|
|
23
|
+
type: TypeLiteral | None,
|
|
24
|
+
printing: str | None,
|
|
25
|
+
min_price: float | None,
|
|
26
|
+
max_price: float | None,
|
|
27
|
+
sort: SortLiteral | None,
|
|
28
|
+
page: int | None,
|
|
29
|
+
per_page: int | None,
|
|
30
|
+
) -> dict[str, object | None]:
|
|
31
|
+
return {
|
|
32
|
+
"q": q,
|
|
33
|
+
"game": game,
|
|
34
|
+
"set_id": set_id,
|
|
35
|
+
"rarity": rarity,
|
|
36
|
+
"type": type,
|
|
37
|
+
"printing": printing,
|
|
38
|
+
"min_price": min_price,
|
|
39
|
+
"max_price": max_price,
|
|
40
|
+
"sort": sort,
|
|
41
|
+
"page": page,
|
|
42
|
+
"per_page": per_page,
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class SearchResource:
|
|
47
|
+
def __init__(self, client: "TCGApi") -> None:
|
|
48
|
+
self._client = client
|
|
49
|
+
|
|
50
|
+
def cards(
|
|
51
|
+
self,
|
|
52
|
+
q: str,
|
|
53
|
+
*,
|
|
54
|
+
game: str | None = None,
|
|
55
|
+
set_id: int | None = None,
|
|
56
|
+
rarity: str | None = None,
|
|
57
|
+
type: TypeLiteral | None = None,
|
|
58
|
+
printing: str | None = None,
|
|
59
|
+
min_price: float | None = None,
|
|
60
|
+
max_price: float | None = None,
|
|
61
|
+
sort: SortLiteral | None = None,
|
|
62
|
+
page: int | None = None,
|
|
63
|
+
per_page: int | None = None,
|
|
64
|
+
) -> Response[list[CardWithPrice]]:
|
|
65
|
+
body = self._client._request(
|
|
66
|
+
"GET",
|
|
67
|
+
"/search",
|
|
68
|
+
_params(q, game, set_id, rarity, type, printing, min_price, max_price, sort, page, per_page),
|
|
69
|
+
)
|
|
70
|
+
return parse_response(list[CardWithPrice], body)
|
|
71
|
+
|
|
72
|
+
def iter(self, q: str, **kwargs: object) -> Iterator[CardWithPrice]:
|
|
73
|
+
page = int(kwargs.pop("page", 1) or 1) # type: ignore[arg-type]
|
|
74
|
+
per_page = min(200, int(kwargs.pop("per_page", 200) or 200)) # type: ignore[arg-type]
|
|
75
|
+
while True:
|
|
76
|
+
resp = self.cards(q, page=page, per_page=per_page, **kwargs) # type: ignore[arg-type]
|
|
77
|
+
yield from resp.data
|
|
78
|
+
if not resp.meta or not resp.meta.has_more:
|
|
79
|
+
return
|
|
80
|
+
page += 1
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class AsyncSearchResource:
|
|
84
|
+
def __init__(self, client: "AsyncTCGApi") -> None:
|
|
85
|
+
self._client = client
|
|
86
|
+
|
|
87
|
+
async def cards(
|
|
88
|
+
self,
|
|
89
|
+
q: str,
|
|
90
|
+
*,
|
|
91
|
+
game: str | None = None,
|
|
92
|
+
set_id: int | None = None,
|
|
93
|
+
rarity: str | None = None,
|
|
94
|
+
type: TypeLiteral | None = None,
|
|
95
|
+
printing: str | None = None,
|
|
96
|
+
min_price: float | None = None,
|
|
97
|
+
max_price: float | None = None,
|
|
98
|
+
sort: SortLiteral | None = None,
|
|
99
|
+
page: int | None = None,
|
|
100
|
+
per_page: int | None = None,
|
|
101
|
+
) -> Response[list[CardWithPrice]]:
|
|
102
|
+
body = await self._client._request(
|
|
103
|
+
"GET",
|
|
104
|
+
"/search",
|
|
105
|
+
_params(q, game, set_id, rarity, type, printing, min_price, max_price, sort, page, per_page),
|
|
106
|
+
)
|
|
107
|
+
return parse_response(list[CardWithPrice], body)
|
|
108
|
+
|
|
109
|
+
async def iter(self, q: str, **kwargs: object) -> AsyncIterator[CardWithPrice]:
|
|
110
|
+
page = int(kwargs.pop("page", 1) or 1) # type: ignore[arg-type]
|
|
111
|
+
per_page = min(200, int(kwargs.pop("per_page", 200) or 200)) # type: ignore[arg-type]
|
|
112
|
+
while True:
|
|
113
|
+
resp = await self.cards(q, page=page, per_page=per_page, **kwargs) # type: ignore[arg-type]
|
|
114
|
+
for card in resp.data:
|
|
115
|
+
yield card
|
|
116
|
+
if not resp.meta or not resp.meta.has_more:
|
|
117
|
+
return
|
|
118
|
+
page += 1
|
tcgapi/resources/sets.py
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""SetsResource — /sets endpoints."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING, AsyncIterator, Iterator, Literal
|
|
6
|
+
|
|
7
|
+
from ..models import BulkPriceRow, CardWithPrice, Response, Set
|
|
8
|
+
from ._base import parse_response
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from ..async_client import AsyncTCGApi
|
|
12
|
+
from ..client import TCGApi
|
|
13
|
+
|
|
14
|
+
SortLiteral = Literal["number", "price_asc", "price_desc", "name"]
|
|
15
|
+
TypeLiteral = Literal["Cards", "Sealed Products"]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class SetsResource:
|
|
19
|
+
def __init__(self, client: "TCGApi") -> None:
|
|
20
|
+
self._client = client
|
|
21
|
+
|
|
22
|
+
def list(
|
|
23
|
+
self,
|
|
24
|
+
*,
|
|
25
|
+
game: str | None = None,
|
|
26
|
+
page: int | None = None,
|
|
27
|
+
per_page: int | None = None,
|
|
28
|
+
) -> Response[list[Set]]:
|
|
29
|
+
body = self._client._request("GET", "/sets", {"game": game, "page": page, "per_page": per_page})
|
|
30
|
+
return parse_response(list[Set], body)
|
|
31
|
+
|
|
32
|
+
def get(self, set_id: int) -> Response[Set]:
|
|
33
|
+
body = self._client._request("GET", f"/sets/{set_id}")
|
|
34
|
+
return parse_response(Set, body)
|
|
35
|
+
|
|
36
|
+
def cards(
|
|
37
|
+
self,
|
|
38
|
+
set_id: int,
|
|
39
|
+
*,
|
|
40
|
+
type: TypeLiteral | None = None,
|
|
41
|
+
sort: SortLiteral | None = None,
|
|
42
|
+
page: int | None = None,
|
|
43
|
+
per_page: int | None = None,
|
|
44
|
+
) -> Response[list[CardWithPrice]]:
|
|
45
|
+
body = self._client._request(
|
|
46
|
+
"GET",
|
|
47
|
+
f"/sets/{set_id}/cards",
|
|
48
|
+
{"type": type, "sort": sort, "page": page, "per_page": per_page},
|
|
49
|
+
)
|
|
50
|
+
return parse_response(list[CardWithPrice], body)
|
|
51
|
+
|
|
52
|
+
def prices(self, set_id: int) -> Response[list[BulkPriceRow]]:
|
|
53
|
+
body = self._client._request("GET", f"/sets/{set_id}/prices")
|
|
54
|
+
return parse_response(list[BulkPriceRow], body)
|
|
55
|
+
|
|
56
|
+
def iter_cards(self, set_id: int, **params: object) -> Iterator[CardWithPrice]:
|
|
57
|
+
"""Paginate through every card in a set, yielding one at a time."""
|
|
58
|
+
page = int(params.pop("page", 1) or 1) # type: ignore[arg-type]
|
|
59
|
+
per_page = min(200, int(params.pop("per_page", 200) or 200)) # type: ignore[arg-type]
|
|
60
|
+
while True:
|
|
61
|
+
resp = self.cards(set_id, page=page, per_page=per_page, **params) # type: ignore[arg-type]
|
|
62
|
+
yield from resp.data
|
|
63
|
+
if not resp.meta or not resp.meta.has_more:
|
|
64
|
+
return
|
|
65
|
+
page += 1
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class AsyncSetsResource:
|
|
69
|
+
def __init__(self, client: "AsyncTCGApi") -> None:
|
|
70
|
+
self._client = client
|
|
71
|
+
|
|
72
|
+
async def list(
|
|
73
|
+
self,
|
|
74
|
+
*,
|
|
75
|
+
game: str | None = None,
|
|
76
|
+
page: int | None = None,
|
|
77
|
+
per_page: int | None = None,
|
|
78
|
+
) -> Response[list[Set]]:
|
|
79
|
+
body = await self._client._request("GET", "/sets", {"game": game, "page": page, "per_page": per_page})
|
|
80
|
+
return parse_response(list[Set], body)
|
|
81
|
+
|
|
82
|
+
async def get(self, set_id: int) -> Response[Set]:
|
|
83
|
+
body = await self._client._request("GET", f"/sets/{set_id}")
|
|
84
|
+
return parse_response(Set, body)
|
|
85
|
+
|
|
86
|
+
async def cards(
|
|
87
|
+
self,
|
|
88
|
+
set_id: int,
|
|
89
|
+
*,
|
|
90
|
+
type: TypeLiteral | None = None,
|
|
91
|
+
sort: SortLiteral | None = None,
|
|
92
|
+
page: int | None = None,
|
|
93
|
+
per_page: int | None = None,
|
|
94
|
+
) -> Response[list[CardWithPrice]]:
|
|
95
|
+
body = await self._client._request(
|
|
96
|
+
"GET",
|
|
97
|
+
f"/sets/{set_id}/cards",
|
|
98
|
+
{"type": type, "sort": sort, "page": page, "per_page": per_page},
|
|
99
|
+
)
|
|
100
|
+
return parse_response(list[CardWithPrice], body)
|
|
101
|
+
|
|
102
|
+
async def prices(self, set_id: int) -> Response[list[BulkPriceRow]]:
|
|
103
|
+
body = await self._client._request("GET", f"/sets/{set_id}/prices")
|
|
104
|
+
return parse_response(list[BulkPriceRow], body)
|
|
105
|
+
|
|
106
|
+
async def iter_cards(self, set_id: int, **params: object) -> AsyncIterator[CardWithPrice]:
|
|
107
|
+
page = int(params.pop("page", 1) or 1) # type: ignore[arg-type]
|
|
108
|
+
per_page = min(200, int(params.pop("per_page", 200) or 200)) # type: ignore[arg-type]
|
|
109
|
+
while True:
|
|
110
|
+
resp = await self.cards(set_id, page=page, per_page=per_page, **params) # type: ignore[arg-type]
|
|
111
|
+
for card in resp.data:
|
|
112
|
+
yield card
|
|
113
|
+
if not resp.meta or not resp.meta.has_more:
|
|
114
|
+
return
|
|
115
|
+
page += 1
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""UsageResource — /usage."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
from ..models import Response, UsageResponse
|
|
8
|
+
from ._base import parse_response
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from ..async_client import AsyncTCGApi
|
|
12
|
+
from ..client import TCGApi
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class UsageResource:
|
|
16
|
+
def __init__(self, client: "TCGApi") -> None:
|
|
17
|
+
self._client = client
|
|
18
|
+
|
|
19
|
+
def get(self) -> Response[UsageResponse]:
|
|
20
|
+
body = self._client._request("GET", "/usage")
|
|
21
|
+
return parse_response(UsageResponse, body)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class AsyncUsageResource:
|
|
25
|
+
def __init__(self, client: "AsyncTCGApi") -> None:
|
|
26
|
+
self._client = client
|
|
27
|
+
|
|
28
|
+
async def get(self) -> Response[UsageResponse]:
|
|
29
|
+
body = await self._client._request("GET", "/usage")
|
|
30
|
+
return parse_response(UsageResponse, body)
|