slab-cli 0.4.0__tar.gz → 0.5.0__tar.gz

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.
Files changed (27) hide show
  1. {slab_cli-0.4.0 → slab_cli-0.5.0}/PKG-INFO +1 -1
  2. {slab_cli-0.4.0 → slab_cli-0.5.0}/pyproject.toml +1 -1
  3. {slab_cli-0.4.0 → slab_cli-0.5.0}/src/slab_cli/client.py +2 -41
  4. {slab_cli-0.4.0 → slab_cli-0.5.0}/src/slab_cli/display.py +7 -6
  5. {slab_cli-0.4.0 → slab_cli-0.5.0}/src/slab_cli/prompts.py +10 -1
  6. {slab_cli-0.4.0 → slab_cli-0.5.0}/README.md +0 -0
  7. {slab_cli-0.4.0 → slab_cli-0.5.0}/src/slab_cli/__init__.py +0 -0
  8. {slab_cli-0.4.0 → slab_cli-0.5.0}/src/slab_cli/banner.py +0 -0
  9. {slab_cli-0.4.0 → slab_cli-0.5.0}/src/slab_cli/commands/__init__.py +0 -0
  10. {slab_cli-0.4.0 → slab_cli-0.5.0}/src/slab_cli/commands/breaks.py +0 -0
  11. {slab_cli-0.4.0 → slab_cli-0.5.0}/src/slab_cli/commands/catalog.py +0 -0
  12. {slab_cli-0.4.0 → slab_cli-0.5.0}/src/slab_cli/commands/collection.py +0 -0
  13. {slab_cli-0.4.0 → slab_cli-0.5.0}/src/slab_cli/commands/custom_sets.py +0 -0
  14. {slab_cli-0.4.0 → slab_cli-0.5.0}/src/slab_cli/commands/export.py +0 -0
  15. {slab_cli-0.4.0 → slab_cli-0.5.0}/src/slab_cli/commands/lots.py +0 -0
  16. {slab_cli-0.4.0 → slab_cli-0.5.0}/src/slab_cli/commands/pricing.py +0 -0
  17. {slab_cli-0.4.0 → slab_cli-0.5.0}/src/slab_cli/commands/registry.py +0 -0
  18. {slab_cli-0.4.0 → slab_cli-0.5.0}/src/slab_cli/commands/setup.py +0 -0
  19. {slab_cli-0.4.0 → slab_cli-0.5.0}/src/slab_cli/config.py +0 -0
  20. {slab_cli-0.4.0 → slab_cli-0.5.0}/src/slab_cli/context.py +0 -0
  21. {slab_cli-0.4.0 → slab_cli-0.5.0}/src/slab_cli/flags.py +0 -0
  22. {slab_cli-0.4.0 → slab_cli-0.5.0}/src/slab_cli/help.py +0 -0
  23. {slab_cli-0.4.0 → slab_cli-0.5.0}/src/slab_cli/main.py +0 -0
  24. {slab_cli-0.4.0 → slab_cli-0.5.0}/src/slab_cli/paging.py +0 -0
  25. {slab_cli-0.4.0 → slab_cli-0.5.0}/src/slab_cli/picker.py +0 -0
  26. {slab_cli-0.4.0 → slab_cli-0.5.0}/src/slab_cli/sources.py +0 -0
  27. {slab_cli-0.4.0 → slab_cli-0.5.0}/src/slab_cli/theme.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: slab-cli
3
- Version: 0.4.0
3
+ Version: 0.5.0
4
4
  Summary: CLI for the slab trading-card API — catalog, collect, and track your cards from the terminal.
5
5
  Author: dev_jeb
6
6
  Requires-Dist: slab-schemas>=0.1.0
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "slab-cli"
3
- version = "0.4.0"
3
+ version = "0.5.0"
4
4
  description = "CLI for the slab trading-card API — catalog, collect, and track your cards from the terminal."
5
5
  readme = "README.md"
6
6
  authors = [{name = "dev_jeb"}]
@@ -22,7 +22,6 @@ from slab_schemas.collection import (
22
22
  BreakOut,
23
23
  BreakSearchQuery,
24
24
  BreakSearchResult,
25
- BreakUpdate,
26
25
  LotCreate,
27
26
  LotOut,
28
27
  LotSearchQuery,
@@ -37,7 +36,6 @@ from slab_schemas.collection import (
37
36
  CollectionResult,
38
37
  CollectionSearchQuery,
39
38
  CollectorOut,
40
- CollectorUpdate,
41
39
  )
42
40
  from slab_schemas.custom_sets import (
43
41
  CustomSetCardAdd,
@@ -51,8 +49,8 @@ from slab_schemas.custom_sets import (
51
49
  )
52
50
  from slab_schemas.dashboard import CatalogStats, DashboardStats
53
51
  from slab_schemas.pricing import CardComps, CardMarket, SetTopCards
54
- from slab_schemas.sealed import SealedMarket, SealedPriceHistory, SealedProductOut
55
- from slab_schemas.timeseries import CardPriceHistory, PortfolioHistory
52
+ from slab_schemas.sealed import SealedPriceHistory, SealedProductOut
53
+ from slab_schemas.timeseries import CardPriceHistory
56
54
 
57
55
 
58
56
  class ApiError(Exception):
@@ -145,21 +143,6 @@ class SlabClient:
145
143
  resp = self._request("GET", f"/cards/{card_uuid}/price-history", params=params)
146
144
  return CardPriceHistory.model_validate(resp.json())
147
145
 
148
- def get_portfolio_history(
149
- self,
150
- collector_uuid: str,
151
- start: str | None = None,
152
- end: str | None = None,
153
- interval: str = "daily",
154
- ) -> PortfolioHistory:
155
- params: dict[str, str] = {"interval": interval}
156
- if start:
157
- params["start"] = start
158
- if end:
159
- params["end"] = end
160
- resp = self._request("GET", f"/collectors/{collector_uuid}/portfolio/history", params=params)
161
- return PortfolioHistory.model_validate(resp.json())
162
-
163
146
  def search_sets(self, query: SetSearchQuery) -> SetSearchResult:
164
147
  resp = self._request("POST", "/sets/search", json=query.model_dump(mode="json", exclude_none=True))
165
148
  return SetSearchResult.model_validate(resp.json())
@@ -175,12 +158,6 @@ class SlabClient:
175
158
  resp = self._request("GET", f"/sets/{set_uuid}/top-cards", params={"limit": str(limit)})
176
159
  return SetTopCards.model_validate(resp.json())
177
160
 
178
- def get_sealed_market(self, product_uuid: str, comps_limit: int = 25) -> SealedMarket:
179
- resp = self._request(
180
- "GET", f"/sealed/{product_uuid}/market", params={"comps_limit": str(comps_limit)}
181
- )
182
- return SealedMarket.model_validate(resp.json())
183
-
184
161
  def get_sealed_price_history(
185
162
  self,
186
163
  product_uuid: str,
@@ -204,14 +181,6 @@ class SlabClient:
204
181
  resp = self._request("GET", "/account")
205
182
  return MeOut.model_validate(resp.json())
206
183
 
207
- # --- collectors ---
208
-
209
- def rename_collector(self, collector_uuid: str, name: str) -> CollectorOut:
210
- resp = self._request(
211
- "PATCH", f"/collectors/{collector_uuid}", json=CollectorUpdate(name=name).model_dump(mode="json")
212
- )
213
- return CollectorOut.model_validate(resp.json())
214
-
215
184
  # --- breaks ---
216
185
 
217
186
  def create_break(self, collector_uuid: str, payload: BreakCreate) -> BreakOut:
@@ -225,14 +194,6 @@ class SlabClient:
225
194
  resp = self._request("POST", f"/collectors/{collector_uuid}/breaks/search", json=body)
226
195
  return BreakSearchResult.model_validate(resp.json())
227
196
 
228
- def update_break(self, collector_uuid: str, break_uuid: str, payload: BreakUpdate) -> BreakOut:
229
- resp = self._request(
230
- "PATCH",
231
- f"/collectors/{collector_uuid}/breaks/{break_uuid}",
232
- json=payload.model_dump(mode="json", exclude_unset=True),
233
- )
234
- return BreakOut.model_validate(resp.json())
235
-
236
197
  def delete_break(self, collector_uuid: str, break_uuid: str) -> None:
237
198
  self._request("DELETE", f"/collectors/{collector_uuid}/breaks/{break_uuid}")
238
199
 
@@ -38,6 +38,7 @@ from .theme import (
38
38
  sparkline,
39
39
  titled,
40
40
  )
41
+ from .prompts import _slot
41
42
 
42
43
 
43
44
  def format_grade_delta(delta: float | None) -> str | None:
@@ -81,7 +82,7 @@ def format_card_summary(card: CardOut) -> str:
81
82
  """One-line summary of a card for inline display. Shows the subset (the line) and the parallel
82
83
  finish only when there is one — so a no-finish INSERT reads as its insert name, not 'Base'."""
83
84
  subjects = ", ".join(s.name for s in card.subjects) or "—"
84
- slot = (card.subset or "?") + (f" · {card.finish}" if card.finish else "")
85
+ slot = _slot(card)
85
86
  pr = f" /{card.print_run}" if card.print_run else ""
86
87
  attrs = f" [{', '.join(a.name for a in card.attributes)}]" if card.attributes else ""
87
88
  return f"{card.card_number} {subjects} — {card.brand or ''} {card.set_name or ''} — {slot}{pr}{attrs}"
@@ -419,7 +420,7 @@ def _highlight_panel(title: str, cards: list[HighlightCard], show_cost: bool = F
419
420
  rows = []
420
421
  for h in cards:
421
422
  subj = ", ".join(h.subjects) or "—"
422
- slot = (h.subset or "?") + (f" · {h.finish}" if h.finish else "")
423
+ slot = _slot(h)
423
424
  run = f" /{h.print_run}" if h.print_run else ""
424
425
  cost = f" [slab.gain]{money(h.cost_basis)}[/]" if (show_cost and h.cost_basis is not None) else ""
425
426
  rows.append(f"[slab.value]#{h.card_number}[/] {subj} [slab.dim]{slot}{run}[/]{cost}")
@@ -785,7 +786,7 @@ def print_custom_set_detail(detail: CustomSetDetail) -> None:
785
786
  for entry in detail.cards:
786
787
  card = entry.card
787
788
  subjects = ", ".join(s.name for s in card.subjects) if card else "—"
788
- slot = (card.subset or "?") + (f" · {card.finish}" if card and card.finish else "")
789
+ slot = _slot(card) if card else "—"
789
790
  pr = f" /{card.print_run}" if card and card.print_run else ""
790
791
  label = f"{card.card_number} {subjects} [slab.dim]— {slot}{pr}[/]" if card else "—"
791
792
 
@@ -819,7 +820,7 @@ def _card_headline(card_number, subjects: str, set_name: str | None, slot: str)
819
820
  def render_card_market(m: CardMarket) -> None:
820
821
  """Display the market pricing for a catalog card — all grade buckets."""
821
822
  subjects = ", ".join(m.subjects) or "—"
822
- slot = (m.subset or "?") + (f" · {m.finish}" if m.finish else "")
823
+ slot = _slot(m)
823
824
  console.print()
824
825
  console.print(_card_headline(m.card_number, subjects, m.set_name, slot))
825
826
 
@@ -926,7 +927,7 @@ def render_set_market(s: SetOut, sealed: list[SealedProductOut], top: SetTopCard
926
927
  ("Comps", {"justify": "right", "style": "slab.dim"}),
927
928
  ])
928
929
  for i, entry in enumerate(top.cards, 1):
929
- slot = (entry.subset or "?") + (f" · {entry.finish}" if entry.finish else "")
930
+ slot = _slot(entry)
930
931
  if entry.print_run:
931
932
  slot += f" /{entry.print_run}"
932
933
  fmv = f"[slab.foil]{money(entry.market.fair_market_value)}[/]"
@@ -947,7 +948,7 @@ def render_set_market(s: SetOut, sealed: list[SealedProductOut], top: SetTopCard
947
948
  def render_comps(c: CardComps) -> None:
948
949
  """Display recent comps (raw sales) for a catalog card — the evidence behind its FMV."""
949
950
  subjects = ", ".join(c.subjects) or "—"
950
- slot = (c.subset or "?") + (f" · {c.finish}" if c.finish else "")
951
+ slot = _slot(c)
951
952
  console.print()
952
953
  console.print(_card_headline(c.card_number, subjects, c.set_name, slot))
953
954
 
@@ -8,6 +8,7 @@ from __future__ import annotations
8
8
  from collections import Counter
9
9
  from datetime import date
10
10
  from decimal import Decimal, InvalidOperation
11
+ from typing import Protocol
11
12
 
12
13
  from InquirerPy import inquirer
13
14
  from InquirerPy.base.control import Choice
@@ -30,7 +31,15 @@ from .theme import console as _console
30
31
  # Label builders (for select menus)
31
32
  # ---------------------------------------------------------------------------
32
33
 
33
- def _slot(c: CardOut) -> str:
34
+ class _HasSlot(Protocol):
35
+ """Any DTO that carries a card's line identity — a subset and an optional parallel finish
36
+ (CardOut, HighlightCard, CardMarket, custom-set entries, …). `_slot` is duck-typed over these."""
37
+
38
+ subset: str | None
39
+ finish: str | None
40
+
41
+
42
+ def _slot(c: _HasSlot) -> str:
34
43
  """The card's line: its subset, plus the parallel finish when it has one. 'Base' shows only
35
44
  when the subset is literally Base — a no-finish INSERT shows its insert name, not 'Base'
36
45
  (id_finish NULL means 'base printing of this line', not 'the base set')."""
File without changes
File without changes
File without changes
File without changes
File without changes