mtg-ssm 2.6.3.dev1__tar.gz → 2.6.4__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 (68) hide show
  1. {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/.github/workflows/integration.yml +1 -1
  2. {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/.github/workflows/pre-commit.yml +3 -4
  3. {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/.github/workflows/publish.yml +1 -1
  4. {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/.github/workflows/run-tests.yml +37 -5
  5. {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/.pre-commit-config.yaml +8 -10
  6. {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/PKG-INFO +29 -2
  7. {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/TODO.md +1 -0
  8. mtg_ssm-2.6.4/mtg_ssm/_version.py +16 -0
  9. {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/mtg_ssm/containers/bundles.py +3 -2
  10. {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/mtg_ssm/containers/collection.py +4 -2
  11. {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/mtg_ssm/containers/counts.py +5 -7
  12. {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/mtg_ssm/containers/indexes.py +1 -3
  13. {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/mtg_ssm/containers/legacy.py +11 -11
  14. {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/mtg_ssm/scryfall/fetcher.py +5 -12
  15. {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/mtg_ssm/scryfall/models.py +28 -26
  16. mtg_ssm-2.6.4/mtg_ssm/serialization/__init__.py +4 -0
  17. {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/mtg_ssm/serialization/csv.py +5 -9
  18. {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/mtg_ssm/serialization/interface.py +6 -13
  19. {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/mtg_ssm/serialization/xlsx.py +3 -7
  20. {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/mtg_ssm/ssm.py +4 -10
  21. {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/mtg_ssm.egg-info/PKG-INFO +29 -2
  22. {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/mtg_ssm.egg-info/SOURCES.txt +0 -1
  23. {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/mtg_ssm.egg-info/requires.txt +2 -8
  24. {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/pyproject.toml +79 -52
  25. {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/tests/conftest.py +1 -2
  26. {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/tests/containers/test_bundles.py +5 -5
  27. {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/tests/containers/test_counts.py +6 -11
  28. {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/tests/containers/test_indexes.py +2 -7
  29. {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/tests/containers/test_legacy.py +5 -10
  30. {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/tests/data/bulk_data.json +2 -2
  31. {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/tests/data/cards.json +771 -336
  32. {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/tests/data/sets.json +42 -42
  33. {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/tests/data/sets1.json +21 -21
  34. {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/tests/data/sets2.json +21 -21
  35. {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/tests/gen_testdata.py +4 -9
  36. {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/tests/mtg/test_util.py +4 -8
  37. {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/tests/scryfall/test_fetcher.py +7 -12
  38. {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/tests/serialization/__snapshots__/test_xlsx.ambr +39 -39
  39. {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/tests/serialization/test_csv.py +6 -15
  40. {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/tests/serialization/test_interface.py +8 -7
  41. {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/tests/serialization/test_xlsx.py +5 -12
  42. {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/tests/test_ssm.py +4 -7
  43. mtg_ssm-2.6.3.dev1/mtg_ssm/_version.py +0 -4
  44. mtg_ssm-2.6.3.dev1/mtg_ssm/serialization/__init__.py +0 -4
  45. mtg_ssm-2.6.3.dev1/pylintrc +0 -51
  46. {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/.editorconfig +0 -0
  47. {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/.github/dependabot.yml +0 -0
  48. {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/.gitignore +0 -0
  49. {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/LICENSE.txt +0 -0
  50. {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/README.rst +0 -0
  51. {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/mtg_ssm/__init__.py +0 -0
  52. {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/mtg_ssm/containers/__init__.py +0 -0
  53. {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/mtg_ssm/mtg/__init__.py +0 -0
  54. {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/mtg_ssm/mtg/util.py +0 -0
  55. {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/mtg_ssm/py.typed +0 -0
  56. {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/mtg_ssm/scryfall/__init__.py +0 -0
  57. {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/mtg_ssm.egg-info/dependency_links.txt +0 -0
  58. {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/mtg_ssm.egg-info/entry_points.txt +0 -0
  59. {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/mtg_ssm.egg-info/top_level.txt +0 -0
  60. {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/setup.cfg +0 -0
  61. {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/tests/__init__.py +0 -0
  62. {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/tests/containers/__init__.py +0 -0
  63. {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/tests/data/LICENSE.txt +0 -0
  64. {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/tests/data/migrations.json +0 -0
  65. {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/tests/mtg/__init__.py +0 -0
  66. {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/tests/scryfall/__init__.py +0 -0
  67. {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/tests/serialization/__init__.py +0 -0
  68. {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/tests/serialization/__snapshots__/test_csv.ambr +0 -0
@@ -20,7 +20,7 @@ jobs:
20
20
  - "[lxml]"
21
21
  name: End-to-end tests
22
22
  steps:
23
- - uses: actions/checkout@v3
23
+ - uses: actions/checkout@v4
24
24
 
25
25
  - uses: actions/setup-python@v4
26
26
  with:
@@ -11,14 +11,13 @@ jobs:
11
11
  runs-on: ubuntu-latest
12
12
  name: Run all pre-commit checks
13
13
  steps:
14
- - uses: actions/checkout@v3
14
+ - uses: actions/checkout@v4
15
15
 
16
16
  - uses: actions/setup-python@v4
17
17
  with:
18
- python-version: "3.8"
18
+ python-version: "3.12"
19
19
  cache: pip
20
- cache-dependency-path: |
21
- pyproject.toml
20
+ cache-dependency-path: pyproject.toml
22
21
 
23
22
  - run: |
24
23
  pip install -U pip setuptools wheel
@@ -16,7 +16,7 @@ jobs:
16
16
  id-token: write
17
17
 
18
18
  steps:
19
- - uses: actions/checkout@v3
19
+ - uses: actions/checkout@v4
20
20
  with:
21
21
  # setuptools_scm needs the git history
22
22
  fetch-depth: 0
@@ -7,6 +7,38 @@ on:
7
7
  - trunk
8
8
 
9
9
  jobs:
10
+ can-run:
11
+ strategy:
12
+ matrix:
13
+ python-version:
14
+ - "3.8"
15
+ - "3.9"
16
+ - "3.10"
17
+ - "3.11"
18
+ - "3.12"
19
+ os:
20
+ - ubuntu-latest
21
+ - macos-latest
22
+ - windows-latest
23
+ runs-on: ${{ matrix.os }}
24
+ name: Can run on ${{ matrix.os }} - Python ${{ matrix.python-version }}
25
+ steps:
26
+ - uses: actions/checkout@v4
27
+
28
+ - uses: actions/setup-python@v4
29
+ with:
30
+ python-version: "${{ matrix.python-version }}"
31
+ cache: pip
32
+ cache-dependency-path: pyproject.toml
33
+
34
+ - name: Install project
35
+ run: |
36
+ pip install -U pip setuptools
37
+ pip install -e .
38
+
39
+ - name: Run mtg-ssm
40
+ run: mtg-ssm --help
41
+
10
42
  pytest:
11
43
  runs-on: ubuntu-latest
12
44
  strategy:
@@ -16,16 +48,16 @@ jobs:
16
48
  - "3.9"
17
49
  - "3.10"
18
50
  - "3.11"
51
+ - "3.12"
19
52
  name: Python ${{ matrix.python-version }} pytest
20
53
  steps:
21
- - uses: actions/checkout@v3
54
+ - uses: actions/checkout@v4
22
55
 
23
56
  - uses: actions/setup-python@v4
24
57
  with:
25
58
  python-version: "${{ matrix.python-version }}"
26
59
  cache: pip
27
- cache-dependency-path: |
28
- pyproject.toml
60
+ cache-dependency-path: pyproject.toml
29
61
 
30
62
  - name: Install project
31
63
  run: |
@@ -39,7 +71,7 @@ jobs:
39
71
  run: coverage lcov
40
72
 
41
73
  - name: Coveralls Parallel
42
- uses: coverallsapp/github-action@v2.2.1
74
+ uses: coverallsapp/github-action@v2.2.3
43
75
  with:
44
76
  github-token: ${{ secrets.github_token }}
45
77
  flag-name: python-${{ matrix.python-version }}
@@ -52,7 +84,7 @@ jobs:
52
84
  runs-on: ubuntu-latest
53
85
  steps:
54
86
  - name: Coveralls Finished
55
- uses: coverallsapp/github-action@v2.2.1
87
+ uses: coverallsapp/github-action@v2.2.3
56
88
  with:
57
89
  github-token: ${{ secrets.github_token }}
58
90
  path-to-lcov: coverage.lcov
@@ -8,21 +8,19 @@ repos:
8
8
  language: system
9
9
  require_serial: true
10
10
  types: [python]
11
- - id: isort
12
- name: Check import order
13
- language: system
14
- entry: isort
15
- types: [python]
16
- - id: pylint
17
- name: Static analysis from pylint
18
- language: system
19
- entry: pylint
20
- types: [python]
21
11
  - id: mypy
22
12
  name: Type checking from mypy
23
13
  language: system
24
14
  entry: mypy
25
15
  types: [python]
16
+ - id: ruff
17
+ name: ruff
18
+ description: "Run ruff on files"
19
+ entry: ruff check --force-exclude
20
+ language: system
21
+ types_or: [python, pyi]
22
+ args: [--fix, --exit-non-zero-on-fix]
23
+ require_serial: true
26
24
  - id: doc8-python
27
25
  name: Documentation checking from doc8 (Python)
28
26
  language: system
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mtg_ssm
3
- Version: 2.6.3.dev1
3
+ Version: 2.6.4
4
4
  Summary: A tool to manage Magic: the Gathering collection spreadsheets
5
5
  Author-email: George Leslie-Waksman <waksman@gmail.com>
6
6
  License: MIT
@@ -26,9 +26,36 @@ Classifier: Programming Language :: Python :: 3.11
26
26
  Classifier: Topic :: Games/Entertainment
27
27
  Requires-Python: >=3.8
28
28
  Description-Content-Type: text/x-rst
29
+ License-File: LICENSE.txt
30
+ Requires-Dist: appdirs~=1.4
31
+ Requires-Dist: msgspec~=0.15
32
+ Requires-Dist: openpyxl~=3.0
33
+ Requires-Dist: requests~=2.27
34
+ Requires-Dist: requests-cache~=0.9.8
35
+ Requires-Dist: typing_extensions~=4.8.0
29
36
  Provides-Extra: lxml
37
+ Requires-Dist: lxml>=3.7.2; extra == "lxml"
30
38
  Provides-Extra: dev
31
- License-File: LICENSE.txt
39
+ Requires-Dist: black; extra == "dev"
40
+ Requires-Dist: coverage[toml]; extra == "dev"
41
+ Requires-Dist: doc8; extra == "dev"
42
+ Requires-Dist: freezegun; extra == "dev"
43
+ Requires-Dist: lxml; extra == "dev"
44
+ Requires-Dist: mypy; extra == "dev"
45
+ Requires-Dist: openpyxl-stubs; extra == "dev"
46
+ Requires-Dist: pip; extra == "dev"
47
+ Requires-Dist: Pygments; extra == "dev"
48
+ Requires-Dist: pytest>=6.0; extra == "dev"
49
+ Requires-Dist: pytest-cov; extra == "dev"
50
+ Requires-Dist: responses; extra == "dev"
51
+ Requires-Dist: ruff; extra == "dev"
52
+ Requires-Dist: setuptools>=45; extra == "dev"
53
+ Requires-Dist: setuptools-scm[toml]>=6.2; extra == "dev"
54
+ Requires-Dist: syrupy>=4.0; extra == "dev"
55
+ Requires-Dist: types-appdirs; extra == "dev"
56
+ Requires-Dist: types-freezegun; extra == "dev"
57
+ Requires-Dist: types-requests; extra == "dev"
58
+ Requires-Dist: wheel; extra == "dev"
32
59
 
33
60
  mtg-ssm - Magic: the Gathering Spreadsheet Manager
34
61
  ===================================================
@@ -8,3 +8,4 @@
8
8
  - Update card_count for sets when processing bundle filters
9
9
  - Can we read images from icon_svg_uri in sets, rasterize, and
10
10
  insert the set icons in the xlsx "All Sets" page?
11
+ - Can we switch to hatchling as our build system?
@@ -0,0 +1,16 @@
1
+ # file generated by setuptools_scm
2
+ # don't change, don't track in version control
3
+ TYPE_CHECKING = False
4
+ if TYPE_CHECKING:
5
+ from typing import Tuple, Union
6
+ VERSION_TUPLE = Tuple[Union[int, str], ...]
7
+ else:
8
+ VERSION_TUPLE = object
9
+
10
+ version: str
11
+ __version__: str
12
+ __version_tuple__: VERSION_TUPLE
13
+ version_tuple: VERSION_TUPLE
14
+
15
+ __version__ = version = '2.6.4'
16
+ __version_tuple__ = version_tuple = (2, 6, 4)
@@ -20,7 +20,7 @@ class ScryfallDataSet(NamedTuple):
20
20
  migrations: List[ScryMigration]
21
21
 
22
22
 
23
- def filter_cards_and_sets(
23
+ def filter_cards_and_sets( # noqa: C901
24
24
  scryfall_data: ScryfallDataSet,
25
25
  *,
26
26
  exclude_set_types: Optional[Set[ScrySetType]] = None,
@@ -49,7 +49,8 @@ def filter_cards_and_sets(
49
49
 
50
50
  accepted_cards = []
51
51
  nonempty_setcodes = set()
52
- for card in scryfall_data.cards:
52
+ for card_loop in scryfall_data.cards:
53
+ card = card_loop # capture loop variable
53
54
  while card.set in remapped_setcodes:
54
55
  collector_number = card.collector_number
55
56
  if collector_number.isdigit():
@@ -2,6 +2,8 @@
2
2
 
3
3
  from dataclasses import dataclass
4
4
 
5
+ from typing_extensions import Self
6
+
5
7
  from mtg_ssm.containers import counts
6
8
  from mtg_ssm.containers.counts import ScryfallCardCount
7
9
  from mtg_ssm.containers.indexes import Oracle
@@ -22,7 +24,7 @@ class MagicCollection:
22
24
  counts=counts.merge_card_counts(self.counts, other.counts),
23
25
  )
24
26
 
25
- def __iadd__(self, other: "MagicCollection") -> "MagicCollection":
27
+ def __iadd__(self, other: "MagicCollection") -> Self:
26
28
  if not isinstance(other, MagicCollection):
27
29
  return NotImplemented
28
30
  self.counts = counts.merge_card_counts(self.counts, other.counts)
@@ -36,7 +38,7 @@ class MagicCollection:
36
38
  counts=counts.diff_card_counts(self.counts, other.counts),
37
39
  )
38
40
 
39
- def __isub__(self, other: "MagicCollection") -> "MagicCollection":
41
+ def __isub__(self, other: "MagicCollection") -> Self:
40
42
  if not isinstance(other, MagicCollection):
41
43
  return NotImplemented
42
44
  self.counts = counts.diff_card_counts(self.counts, other.counts)
@@ -33,7 +33,8 @@ def aggregate_card_counts(
33
33
  ) -> ScryfallCardCount:
34
34
  """Extract card counts from card rows."""
35
35
  card_counts: ScryfallCardCount = {}
36
- for card_row in card_rows:
36
+ for card_row_loop in card_rows:
37
+ card_row = card_row_loop # capture loop variable
37
38
  if "scryfall_id" not in card_row:
38
39
  card_row = legacy.coerce_row(card_row, oracle)
39
40
  if not card_row:
@@ -50,9 +51,8 @@ def aggregate_card_counts(
50
51
  while scryfall_id in oracle.index.migrate_old_id_to_new_id:
51
52
  scryfall_id = oracle.index.migrate_old_id_to_new_id[scryfall_id]
52
53
  if scryfall_id not in oracle.index.id_to_card:
53
- raise CardNotFoundError(
54
- f"Found counts for card={scryfall_id} not found scryfall data"
55
- )
54
+ msg = f"Found counts for card={scryfall_id} not found scryfall data"
55
+ raise CardNotFoundError(msg)
56
56
  card_counts[scryfall_id] = counts
57
57
  return card_counts
58
58
 
@@ -66,9 +66,7 @@ def merge_card_counts(*card_counts_args: ScryfallCardCount) -> ScryfallCardCount
66
66
  return dict(merged_counts)
67
67
 
68
68
 
69
- def diff_card_counts(
70
- left: ScryfallCardCount, right: ScryfallCardCount
71
- ) -> ScryfallCardCount:
69
+ def diff_card_counts(left: ScryfallCardCount, right: ScryfallCardCount) -> ScryfallCardCount:
72
70
  """Subtract right print counts from left print counts."""
73
71
  diffed_counts: ScryfallCardCount = collections.defaultdict(dict)
74
72
  for card_id in left.keys() | right.keys():
@@ -73,9 +73,7 @@ class ScryfallDataIndex:
73
73
 
74
74
  self.snnma_to_id = collections.defaultdict(set)
75
75
 
76
- name_to_unsorted_cards: Dict[str, List[ScryCard]] = collections.defaultdict(
77
- list
78
- )
76
+ name_to_unsorted_cards: Dict[str, List[ScryCard]] = collections.defaultdict(list)
79
77
  setcode_to_unsorted_cards: Dict[str, List[ScryCard]] = {}
80
78
 
81
79
  for set_ in scrydata.sets:
@@ -102,20 +102,18 @@ PSUDONYM_TO_ARTIST: Dict[Optional[str], str] = {
102
102
  def find_scryfall_id(card_row: Dict[str, str], oracle: Oracle) -> UUID:
103
103
  """Heuristically determine the scryfall id for a given input row."""
104
104
  set_code = card_row.get("set", "")
105
- set_codes = [set_code, set_code.lower()] + OTHER_SET_CODE_TO_SET_CODE.get(
106
- set_code, []
107
- )
105
+ set_codes = [
106
+ set_code,
107
+ set_code.lower(),
108
+ *OTHER_SET_CODE_TO_SET_CODE.get(set_code, []),
109
+ ]
108
110
  name = card_row.get("name", "")
109
111
  collector_number = card_row.get("number") or None
110
112
  mvid = int(card_row.get("multiverseid") or -1)
111
113
  artist = card_row.get("artist") or None
112
114
  artist = PSUDONYM_TO_ARTIST.get(artist, artist)
113
- print(
114
- f"Searching => Set: {set_code}; Name: {name}; Number: {collector_number}; MVID: {mvid}"
115
- )
116
- snnma_keys: List[
117
- Tuple[Optional[str], str, Optional[str], Optional[int], Optional[str]]
118
- ] = []
115
+ print(f"Searching => Set: {set_code}; Name: {name}; Number: {collector_number}; MVID: {mvid}")
116
+ snnma_keys: List[Tuple[Optional[str], str, Optional[str], Optional[int], Optional[str]]] = []
119
117
  for set_ in set_codes:
120
118
  snnma_keys += [
121
119
  (set_, name, collector_number, None, None),
@@ -141,8 +139,10 @@ def find_scryfall_id(card_row: Dict[str, str], oracle: Oracle) -> UUID:
141
139
  )
142
140
  return scryfall_id
143
141
  if seen:
144
- raise MultipleMatchError(f"Could not find scryfall card for row: {card_row}")
145
- raise NoMatchError(f"Could not find scryfall card for row: {card_row}")
142
+ msg = f"Could not find scryfall card for row: {card_row}"
143
+ raise MultipleMatchError(msg)
144
+ msg = f"Could not find scryfall card for row: {card_row}"
145
+ raise NoMatchError(msg)
146
146
 
147
147
 
148
148
  def coerce_row(card_row: Dict[str, Any], oracle: Oracle) -> Dict[str, Any]:
@@ -28,7 +28,7 @@ CACHE_SERIALIZER = SerializerPipeline(
28
28
  is_binary=True,
29
29
  )
30
30
  SESSION = CachedSession(
31
- os.path.join(CACHE_DIR, "requests_cache.sqlite"),
31
+ os.path.join(CACHE_DIR, "requests_cache.sqlite"), # noqa: PTH118
32
32
  backend="sqlite",
33
33
  serializer=CACHE_SERIALIZER,
34
34
  cache_control=True,
@@ -51,7 +51,7 @@ def _fetch_endpoint(endpoint: str) -> bytes:
51
51
  return response.content
52
52
 
53
53
 
54
- def scryfetch() -> ScryfallDataSet: # pylint: disable=too-many-locals
54
+ def scryfetch() -> ScryfallDataSet:
55
55
  """Retrieve and deserialize Scryfall object data."""
56
56
  print("Reading data from scryfall")
57
57
  bulk_data = msgspec.json.decode(
@@ -66,9 +66,7 @@ def scryfetch() -> ScryfallDataSet: # pylint: disable=too-many-locals
66
66
  sets_data += sets_list.data
67
67
 
68
68
  scrylistmigration_decoder = msgspec.json.Decoder(ScryList[ScryMigration])
69
- migrations_list = scrylistmigration_decoder.decode(
70
- _fetch_endpoint(MIGRATIONS_ENDPOINT)
71
- )
69
+ migrations_list = scrylistmigration_decoder.decode(_fetch_endpoint(MIGRATIONS_ENDPOINT))
72
70
  migrations_data = migrations_list.data
73
71
  while migrations_list.has_more and migrations_list.next_page is not None:
74
72
  migrations_list = scrylistmigration_decoder.decode(
@@ -77,11 +75,6 @@ def scryfetch() -> ScryfallDataSet: # pylint: disable=too-many-locals
77
75
  migrations_data += migrations_list.data
78
76
 
79
77
  [cards_endpoint] = [bd.download_uri for bd in bulk_data if bd.type == BULK_TYPE]
80
- cards_data = msgspec.json.decode(
81
- _fetch_endpoint(cards_endpoint), type=List[ScryCard]
82
- )
78
+ cards_data = msgspec.json.decode(_fetch_endpoint(cards_endpoint), type=List[ScryCard])
83
79
 
84
- scryfall_data = ScryfallDataSet(
85
- sets=sets_data, cards=cards_data, migrations=migrations_data
86
- )
87
- return scryfall_data
80
+ return ScryfallDataSet(sets=sets_data, cards=cards_data, migrations=migrations_data)
@@ -1,9 +1,10 @@
1
1
  """Scryfall object models."""
2
+ # ruff: noqa: A003
2
3
 
3
4
  import datetime as dt
4
5
  from decimal import Decimal
5
6
  from enum import Enum
6
- from typing import Dict, Generic, List, Literal, Optional, TypeVar, Union
7
+ from typing import Dict, Generic, List, Optional, TypeVar, Union
7
8
  from uuid import UUID
8
9
 
9
10
  from msgspec import Struct
@@ -11,7 +12,7 @@ from typing_extensions import TypeAlias
11
12
 
12
13
 
13
14
  class ScryColor(str, Enum):
14
- """Enum for https://scryfall.com/docs/api/colors#color-arrays"""
15
+ """Enum for https://scryfall.com/docs/api/colors#color-arrays."""
15
16
 
16
17
  WHITE = "W"
17
18
  BLUE = "U"
@@ -23,7 +24,7 @@ class ScryColor(str, Enum):
23
24
 
24
25
 
25
26
  class ScrySetType(str, Enum):
26
- """Enum for https://scryfall.com/docs/api/sets#set-types"""
27
+ """Enum for https://scryfall.com/docs/api/sets#set-types."""
27
28
 
28
29
  CORE = "core"
29
30
  EXPANSION = "expansion"
@@ -43,7 +44,7 @@ class ScrySetType(str, Enum):
43
44
  STARTER = "starter"
44
45
  BOX = "box"
45
46
  PROMO = "promo"
46
- TOKEN = "token"
47
+ TOKEN = "token" # noqa: S105
47
48
  MEMORABILIA = "memorabilia"
48
49
  ALCHEMY = "alchemy"
49
50
  ARSENAL = "arsenal"
@@ -51,7 +52,7 @@ class ScrySetType(str, Enum):
51
52
 
52
53
 
53
54
  class ScryCardLayout(str, Enum):
54
- """Enum for https://scryfall.com/docs/api/layouts#layout"""
55
+ """Enum for https://scryfall.com/docs/api/layouts#layout."""
55
56
 
56
57
  NORMAL = "normal"
57
58
  SPLIT = "split"
@@ -66,8 +67,8 @@ class ScryCardLayout(str, Enum):
66
67
  PLANAR = "planar"
67
68
  SCHEME = "scheme"
68
69
  VANGUARD = "vanguard"
69
- TOKEN = "token"
70
- DOUBLE_FACED_TOKEN = "double_faced_token"
70
+ TOKEN = "token" # noqa: S105
71
+ DOUBLE_FACED_TOKEN = "double_faced_token" # noqa: S105
71
72
  EMBLEM = "emblem"
72
73
  AUGMENT = "augment"
73
74
  HOST = "host"
@@ -79,7 +80,7 @@ class ScryCardLayout(str, Enum):
79
80
 
80
81
 
81
82
  class ScryCardFrame(str, Enum):
82
- """Enum for https://scryfall.com/docs/api/layouts#frames"""
83
+ """Enum for https://scryfall.com/docs/api/layouts#frames."""
83
84
 
84
85
  Y1993 = "1993"
85
86
  Y1997 = "1997"
@@ -89,7 +90,7 @@ class ScryCardFrame(str, Enum):
89
90
 
90
91
 
91
92
  class ScryFrameEffect(str, Enum):
92
- """Enum for https://scryfall.com/docs/api/layouts#frame-effects"""
93
+ """Enum for https://scryfall.com/docs/api/layouts#frame-effects."""
93
94
 
94
95
  NONE = ""
95
96
  LEGENDARY = "legendary"
@@ -122,10 +123,11 @@ class ScryFrameEffect(str, Enum):
122
123
  GILDED = "gilded"
123
124
  BORDERLESS = "borderless"
124
125
  THICK = "thick"
126
+ GRAVESTONE = "gravestone"
125
127
 
126
128
 
127
129
  class ScryBorderColor(str, Enum):
128
- """Enum for card border_color"""
130
+ """Enum for card border_color."""
129
131
 
130
132
  BLACK = "black"
131
133
  BORDERLESS = "borderless"
@@ -135,7 +137,7 @@ class ScryBorderColor(str, Enum):
135
137
 
136
138
 
137
139
  class ScryFinish(str, Enum):
138
- """Enum for card finishes"""
140
+ """Enum for card finishes."""
139
141
 
140
142
  FOIL = "foil"
141
143
  NONFOIL = "nonfoil"
@@ -144,7 +146,7 @@ class ScryFinish(str, Enum):
144
146
 
145
147
 
146
148
  class ScryImageStatus(str, Enum):
147
- """Enum for card image_status"""
149
+ """Enum for card image_status."""
148
150
 
149
151
  MISSING = "missing"
150
152
  PLACEHOLDER = "placeholder"
@@ -153,7 +155,7 @@ class ScryImageStatus(str, Enum):
153
155
 
154
156
 
155
157
  class ScryGame(str, Enum):
156
- """Enum for card games"""
158
+ """Enum for card games."""
157
159
 
158
160
  PAPER = "paper"
159
161
  ARENA = "arena"
@@ -163,7 +165,7 @@ class ScryGame(str, Enum):
163
165
 
164
166
 
165
167
  class ScryRarity(str, Enum):
166
- """Enum for card rarity"""
168
+ """Enum for card rarity."""
167
169
 
168
170
  COMMON = "common"
169
171
  UNCOMMON = "uncommon"
@@ -174,7 +176,7 @@ class ScryRarity(str, Enum):
174
176
 
175
177
 
176
178
  class ScrySecurityStamp(str, Enum):
177
- """Enum for card security_stamp"""
179
+ """Enum for card security_stamp."""
178
180
 
179
181
  OVAL = "oval"
180
182
  TRIANGLE = "triangle"
@@ -185,7 +187,7 @@ class ScrySecurityStamp(str, Enum):
185
187
 
186
188
 
187
189
  class ScryFormat(str, Enum):
188
- """Enum for card legalities keys"""
190
+ """Enum for card legalities keys."""
189
191
 
190
192
  BRAWL = "brawl"
191
193
  COMMANDER = "commander"
@@ -212,7 +214,7 @@ class ScryFormat(str, Enum):
212
214
 
213
215
 
214
216
  class ScryLegality(str, Enum):
215
- """Enum for card legalities values"""
217
+ """Enum for card legalities values."""
216
218
 
217
219
  LEGAL = "legal"
218
220
  NOT_LEGAL = "not_legal"
@@ -221,7 +223,7 @@ class ScryLegality(str, Enum):
221
223
 
222
224
 
223
225
  class ScryMigrationStrategy(str, Enum):
224
- """Enum for migration strategy values"""
226
+ """Enum for migration strategy values."""
225
227
 
226
228
  MERGE = "merge"
227
229
  DELETE = "delete"
@@ -234,7 +236,7 @@ class ScrySet(
234
236
  kw_only=True,
235
237
  omit_defaults=True,
236
238
  ):
237
- """Model for https://scryfall.com/docs/api/sets"""
239
+ """Model for https://scryfall.com/docs/api/sets."""
238
240
 
239
241
  id: UUID
240
242
  code: str
@@ -265,7 +267,7 @@ class ScryRelatedCard(
265
267
  kw_only=True,
266
268
  omit_defaults=True,
267
269
  ):
268
- """Model for https://scryfall.com/docs/api/cards#related-card-objects"""
270
+ """Model for https://scryfall.com/docs/api/cards#related-card-objects."""
269
271
 
270
272
  id: UUID
271
273
  component: str
@@ -281,7 +283,7 @@ class ScryCardFace(
281
283
  kw_only=True,
282
284
  omit_defaults=True,
283
285
  ):
284
- """Model for https://scryfall.com/docs/api/cards#card-face-objects"""
286
+ """Model for https://scryfall.com/docs/api/cards#card-face-objects."""
285
287
 
286
288
  artist: Optional[str] = None
287
289
  artist_id: Optional[UUID] = None
@@ -311,7 +313,7 @@ class CardPreviewBlock(Struct):
311
313
  """Model for card preview block."""
312
314
 
313
315
  source: str
314
- source_uri: Union[str, Literal[""], str]
316
+ source_uri: str
315
317
  previewed_at: dt.date
316
318
 
317
319
 
@@ -322,7 +324,7 @@ class ScryCard(
322
324
  kw_only=True,
323
325
  omit_defaults=True,
324
326
  ):
325
- """Model for https://scryfall.com/docs/api/cards"""
327
+ """Model for https://scryfall.com/docs/api/cards."""
326
328
 
327
329
  # Core Card Fields
328
330
  arena_id: Optional[int] = None
@@ -420,7 +422,7 @@ class ScryBulkData(
420
422
  kw_only=True,
421
423
  omit_defaults=True,
422
424
  ):
423
- """Model for https://scryfall.com/docs/api/bulk-data"""
425
+ """Model for https://scryfall.com/docs/api/bulk-data."""
424
426
 
425
427
  id: UUID
426
428
  uri: str
@@ -441,7 +443,7 @@ class ScryMigration(
441
443
  kw_only=True,
442
444
  omit_defaults=True,
443
445
  ):
444
- """Model for https://scryfall.com/docs/api/migrations"""
446
+ """Model for https://scryfall.com/docs/api/migrations."""
445
447
 
446
448
  id: UUID
447
449
  uri: str
@@ -470,7 +472,7 @@ class ScryList(
470
472
  kw_only=True,
471
473
  omit_defaults=True,
472
474
  ):
473
- """Model for https://scryfall.com/docs/api/lists"""
475
+ """Model for https://scryfall.com/docs/api/lists."""
474
476
 
475
477
  data: List[_ScryListableT]
476
478
  has_more: bool
@@ -0,0 +1,4 @@
1
+ """Ensure that all serializers are imported to properly set up interface."""
2
+ from . import csv, interface, xlsx
3
+
4
+ __all__ = ["csv", "interface", "xlsx"]
@@ -12,9 +12,7 @@ from mtg_ssm.containers.indexes import Oracle
12
12
  from mtg_ssm.scryfall.models import ScryCard
13
13
  from mtg_ssm.serialization import interface
14
14
 
15
- CSV_HEADER = ["set", "name", "collector_number", "scryfall_id"] + [
16
- ct.value for ct in CountType
17
- ]
15
+ CSV_HEADER = ["set", "name", "collector_number", "scryfall_id"] + [ct.value for ct in CountType]
18
16
 
19
17
 
20
18
  def row_for_card(card: ScryCard, card_count: Mapping[CountType, int]) -> Dict[str, Any]:
@@ -28,10 +26,8 @@ def row_for_card(card: ScryCard, card_count: Mapping[CountType, int]) -> Dict[st
28
26
  }
29
27
 
30
28
 
31
- def rows_for_cards(
32
- collection: MagicCollection, verbose: bool
33
- ) -> Iterable[Dict[str, Any]]:
34
- """Generator that yields csv rows from a collection."""
29
+ def rows_for_cards(collection: MagicCollection, verbose: bool) -> Iterable[Dict[str, Any]]:
30
+ """Yield csv rows from a collection."""
35
31
  for card_set in sorted(
36
32
  collection.oracle.index.setcode_to_set.values(),
37
33
  key=lambda cset: (cset.released_at or dt.date.min, cset.code),
@@ -43,7 +39,7 @@ def rows_for_cards(
43
39
 
44
40
 
45
41
  class CsvFullDialect(interface.SerializationDialect):
46
- """csv collection writing a row for every printing"""
42
+ """csv collection writing a row for every printing."""
47
43
 
48
44
  extension: ClassVar[str] = "csv"
49
45
  dialect: ClassVar[str] = "csv"
@@ -67,7 +63,7 @@ class CsvFullDialect(interface.SerializationDialect):
67
63
 
68
64
 
69
65
  class CsvTerseDialect(CsvFullDialect):
70
- """csv collection writing only rows that have counts"""
66
+ """csv collection writing only rows that have counts."""
71
67
 
72
68
  dialect: ClassVar[str] = "terse"
73
69