mtg-ssm 2.8.3.dev1__tar.gz → 2.8.3.dev5__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 (72) hide show
  1. {mtg_ssm-2.8.3.dev1 → mtg_ssm-2.8.3.dev5}/.github/workflows/integration.yml +1 -1
  2. {mtg_ssm-2.8.3.dev1 → mtg_ssm-2.8.3.dev5}/.github/workflows/pre-commit.yml +1 -1
  3. {mtg_ssm-2.8.3.dev1 → mtg_ssm-2.8.3.dev5}/.github/workflows/publish.yml +1 -1
  4. {mtg_ssm-2.8.3.dev1 → mtg_ssm-2.8.3.dev5}/.github/workflows/run-tests.yml +2 -2
  5. {mtg_ssm-2.8.3.dev1 → mtg_ssm-2.8.3.dev5}/PKG-INFO +3 -2
  6. {mtg_ssm-2.8.3.dev1 → mtg_ssm-2.8.3.dev5}/README.rst +2 -1
  7. {mtg_ssm-2.8.3.dev1 → mtg_ssm-2.8.3.dev5}/mtg_ssm/_version.py +3 -3
  8. {mtg_ssm-2.8.3.dev1 → mtg_ssm-2.8.3.dev5}/mtg_ssm/scryfall/fetcher.py +26 -2
  9. {mtg_ssm-2.8.3.dev1 → mtg_ssm-2.8.3.dev5}/mtg_ssm/scryfall/models.py +7 -4
  10. {mtg_ssm-2.8.3.dev1 → mtg_ssm-2.8.3.dev5}/mtg_ssm.egg-info/PKG-INFO +3 -2
  11. {mtg_ssm-2.8.3.dev1 → mtg_ssm-2.8.3.dev5}/mtg_ssm.egg-info/SOURCES.txt +1 -0
  12. {mtg_ssm-2.8.3.dev1 → mtg_ssm-2.8.3.dev5}/mtg_ssm.egg-info/scm_file_list.json +1 -0
  13. {mtg_ssm-2.8.3.dev1 → mtg_ssm-2.8.3.dev5}/mtg_ssm.egg-info/scm_version.json +2 -2
  14. {mtg_ssm-2.8.3.dev1 → mtg_ssm-2.8.3.dev5}/tests/containers/test_bundles.py +9 -2
  15. mtg_ssm-2.8.3.dev5/tests/data/.gitattributes +1 -0
  16. {mtg_ssm-2.8.3.dev1 → mtg_ssm-2.8.3.dev5}/tests/data/bulk_data.json +3 -4
  17. {mtg_ssm-2.8.3.dev1 → mtg_ssm-2.8.3.dev5}/tests/data/cards.json +4684 -2824
  18. {mtg_ssm-2.8.3.dev1 → mtg_ssm-2.8.3.dev5}/tests/data/migrations.json +10 -0
  19. {mtg_ssm-2.8.3.dev1 → mtg_ssm-2.8.3.dev5}/tests/data/sets.json +56 -54
  20. {mtg_ssm-2.8.3.dev1 → mtg_ssm-2.8.3.dev5}/tests/data/sets1.json +27 -27
  21. {mtg_ssm-2.8.3.dev1 → mtg_ssm-2.8.3.dev5}/tests/data/sets2.json +29 -27
  22. {mtg_ssm-2.8.3.dev1 → mtg_ssm-2.8.3.dev5}/tests/gen_testdata.py +2 -2
  23. mtg_ssm-2.8.3.dev5/tests/scryfall/test_fetcher.py +136 -0
  24. mtg_ssm-2.8.3.dev5/tests/scryfall/test_models.py +58 -0
  25. {mtg_ssm-2.8.3.dev1 → mtg_ssm-2.8.3.dev5}/tests/serialization/__snapshots__/test_xlsx_serializer.ambr +42 -42
  26. mtg_ssm-2.8.3.dev1/tests/scryfall/test_fetcher.py +0 -66
  27. mtg_ssm-2.8.3.dev1/tests/scryfall/test_models.py +0 -26
  28. {mtg_ssm-2.8.3.dev1 → mtg_ssm-2.8.3.dev5}/.editorconfig +0 -0
  29. {mtg_ssm-2.8.3.dev1 → mtg_ssm-2.8.3.dev5}/.github/dependabot.yml +0 -0
  30. {mtg_ssm-2.8.3.dev1 → mtg_ssm-2.8.3.dev5}/.gitignore +0 -0
  31. {mtg_ssm-2.8.3.dev1 → mtg_ssm-2.8.3.dev5}/.pre-commit-config.yaml +0 -0
  32. {mtg_ssm-2.8.3.dev1 → mtg_ssm-2.8.3.dev5}/LICENSE.txt +0 -0
  33. {mtg_ssm-2.8.3.dev1 → mtg_ssm-2.8.3.dev5}/TODO.md +0 -0
  34. {mtg_ssm-2.8.3.dev1 → mtg_ssm-2.8.3.dev5}/mtg_ssm/__init__.py +0 -0
  35. {mtg_ssm-2.8.3.dev1 → mtg_ssm-2.8.3.dev5}/mtg_ssm/containers/__init__.py +0 -0
  36. {mtg_ssm-2.8.3.dev1 → mtg_ssm-2.8.3.dev5}/mtg_ssm/containers/bundles.py +0 -0
  37. {mtg_ssm-2.8.3.dev1 → mtg_ssm-2.8.3.dev5}/mtg_ssm/containers/collection.py +0 -0
  38. {mtg_ssm-2.8.3.dev1 → mtg_ssm-2.8.3.dev5}/mtg_ssm/containers/counts.py +0 -0
  39. {mtg_ssm-2.8.3.dev1 → mtg_ssm-2.8.3.dev5}/mtg_ssm/containers/indexes.py +0 -0
  40. {mtg_ssm-2.8.3.dev1 → mtg_ssm-2.8.3.dev5}/mtg_ssm/containers/legacy.py +0 -0
  41. {mtg_ssm-2.8.3.dev1 → mtg_ssm-2.8.3.dev5}/mtg_ssm/mtg/__init__.py +0 -0
  42. {mtg_ssm-2.8.3.dev1 → mtg_ssm-2.8.3.dev5}/mtg_ssm/mtg/util.py +0 -0
  43. {mtg_ssm-2.8.3.dev1 → mtg_ssm-2.8.3.dev5}/mtg_ssm/py.typed +0 -0
  44. {mtg_ssm-2.8.3.dev1 → mtg_ssm-2.8.3.dev5}/mtg_ssm/scryfall/__init__.py +0 -0
  45. {mtg_ssm-2.8.3.dev1 → mtg_ssm-2.8.3.dev5}/mtg_ssm/serialization/__init__.py +0 -0
  46. {mtg_ssm-2.8.3.dev1 → mtg_ssm-2.8.3.dev5}/mtg_ssm/serialization/csv_serializer.py +0 -0
  47. {mtg_ssm-2.8.3.dev1 → mtg_ssm-2.8.3.dev5}/mtg_ssm/serialization/interface.py +0 -0
  48. {mtg_ssm-2.8.3.dev1 → mtg_ssm-2.8.3.dev5}/mtg_ssm/serialization/xlsx_serializer.py +0 -0
  49. {mtg_ssm-2.8.3.dev1 → mtg_ssm-2.8.3.dev5}/mtg_ssm/ssm.py +0 -0
  50. {mtg_ssm-2.8.3.dev1 → mtg_ssm-2.8.3.dev5}/mtg_ssm.egg-info/dependency_links.txt +0 -0
  51. {mtg_ssm-2.8.3.dev1 → mtg_ssm-2.8.3.dev5}/mtg_ssm.egg-info/entry_points.txt +0 -0
  52. {mtg_ssm-2.8.3.dev1 → mtg_ssm-2.8.3.dev5}/mtg_ssm.egg-info/requires.txt +0 -0
  53. {mtg_ssm-2.8.3.dev1 → mtg_ssm-2.8.3.dev5}/mtg_ssm.egg-info/top_level.txt +0 -0
  54. {mtg_ssm-2.8.3.dev1 → mtg_ssm-2.8.3.dev5}/pyproject.toml +0 -0
  55. {mtg_ssm-2.8.3.dev1 → mtg_ssm-2.8.3.dev5}/setup.cfg +0 -0
  56. {mtg_ssm-2.8.3.dev1 → mtg_ssm-2.8.3.dev5}/tests/__init__.py +0 -0
  57. {mtg_ssm-2.8.3.dev1 → mtg_ssm-2.8.3.dev5}/tests/conftest.py +0 -0
  58. {mtg_ssm-2.8.3.dev1 → mtg_ssm-2.8.3.dev5}/tests/containers/__init__.py +0 -0
  59. {mtg_ssm-2.8.3.dev1 → mtg_ssm-2.8.3.dev5}/tests/containers/test_counts.py +0 -0
  60. {mtg_ssm-2.8.3.dev1 → mtg_ssm-2.8.3.dev5}/tests/containers/test_indexes.py +0 -0
  61. {mtg_ssm-2.8.3.dev1 → mtg_ssm-2.8.3.dev5}/tests/containers/test_legacy.py +0 -0
  62. {mtg_ssm-2.8.3.dev1 → mtg_ssm-2.8.3.dev5}/tests/data/LICENSE.txt +0 -0
  63. {mtg_ssm-2.8.3.dev1 → mtg_ssm-2.8.3.dev5}/tests/mtg/__init__.py +0 -0
  64. {mtg_ssm-2.8.3.dev1 → mtg_ssm-2.8.3.dev5}/tests/mtg/test_util.py +0 -0
  65. {mtg_ssm-2.8.3.dev1 → mtg_ssm-2.8.3.dev5}/tests/scryfall/__init__.py +0 -0
  66. {mtg_ssm-2.8.3.dev1 → mtg_ssm-2.8.3.dev5}/tests/serialization/__init__.py +0 -0
  67. {mtg_ssm-2.8.3.dev1 → mtg_ssm-2.8.3.dev5}/tests/serialization/__snapshots__/test_csv_serializer.ambr +0 -0
  68. {mtg_ssm-2.8.3.dev1 → mtg_ssm-2.8.3.dev5}/tests/serialization/test_csv_serializer.py +0 -0
  69. {mtg_ssm-2.8.3.dev1 → mtg_ssm-2.8.3.dev5}/tests/serialization/test_interface.py +0 -0
  70. {mtg_ssm-2.8.3.dev1 → mtg_ssm-2.8.3.dev5}/tests/serialization/test_xlsx_serializer.py +0 -0
  71. {mtg_ssm-2.8.3.dev1 → mtg_ssm-2.8.3.dev5}/tests/test_ssm.py +0 -0
  72. {mtg_ssm-2.8.3.dev1 → mtg_ssm-2.8.3.dev5}/uv.lock +0 -0
@@ -22,7 +22,7 @@ jobs:
22
22
  sync-args: "--extra lxml"
23
23
  name: End-to-end tests (${{ matrix.name }})
24
24
  steps:
25
- - uses: actions/checkout@v6
25
+ - uses: actions/checkout@v7
26
26
 
27
27
  - uses: astral-sh/setup-uv@v7
28
28
  with:
@@ -11,7 +11,7 @@ jobs:
11
11
  runs-on: ubuntu-latest
12
12
  name: Run all pre-commit checks
13
13
  steps:
14
- - uses: actions/checkout@v6
14
+ - uses: actions/checkout@v7
15
15
 
16
16
  - uses: astral-sh/setup-uv@v7
17
17
  with:
@@ -16,7 +16,7 @@ jobs:
16
16
  id-token: write
17
17
 
18
18
  steps:
19
- - uses: actions/checkout@v6
19
+ - uses: actions/checkout@v7
20
20
  with:
21
21
  # setuptools_scm needs the git history
22
22
  fetch-depth: 0
@@ -23,7 +23,7 @@ jobs:
23
23
  runs-on: ${{ matrix.os }}
24
24
  name: Can run on ${{ matrix.os }} - Python ${{ matrix.python-version }}
25
25
  steps:
26
- - uses: actions/checkout@v6
26
+ - uses: actions/checkout@v7
27
27
 
28
28
  - uses: astral-sh/setup-uv@v7
29
29
  with:
@@ -48,7 +48,7 @@ jobs:
48
48
  - "3.14"
49
49
  name: Python ${{ matrix.python-version }} pytest
50
50
  steps:
51
- - uses: actions/checkout@v6
51
+ - uses: actions/checkout@v7
52
52
 
53
53
  - uses: astral-sh/setup-uv@v7
54
54
  with:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mtg_ssm
3
- Version: 2.8.3.dev1
3
+ Version: 2.8.3.dev5
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-Expression: MIT
@@ -243,7 +243,8 @@ Changelog
243
243
  Development
244
244
  -----------
245
245
 
246
- - ...
246
+ - Fix Scryfall fetching with application request headers, JSON Lines bulk
247
+ downloads, and updated card layout and format values.
247
248
 
248
249
  2.8.1
249
250
  -----
@@ -204,7 +204,8 @@ Changelog
204
204
  Development
205
205
  -----------
206
206
 
207
- - ...
207
+ - Fix Scryfall fetching with application request headers, JSON Lines bulk
208
+ downloads, and updated card layout and format values.
208
209
 
209
210
  2.8.1
210
211
  -----
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '2.8.3.dev1'
22
- __version_tuple__ = version_tuple = (2, 8, 3, 'dev1')
21
+ __version__ = version = '2.8.3.dev5'
22
+ __version_tuple__ = version_tuple = (2, 8, 3, 'dev5')
23
23
 
24
- __commit_id__ = commit_id = 'g9288fb645'
24
+ __commit_id__ = commit_id = 'gbe794fd02'
@@ -1,12 +1,14 @@
1
1
  """Scryfall data fetcher."""
2
2
 
3
3
  import gzip
4
+ import io
4
5
  from pathlib import Path
5
6
 
6
7
  import msgspec
7
8
  from platformdirs import user_cache_dir
8
9
  from requests_cache import CachedSession, SerializerPipeline, Stage, pickle_serializer
9
10
 
11
+ from mtg_ssm import __version__
10
12
  from mtg_ssm.containers.bundles import ScryfallDataSet
11
13
  from mtg_ssm.scryfall.models import ScryBulkData, ScryCard, ScryList, ScryMigration, ScrySet
12
14
 
@@ -27,6 +29,12 @@ SESSION = CachedSession(
27
29
  cache_control=True,
28
30
  expire_after=86400,
29
31
  )
32
+ SESSION.headers.update(
33
+ {
34
+ "User-Agent": f"mtg-ssm/{__version__} (https://github.com/gwax/mtg_ssm)",
35
+ "Accept": "application/json;q=0.9,*/*;q=0.8",
36
+ }
37
+ )
30
38
 
31
39
  BULK_DATA_ENDPOINT = "https://api.scryfall.com/bulk-data"
32
40
  SETS_ENDPOINT = "https://api.scryfall.com/sets"
@@ -44,6 +52,22 @@ def _fetch_endpoint(endpoint: str) -> bytes:
44
52
  return response.content
45
53
 
46
54
 
55
+ def _fetch_cards(bulk: ScryBulkData) -> list[ScryCard]:
56
+ if bulk.jsonl_download_uri is not None:
57
+ content = _fetch_endpoint(bulk.jsonl_download_uri)
58
+ decoder = msgspec.json.Decoder(ScryCard)
59
+ # Requests may already have decoded gzip supplied as an HTTP encoding.
60
+ with (
61
+ io.BytesIO(content) as stream,
62
+ gzip.GzipFile(fileobj=stream) if content.startswith(b"\x1f\x8b") else stream as lines,
63
+ ):
64
+ return [decoder.decode(line) for line in lines if line.strip()]
65
+ if bulk.download_uri is not None:
66
+ return msgspec.json.decode(_fetch_endpoint(bulk.download_uri), type=list[ScryCard])
67
+ msg = f"No download URI for Scryfall bulk data: {bulk.type}"
68
+ raise ValueError(msg)
69
+
70
+
47
71
  def scryfetch() -> ScryfallDataSet:
48
72
  """Retrieve and deserialize Scryfall object data."""
49
73
  print("Reading data from scryfall")
@@ -67,7 +91,7 @@ def scryfetch() -> ScryfallDataSet:
67
91
  )
68
92
  migrations_data += migrations_list.data
69
93
 
70
- [cards_endpoint] = [bd.download_uri for bd in bulk_data if bd.type == BULK_TYPE]
71
- cards_data = msgspec.json.decode(_fetch_endpoint(cards_endpoint), type=list[ScryCard])
94
+ [cards_bulk] = [bd for bd in bulk_data if bd.type == BULK_TYPE]
95
+ cards_data = _fetch_cards(cards_bulk)
72
96
 
73
97
  return ScryfallDataSet(sets=sets_data, cards=cards_data, migrations=migrations_data)
@@ -62,6 +62,7 @@ class ScryCardLayout(str, Enum):
62
62
  DOUBLE_SIDED = "double_sided"
63
63
  EMBLEM = "emblem"
64
64
  FLIP = "flip"
65
+ FRONT_CARD = "front_card"
65
66
  HOST = "host"
66
67
  LEVELER = "leveler"
67
68
  MELD = "meld"
@@ -202,6 +203,7 @@ class ScryFormat(str, Enum):
202
203
  ALCHEMY = "alchemy"
203
204
  BRAWL = "brawl"
204
205
  COMMANDER = "commander"
206
+ COMPETITIVEBRAWL = "competitivebrawl"
205
207
  DUEL = "duel"
206
208
  EXPLORER = "explorer"
207
209
  FRONTIER = "frontier"
@@ -326,7 +328,7 @@ class CardPreviewBlock(Struct):
326
328
  """Model for card preview block."""
327
329
 
328
330
  source: str
329
- source_uri: str
331
+ source_uri: str | None
330
332
  previewed_at: dt.date
331
333
 
332
334
 
@@ -442,11 +444,12 @@ class ScryBulkData(
442
444
  type: str
443
445
  name: str
444
446
  description: str
445
- download_uri: str
447
+ download_uri: str | None = None
448
+ jsonl_download_uri: str | None = None
446
449
  updated_at: dt.datetime
447
450
  compressed_size: int | None = None
448
- content_type: str
449
- content_encoding: str
451
+ content_type: str | None = None
452
+ content_encoding: str | None = None
450
453
 
451
454
 
452
455
  class ScryMigration(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mtg_ssm
3
- Version: 2.8.3.dev1
3
+ Version: 2.8.3.dev5
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-Expression: MIT
@@ -243,7 +243,8 @@ Changelog
243
243
  Development
244
244
  -----------
245
245
 
246
- - ...
246
+ - Fix Scryfall fetching with application request headers, JSON Lines bulk
247
+ downloads, and updated card layout and format values.
247
248
 
248
249
  2.8.1
249
250
  -----
@@ -47,6 +47,7 @@ tests/containers/test_bundles.py
47
47
  tests/containers/test_counts.py
48
48
  tests/containers/test_indexes.py
49
49
  tests/containers/test_legacy.py
50
+ tests/data/.gitattributes
50
51
  tests/data/LICENSE.txt
51
52
  tests/data/bulk_data.json
52
53
  tests/data/cards.json
@@ -37,6 +37,7 @@
37
37
  "tests/containers/test_counts.py",
38
38
  "tests/containers/test_indexes.py",
39
39
  "tests/containers/test_legacy.py",
40
+ "tests/data/.gitattributes",
40
41
  "tests/data/LICENSE.txt",
41
42
  "tests/data/bulk_data.json",
42
43
  "tests/data/cards.json",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "tag": "2.8.2",
3
- "distance": 1,
4
- "node": "g9288fb6451bfb748a4faf00a5304cb0f3eeaeeb6",
3
+ "distance": 5,
4
+ "node": "gbe794fd029820ddaed88cc6321250acaf0be03a7",
5
5
  "dirty": false,
6
6
  "branch": "trunk",
7
7
  "node_date": "2026-10-16"
@@ -8,6 +8,7 @@ from mtg_ssm.scryfall.models import ScryCardLayout, ScrySetType
8
8
 
9
9
 
10
10
  def test_remove_digital(scryfall_data: bundles.ScryfallDataSet) -> None:
11
+ digital_pathway_id = UUID("edc71870-656c-40f9-b2b0-f69a46bf8c51")
11
12
  set_codes = {s.code for s in scryfall_data.sets}
12
13
  card_ids = {c.id for c in scryfall_data.cards}
13
14
  card_names = {c.name for c in scryfall_data.cards}
@@ -17,7 +18,10 @@ def test_remove_digital(scryfall_data: bundles.ScryfallDataSet) -> None:
17
18
 
18
19
  assert "khm" in set_codes
19
20
  assert "Cosmos Elixir" in card_names
20
- assert "A-Cosmos Elixir" in card_names
21
+
22
+ assert "znr" in set_codes
23
+ assert "Needleverge Pathway // Pillarverge Pathway" in card_names
24
+ assert digital_pathway_id in card_ids
21
25
 
22
26
  assert "yecl" in set_codes
23
27
  assert "Thoughtsteal Sprites" in card_names
@@ -32,7 +36,10 @@ def test_remove_digital(scryfall_data: bundles.ScryfallDataSet) -> None:
32
36
 
33
37
  assert "khm" in set_codes2
34
38
  assert "Cosmos Elixir" in card_names2
35
- assert "A-Cosmos Elixir" not in card_names2
39
+
40
+ assert "znr" in set_codes2
41
+ assert "Needleverge Pathway // Pillarverge Pathway" in card_names2
42
+ assert digital_pathway_id not in card_ids2
36
43
 
37
44
  assert "yecl" not in set_codes2
38
45
  assert "Thoughtsteal Sprites" not in card_names2
@@ -0,0 +1 @@
1
+ *.json linguist-generated=true
@@ -8,10 +8,9 @@
8
8
  "type": "default_cards",
9
9
  "name": "Default Cards",
10
10
  "description": "A JSON file containing every card object on Scryfall in English or the printed language if the card is only available in one language.",
11
- "download_uri": "https://data.scryfall.io/default-cards/default-cards-20260402212821.json",
12
- "updated_at": "2026-04-02T21:28:21.973000Z",
13
- "content_type": "application/json",
14
- "content_encoding": "gzip"
11
+ "jsonl_download_uri": "https://data.scryfall.io/default-cards/default-cards-20261016090544.jsonl.gz",
12
+ "updated_at": "2026-10-16T09:05:44.990000Z",
13
+ "compressed_size": 79720941
15
14
  }
16
15
  ],
17
16
  "has_more": false