spells-mtg 0.0.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.
Potentially problematic release.
This version of spells-mtg might be problematic. Click here for more details.
- spells/__init__.py +5 -0
- spells/cache.py +106 -0
- spells/cards.py +96 -0
- spells/columns.py +771 -0
- spells/draft_data.py +300 -0
- spells/enums.py +154 -0
- spells/external.py +314 -0
- spells/filter.py +137 -0
- spells/manifest.py +184 -0
- spells/schema.py +157 -0
- spells_mtg-0.0.1.dist-info/METADATA +465 -0
- spells_mtg-0.0.1.dist-info/RECORD +15 -0
- spells_mtg-0.0.1.dist-info/WHEEL +4 -0
- spells_mtg-0.0.1.dist-info/entry_points.txt +5 -0
- spells_mtg-0.0.1.dist-info/licenses/LICENSE +21 -0
spells/__init__.py
ADDED
spells/cache.py
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Module for caching the result of distributed dataframe calculations to parquet files.
|
|
3
|
+
|
|
4
|
+
Caches are keyed by a hash that is function of set code, aggregation type, base filter,
|
|
5
|
+
and groupbys.
|
|
6
|
+
|
|
7
|
+
Caches are cleared per-set when new files are downloaded.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from enum import StrEnum
|
|
11
|
+
import os
|
|
12
|
+
import sys
|
|
13
|
+
|
|
14
|
+
import polars as pl
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class DataDir(StrEnum):
|
|
18
|
+
CACHE = "cache"
|
|
19
|
+
EXTERNAL = "external"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def spells_print(mode, content):
|
|
23
|
+
print(f"🪄 {mode} ✨ {content}")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def data_home() -> str:
|
|
27
|
+
is_win = sys.platform == "win32"
|
|
28
|
+
return os.path.expanduser(
|
|
29
|
+
os.environ.get(
|
|
30
|
+
"SPELLS_DATA_HOME",
|
|
31
|
+
os.environ.get(
|
|
32
|
+
"XDG_DATA_HOME",
|
|
33
|
+
r"C:\Users\$USERNAME\AppData\Local\Spells"
|
|
34
|
+
if is_win
|
|
35
|
+
else "~/.local/share/spells/",
|
|
36
|
+
),
|
|
37
|
+
)
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def data_dir_path(cache_dir: DataDir) -> str:
|
|
42
|
+
"""
|
|
43
|
+
Where 17Lands data is stored. MDU_DATA_DIR environment variable is used, if it exists,
|
|
44
|
+
otherwise the cwd is used
|
|
45
|
+
"""
|
|
46
|
+
is_win = sys.platform == "win32"
|
|
47
|
+
|
|
48
|
+
ext = {
|
|
49
|
+
DataDir.CACHE: "Cache" if is_win else "cache",
|
|
50
|
+
DataDir.EXTERNAL: "External" if is_win else "external",
|
|
51
|
+
}[cache_dir]
|
|
52
|
+
|
|
53
|
+
data_dir = os.path.join(data_home(), ext)
|
|
54
|
+
return data_dir
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def cache_dir_for_set(set_code: str) -> str:
|
|
58
|
+
return os.path.join(data_dir_path(DataDir.CACHE), set_code)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def cache_path_for_key(set_code: str, cache_key: str) -> str:
|
|
62
|
+
return os.path.join(cache_dir_for_set(set_code), cache_key + ".parquet")
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def cache_exists(set_code: str, cache_key: str) -> bool:
|
|
66
|
+
return os.path.isdir(cache_dir_for_set(set_code)) and os.path.isfile(
|
|
67
|
+
cache_path_for_key(set_code, cache_key)
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def read_cache(set_code: str, cache_key: str) -> pl.DataFrame:
|
|
72
|
+
return pl.read_parquet(cache_path_for_key(set_code, cache_key))
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def write_cache(set_code: str, cache_key: str, df: pl.DataFrame) -> None:
|
|
76
|
+
cache_dir = cache_dir_for_set(set_code)
|
|
77
|
+
if not os.path.isdir(cache_dir):
|
|
78
|
+
os.makedirs(cache_dir)
|
|
79
|
+
|
|
80
|
+
df.write_parquet(cache_path_for_key(set_code, cache_key))
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def clear(set_code: str) -> int:
|
|
84
|
+
mode = "clean"
|
|
85
|
+
cache_dir = cache_dir_for_set(set_code)
|
|
86
|
+
if os.path.isdir(cache_dir):
|
|
87
|
+
with os.scandir(cache_dir) as set_dir:
|
|
88
|
+
count = 0
|
|
89
|
+
for entry in set_dir:
|
|
90
|
+
if not entry.name.endswith(".parquet"):
|
|
91
|
+
spells_print(
|
|
92
|
+
mode,
|
|
93
|
+
f"Unexpected file {entry.name} found in local cache, please sort that out!",
|
|
94
|
+
)
|
|
95
|
+
return 1
|
|
96
|
+
count += 1
|
|
97
|
+
os.remove(entry)
|
|
98
|
+
spells_print(
|
|
99
|
+
mode, f"Removed {count} files from local cache for set {set_code}"
|
|
100
|
+
)
|
|
101
|
+
os.rmdir(cache_dir)
|
|
102
|
+
spells_print(mode, f"Removed local cache dir {cache_dir}")
|
|
103
|
+
return 0
|
|
104
|
+
else:
|
|
105
|
+
spells_print(mode, f"No local cache found for set {set_code}")
|
|
106
|
+
return 0
|
spells/cards.py
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import urllib.request
|
|
3
|
+
from enum import StrEnum
|
|
4
|
+
|
|
5
|
+
import polars as pl
|
|
6
|
+
|
|
7
|
+
from spells.enums import ColName
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class CardAttr(StrEnum):
|
|
11
|
+
NAME = ColName.NAME
|
|
12
|
+
SET_CODE = ColName.SET_CODE
|
|
13
|
+
COLOR = ColName.COLOR
|
|
14
|
+
RARITY = ColName.RARITY
|
|
15
|
+
COLOR_IDENTITY = ColName.COLOR_IDENTITY
|
|
16
|
+
CARD_TYPE = ColName.CARD_TYPE
|
|
17
|
+
SUBTYPE = ColName.SUBTYPE
|
|
18
|
+
MANA_VALUE = ColName.MANA_VALUE
|
|
19
|
+
MANA_COST = ColName.MANA_COST
|
|
20
|
+
POWER = ColName.POWER
|
|
21
|
+
TOUGHNESS = ColName.TOUGHNESS
|
|
22
|
+
IS_BONUS_SHEET = ColName.IS_BONUS_SHEET
|
|
23
|
+
IS_DFC = ColName.IS_DFC
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
MTG_JSON_TEMPLATE = "https://mtgjson.com/api/v5/{set_code}.json"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _fetch_mtg_json(set_code: str) -> dict:
|
|
30
|
+
request = urllib.request.Request(
|
|
31
|
+
MTG_JSON_TEMPLATE.format(set_code=set_code),
|
|
32
|
+
headers={"User-Agent": "spells-mtg/0.1.0"},
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
with urllib.request.urlopen(request) as f:
|
|
36
|
+
draft_set_json = json.loads(f.read().decode("utf-8"))
|
|
37
|
+
|
|
38
|
+
return draft_set_json
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _extract_value(set_code: str, name: str, card_dict: dict, field: CardAttr):
|
|
42
|
+
match field:
|
|
43
|
+
case CardAttr.NAME:
|
|
44
|
+
return name
|
|
45
|
+
case CardAttr.SET_CODE:
|
|
46
|
+
return card_dict.get("setCode", "")
|
|
47
|
+
case CardAttr.COLOR:
|
|
48
|
+
return "".join(card_dict.get("colors", []))
|
|
49
|
+
case CardAttr.RARITY:
|
|
50
|
+
return card_dict.get("rarity", "")
|
|
51
|
+
case CardAttr.COLOR_IDENTITY:
|
|
52
|
+
return "".join(card_dict.get("colorIdentity", []))
|
|
53
|
+
case CardAttr.CARD_TYPE:
|
|
54
|
+
return " ".join(card_dict.get("types", []))
|
|
55
|
+
case CardAttr.SUBTYPE:
|
|
56
|
+
return " ".join(card_dict.get("subtypes", []))
|
|
57
|
+
case CardAttr.MANA_VALUE:
|
|
58
|
+
return card_dict.get("manaValue", 0)
|
|
59
|
+
case CardAttr.MANA_COST:
|
|
60
|
+
return card_dict.get("manaCost", "")
|
|
61
|
+
case CardAttr.POWER:
|
|
62
|
+
return card_dict.get("power", None)
|
|
63
|
+
case CardAttr.TOUGHNESS:
|
|
64
|
+
return card_dict.get("toughness", None)
|
|
65
|
+
case CardAttr.IS_BONUS_SHEET:
|
|
66
|
+
return card_dict.get("setCode", set_code) != set_code
|
|
67
|
+
case CardAttr.IS_DFC:
|
|
68
|
+
return len(card_dict.get("otherFaceIds", [])) > 0
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def card_df(draft_set_code: str, names: list[str]) -> pl.DataFrame:
|
|
72
|
+
draft_set_json = _fetch_mtg_json(draft_set_code)
|
|
73
|
+
set_codes = draft_set_json["data"]["booster"]["play"]["sourceSetCodes"]
|
|
74
|
+
set_codes.reverse()
|
|
75
|
+
|
|
76
|
+
card_data_map = {}
|
|
77
|
+
for set_code in set_codes:
|
|
78
|
+
if set_code != draft_set_code:
|
|
79
|
+
card_data = _fetch_mtg_json(set_code)["data"]["cards"]
|
|
80
|
+
else:
|
|
81
|
+
card_data = draft_set_json["data"]["cards"]
|
|
82
|
+
|
|
83
|
+
card_data.reverse() # prefer front face for split cards
|
|
84
|
+
face_name_cards = [item for item in card_data if "faceName" in item]
|
|
85
|
+
card_data_map.update({item["faceName"]: item for item in face_name_cards})
|
|
86
|
+
card_data_map.update({item["name"]: item for item in card_data})
|
|
87
|
+
|
|
88
|
+
return pl.DataFrame(
|
|
89
|
+
[
|
|
90
|
+
{
|
|
91
|
+
field: _extract_value(draft_set_code, name, card_data_map[name], field)
|
|
92
|
+
for field in CardAttr
|
|
93
|
+
}
|
|
94
|
+
for name in names
|
|
95
|
+
]
|
|
96
|
+
)
|