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.
- {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/.github/workflows/integration.yml +1 -1
- {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/.github/workflows/pre-commit.yml +3 -4
- {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/.github/workflows/publish.yml +1 -1
- {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/.github/workflows/run-tests.yml +37 -5
- {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/.pre-commit-config.yaml +8 -10
- {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/PKG-INFO +29 -2
- {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/TODO.md +1 -0
- mtg_ssm-2.6.4/mtg_ssm/_version.py +16 -0
- {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/mtg_ssm/containers/bundles.py +3 -2
- {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/mtg_ssm/containers/collection.py +4 -2
- {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/mtg_ssm/containers/counts.py +5 -7
- {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/mtg_ssm/containers/indexes.py +1 -3
- {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/mtg_ssm/containers/legacy.py +11 -11
- {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/mtg_ssm/scryfall/fetcher.py +5 -12
- {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/mtg_ssm/scryfall/models.py +28 -26
- mtg_ssm-2.6.4/mtg_ssm/serialization/__init__.py +4 -0
- {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/mtg_ssm/serialization/csv.py +5 -9
- {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/mtg_ssm/serialization/interface.py +6 -13
- {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/mtg_ssm/serialization/xlsx.py +3 -7
- {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/mtg_ssm/ssm.py +4 -10
- {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/mtg_ssm.egg-info/PKG-INFO +29 -2
- {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/mtg_ssm.egg-info/SOURCES.txt +0 -1
- {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/mtg_ssm.egg-info/requires.txt +2 -8
- {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/pyproject.toml +79 -52
- {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/tests/conftest.py +1 -2
- {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/tests/containers/test_bundles.py +5 -5
- {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/tests/containers/test_counts.py +6 -11
- {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/tests/containers/test_indexes.py +2 -7
- {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/tests/containers/test_legacy.py +5 -10
- {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/tests/data/bulk_data.json +2 -2
- {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/tests/data/cards.json +771 -336
- {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/tests/data/sets.json +42 -42
- {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/tests/data/sets1.json +21 -21
- {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/tests/data/sets2.json +21 -21
- {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/tests/gen_testdata.py +4 -9
- {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/tests/mtg/test_util.py +4 -8
- {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/tests/scryfall/test_fetcher.py +7 -12
- {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/tests/serialization/__snapshots__/test_xlsx.ambr +39 -39
- {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/tests/serialization/test_csv.py +6 -15
- {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/tests/serialization/test_interface.py +8 -7
- {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/tests/serialization/test_xlsx.py +5 -12
- {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/tests/test_ssm.py +4 -7
- mtg_ssm-2.6.3.dev1/mtg_ssm/_version.py +0 -4
- mtg_ssm-2.6.3.dev1/mtg_ssm/serialization/__init__.py +0 -4
- mtg_ssm-2.6.3.dev1/pylintrc +0 -51
- {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/.editorconfig +0 -0
- {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/.github/dependabot.yml +0 -0
- {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/.gitignore +0 -0
- {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/LICENSE.txt +0 -0
- {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/README.rst +0 -0
- {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/mtg_ssm/__init__.py +0 -0
- {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/mtg_ssm/containers/__init__.py +0 -0
- {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/mtg_ssm/mtg/__init__.py +0 -0
- {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/mtg_ssm/mtg/util.py +0 -0
- {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/mtg_ssm/py.typed +0 -0
- {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/mtg_ssm/scryfall/__init__.py +0 -0
- {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/mtg_ssm.egg-info/dependency_links.txt +0 -0
- {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/mtg_ssm.egg-info/entry_points.txt +0 -0
- {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/mtg_ssm.egg-info/top_level.txt +0 -0
- {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/setup.cfg +0 -0
- {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/tests/__init__.py +0 -0
- {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/tests/containers/__init__.py +0 -0
- {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/tests/data/LICENSE.txt +0 -0
- {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/tests/data/migrations.json +0 -0
- {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/tests/mtg/__init__.py +0 -0
- {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/tests/scryfall/__init__.py +0 -0
- {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/tests/serialization/__init__.py +0 -0
- {mtg_ssm-2.6.3.dev1 → mtg_ssm-2.6.4}/tests/serialization/__snapshots__/test_csv.ambr +0 -0
|
@@ -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@
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
15
|
|
|
16
16
|
- uses: actions/setup-python@v4
|
|
17
17
|
with:
|
|
18
|
-
python-version: "3.
|
|
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
|
|
@@ -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@
|
|
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.
|
|
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.
|
|
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
|
+
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
|
-
|
|
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
|
===================================================
|
|
@@ -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
|
|
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") ->
|
|
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") ->
|
|
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
|
|
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
|
-
|
|
54
|
-
|
|
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 = [
|
|
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
|
-
|
|
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
|
-
|
|
145
|
-
|
|
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:
|
|
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
|
-
|
|
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,
|
|
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:
|
|
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
|
|
@@ -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
|
-
|
|
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
|
|