spells-mtg 0.9.4__py3-none-any.whl → 0.9.7__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 CHANGED
@@ -7,4 +7,12 @@ from spells.log import setup_logging
7
7
 
8
8
  setup_logging()
9
9
 
10
- __all__ = ["summon", "view_select", "get_names", "ColSpec", "ColType", "ColName", "logging"]
10
+ __all__ = [
11
+ "summon",
12
+ "view_select",
13
+ "get_names",
14
+ "ColSpec",
15
+ "ColType",
16
+ "ColName",
17
+ "logging",
18
+ ]
spells/cache.py CHANGED
@@ -25,7 +25,7 @@ class DataDir(StrEnum):
25
25
 
26
26
 
27
27
  def spells_print(mode, content):
28
- print(f"🪄 {mode} ✨ {content}")
28
+ print(f" 🪄 {mode} ✨ {content}")
29
29
 
30
30
 
31
31
  def data_home() -> str:
spells/cards.py CHANGED
@@ -78,7 +78,13 @@ def card_df(draft_set_code: str, names: list[str]) -> pl.DataFrame:
78
78
  draft_set_json = _fetch_mtg_json(draft_set_code)
79
79
  booster_info = draft_set_json["data"]["booster"]
80
80
 
81
- booster_type = "play" if "play" in booster_info else "draft"
81
+ booster_type = (
82
+ "play"
83
+ if "play" in booster_info
84
+ else "draft"
85
+ if "draft" in booster_info
86
+ else list(booster_info.keys())[0]
87
+ )
82
88
  set_codes = booster_info[booster_type]["sourceSetCodes"]
83
89
  set_codes.reverse()
84
90
 
spells/draft_data.py CHANGED
@@ -148,6 +148,10 @@ def _determine_expression(
148
148
  )
149
149
  )
150
150
  except KeyError:
151
+ logging.warning(
152
+ f"KeyError raised in calculation of {col}, probably caused by "
153
+ + "missing context. Column will have value `None`"
154
+ )
151
155
  expr = tuple(pl.lit(None).alias(f"{col}_{name}") for name in names)
152
156
  else:
153
157
  expr = tuple(map(lambda name: pl.col(f"{col}_{name}"), names))
@@ -233,7 +237,11 @@ def _infer_dependencies(
233
237
  ):
234
238
  dependencies.add(split[0])
235
239
  found = True
236
- # fail silently here, so that columns can be passed in harmlessly
240
+ if not found:
241
+ logging.warning(
242
+ f"No column definition found matching dependency {item}! "
243
+ + "`summon` will fail if called with this column"
244
+ )
237
245
 
238
246
  return dependencies
239
247
 
@@ -455,7 +463,7 @@ def _base_agg_df(
455
463
  return joined_df
456
464
 
457
465
 
458
- @make_verbose
466
+ @make_verbose()
459
467
  def summon(
460
468
  set_code: str | list[str],
461
469
  columns: list[str] | None = None,
spells/external.py CHANGED
@@ -100,7 +100,7 @@ def cli() -> int:
100
100
 
101
101
 
102
102
  def _add(set_code: str, force_download=False):
103
- if set_code == 'all':
103
+ if set_code == "all":
104
104
  for code in all_sets:
105
105
  _add(code, force_download=force_download)
106
106
 
spells/log.py CHANGED
@@ -42,8 +42,7 @@ def rotate_logs():
42
42
  logging.debug("Log file manually rotated")
43
43
 
44
44
 
45
- @contextmanager
46
- def console_logging(log_level):
45
+ def add_console_handler(log_level):
47
46
  console_handler = logging.StreamHandler(sys.stdout)
48
47
  console_handler.setLevel(log_level)
49
48
  formatter = logging.Formatter(
@@ -52,17 +51,25 @@ def console_logging(log_level):
52
51
  console_handler.setFormatter(formatter)
53
52
  logger = logging.getLogger()
54
53
  logger.addHandler(console_handler)
54
+ return logger, console_handler
55
+
55
56
 
57
+ @contextmanager
58
+ def console_logging(log_level):
59
+ logger, console_handler = add_console_handler(log_level)
56
60
  try:
57
61
  yield
58
62
  finally:
59
63
  logger.removeHandler(console_handler)
60
64
 
61
65
 
62
- def make_verbose(func: Callable) -> Callable:
63
- @wraps(func)
64
- def wrapped(*args, logging: int=logging.ERROR, **kwargs):
65
- with console_logging(logging):
66
- return func(*args, **kwargs)
67
- return wrapped
66
+ def make_verbose(level: int = logging.ERROR) -> Callable:
67
+ def decorator(func: Callable) -> Callable:
68
+ @wraps(func)
69
+ def wrapped(*args, log_to_console: int = level, **kwargs):
70
+ with console_logging(log_to_console):
71
+ return func(*args, **kwargs)
72
+
73
+ return wrapped
68
74
 
75
+ return decorator
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: spells-mtg
3
- Version: 0.9.4
3
+ Version: 0.9.7
4
4
  Summary: analaysis of 17Lands.com public datasets
5
5
  Author-Email: Joel Barnes <oelarnes@gmail.com>
6
6
  License: MIT
@@ -15,21 +15,23 @@ Description-Content-Type: text/markdown
15
15
 
16
16
  ```
17
17
  $ spells add DSK
18
- 🪄 spells ✨ [data home]=/Users/joel/.local/share/spells/
18
+ 🪄 spells ✨ [data home]=/home/joel/.local/share/spells/
19
19
 
20
- 🪄 add ✨ Downloading draft dataset from 17Lands.com
20
+ 🪄 add ✨ Downloading draft dataset from 17Lands.com
21
21
  100% [......................................................................] 250466473 / 250466473
22
- 🪄 add ✨ Unzipping and transforming to parquet (this might take a few minutes)...
23
- 🪄 add ✨ Wrote file /Users/joel/.local/share/spells/external/DSK/DSK_PremierDraft_draft.parquet
24
- 🪄 clean ✨ No local cache found for set DSK
25
- 🪄 add ✨ Fetching card data from mtgjson.com and writing card parquet file
26
- 🪄 add ✨ Wrote file /Users/joel/.local/share/spells/external/DSK/DSK_card.parquet
27
- 🪄 add ✨ Downloading game dataset from 17Lands.com
22
+ 🪄 add ✨ Unzipping and transforming to parquet (this might take a few minutes)...
23
+ 🪄 add ✨ Wrote file /home/joel/.local/share/spells/external/DSK/DSK_PremierDraft_draft.parquet
24
+ 🪄 clean ✨ No local cache found for set DSK
25
+ 🪄 add ✨ Fetching card data from mtgjson.com and writing card file
26
+ 🪄 add ✨ Wrote file /home/joel/.local/share/spells/external/DSK/DSK_card.parquet
27
+ 🪄 add ✨ Calculating set context
28
+ 🪄 add ✨ Wrote file /home/joel/.local/share/spells/external/DSK/DSK_PremierDraft_context.parquet
29
+ 🪄 add ✨ Downloading game dataset from 17Lands.com
28
30
  100% [........................................................................] 77145600 / 77145600
29
- 🪄 add ✨ Unzipping and transforming to parquet (this might take a few minutes)...
30
- 🪄 add ✨ Wrote file /Users/joel/.local/share/spells/external/DSK/DSK_PremierDraft_game.parquet
31
- 🪄 clean ✨ No local cache found for set DSK
32
- $ ipython
31
+ 🪄 add ✨ Unzipping and transforming to parquet (this might take a few minutes)...
32
+ 🪄 add ✨ Wrote file /home/joel/.local/share/spells/external/DSK/DSK_PremierDraft_game.parquet
33
+ 🪄 clean ✨ Removed 1 files from local cache for set DSK
34
+ 🪄 clean ✨ Removed local cache dir /home/joel/.local/share/spells/cache/DSK
33
35
  ```
34
36
 
35
37
  ```python
@@ -70,6 +72,7 @@ Spells is not affiliated with 17Lands. Please review the [Usage Guidelines](http
70
72
 
71
73
  - Uses [Polars](https://docs.pola.rs/) for high-performance, multi-threaded aggregations of large datasets
72
74
  - Uses Polars to power an expressive query language for specifying custom extensions
75
+ - Analyzes larger-than-memory datasets using Polars streaming mode
73
76
  - Converts csv datasets to parquet for 10x faster calculations and 20x smaller file sizes
74
77
  - Supports calculating the standard aggregations and measures out of the box with no arguments (ALSA, GIH WR, etc)
75
78
  - Caches aggregate DataFrames in the local file system automatically for instantaneous reproduction of previous analysis
@@ -249,7 +252,7 @@ Spells caches the results of expensive aggregations in the local file system as
249
252
 
250
253
  ### Memory Usage
251
254
 
252
- One of my goals in creating Spells was to eliminate issues with memory pressure by exclusively using the map-reduce paradigm and a technology that supports partitioned/streaming aggregation of larget-than-memory datasets. By default, Polars loads the entire dataset in memory, but the API exposes a parameter `streaming` which I have exposed as `use_streaming`. Unfortunately, that feature does not seem to work for my queries and the memory performance can be quite poor. The one feature that may assist in memory management is the local caching, since you can restart the kernel without losing all of your progress. In particular, be careful about opening multiple Jupyter tabs unless you have at least 32 GB. In general I have not run into issues on my 16 GB MacBook Air except with running multiple kernels at once. Supporting larger-than memory computations is on my roadmap, so check back periodically to see if I've made any progress.
255
+ One of my goals in creating Spells was to eliminate issues with memory pressure by exclusively using the map-reduce paradigm and a technology that supports partitioned/streaming aggregation of larget-than-memory datasets. By default, Polars loads the entire dataset in memory, but the API exposes a parameter `streaming` which I have exposed as `use_streaming`. Further testing is needed to determine the performance impacts, but this is the first thing you should try if you run into memory issues.
253
256
 
254
257
  When refreshing a given set's data files from 17Lands using the provided cli, the cache for that set is automatically cleared. The `spells` CLI gives additional tools for managing the local and external caches.
255
258
 
@@ -289,9 +292,10 @@ To use `spells`, make sure Spells is installed in your environment using pip or
289
292
  ### Summon
290
293
 
291
294
  ```python
292
- from spell import summon
295
+ from spells import summon
293
296
 
294
297
  summon(
298
+ set_code: list[str] | str,
295
299
  columns: list[str] | None = None,
296
300
  group_by: list[str] | None = None,
297
301
  filter_spec: dict | None = None,
@@ -300,11 +304,16 @@ summon(
300
304
  set_context: pl.DataFrame | dict[str, Any] | None = None,
301
305
  read_cache: bool = True,
302
306
  write_cache: bool = True,
307
+ use_streaming: bool = False,
308
+ log_to_console: int = logging.ERROR,
303
309
  ) -> polars.DataFrame
304
310
  ```
305
311
 
306
312
  #### parameters
307
313
 
314
+ - `set_code`: a set code or list of set codes among those that you have added using `spells add`.
315
+ You can use "expansion" as a group_by to separate results from multiple sets, or you can aggregate them together.
316
+
308
317
  - `columns`: a list of string or `ColName` values to select as non-grouped columns. Valid `ColTypes` are `PICK_SUM`, `NAME_SUM`, `GAME_SUM`, and `AGG`. Min/Max/Unique
309
318
  aggregations of non-numeric (or numeric) data types are not supported. If `None`, use a set of columns modeled on the commonly used values on 17Lands.com/card_data.
310
319
 
@@ -326,6 +335,8 @@ aggregations of non-numeric (or numeric) data types are not supported. If `None`
326
335
 
327
336
  - `read_cache`/`write_cache`: Use the local file system to cache and retrieve aggregations to minimize expensive reads of the large datasets. You shouldn't need to touch these arguments unless you are debugging.
328
337
 
338
+ - 'log_to_console': Set to `logging.INFO` to see useful messages on the progress of your aggregation, or `logging.WARNING` to see warning messages about potentially invalid column definitions.
339
+
329
340
  ### Enums
330
341
 
331
342
  ```python
@@ -505,13 +516,10 @@ A table of all included columns. Columns can be referenced by enum or by string
505
516
  # Roadmap to 1.0
506
517
 
507
518
  - [ ] Support Traditional and Premier datasets (currently only Premier is supported)
508
- - [ ] Group by all
509
519
  - [ ] Enable configuration using $XDG_CONFIG_HOME/cfg.toml
510
- - [ ] Support min and max aggregations over base views
511
520
  - [ ] Enhanced profiling
512
521
  - [ ] Optimized caching strategy
513
522
  - [ ] Organize and analyze daily downloads from 17Lands (not a scraper!)
514
523
  - [ ] Helper functions to generate second-order analysis by card name
515
524
  - [ ] Helper functions for common plotting paradigms
516
- - [ ] Example notebooks
517
525
  - [ ] Scientific workflows: regression, MLE, etc
@@ -0,0 +1,18 @@
1
+ spells/__init__.py,sha256=0pnh2NLn9FrNKncE9-tBsighp3e8YAsN--_ovUpgWGs,333
2
+ spells/cache.py,sha256=7XDcltmlpagK2nkhuCCcAQVyrFMYkw9Acr7iVsLs3JU,3799
3
+ spells/cards.py,sha256=uYOnFXroAuUpMB0NuuBcAZ1n5OtNTQyq1mvK3ctls6Q,3710
4
+ spells/columns.py,sha256=PztPNLtqKIqIQdiCHYykk7bcYlx0_QRypu3WOsSZb-A,17856
5
+ spells/config.py,sha256=4Q4F_wNsjXjWONA5uLn_Xu2xPoNm0vcPGqNHWd7khIk,202
6
+ spells/draft_data.py,sha256=DpbKEcv-tTCJ1-R9-_lS-H8oejYl-2OEcUDSUDsRtJI,19626
7
+ spells/enums.py,sha256=DL7e1xDEvrsTMbA7vJB_Et1DaYkyO4rIEzvIQDz3MZk,4871
8
+ spells/extension.py,sha256=DVzIedggeGfkD6BD5g-dko9l9BoPgmXWvcQ3NWdEG0U,6991
9
+ spells/external.py,sha256=ukcEWiu6aLJYJgQlRMAH6EmdZ2OZUFPEr54KdWnR5ug,11510
10
+ spells/filter.py,sha256=J-YTOOAzOQpvIX29tviYL04RVoOUlfsbjBXoQBDCEdQ,3380
11
+ spells/log.py,sha256=_yQjVge-krVtom7CfTmgv_oc78jnt5NDKElTBgdqdfE,2086
12
+ spells/manifest.py,sha256=dOUmj2uZZ17vCWpFwv7B5F6wOIWnoQdZkEB9SDKdx9M,8310
13
+ spells/schema.py,sha256=DbMvV8PIThJTp0Xzp_XIorlW6JhE1ud1kWRGf5SQ4_c,6406
14
+ spells_mtg-0.9.7.dist-info/METADATA,sha256=jP7n_UCl7q2Ude2HClulr4ZsJhLJF1rVd5xwiL5HDIU,47007
15
+ spells_mtg-0.9.7.dist-info/WHEEL,sha256=thaaA2w1JzcGC48WYufAs8nrYZjJm8LqNfnXFOFyCC4,90
16
+ spells_mtg-0.9.7.dist-info/entry_points.txt,sha256=a9Y1omdl9MdnKuIj3aOodgrp-zZII6OCdvqwgP6BFvI,63
17
+ spells_mtg-0.9.7.dist-info/licenses/LICENSE,sha256=tS54XYbJSgmq5zuHhbsQGbNQLJPVgXqhF5nu2CSRMig,1068
18
+ spells_mtg-0.9.7.dist-info/RECORD,,
@@ -1,18 +0,0 @@
1
- spells/__init__.py,sha256=8RdhAbk2VN_ExGZN44JnsD6_4VG30XMBe90w5-1gc3U,302
2
- spells/cache.py,sha256=JTFA31quGUL8pxy04NLWjEhQd5GEp37Q4PWUMnMuUJU,3797
3
- spells/cards.py,sha256=EOXAB_F2yedjf6KquCERCIHl0TSIJIoOe1jv8g4JzOc,3601
4
- spells/columns.py,sha256=PztPNLtqKIqIQdiCHYykk7bcYlx0_QRypu3WOsSZb-A,17856
5
- spells/config.py,sha256=4Q4F_wNsjXjWONA5uLn_Xu2xPoNm0vcPGqNHWd7khIk,202
6
- spells/draft_data.py,sha256=3YHNn-od8shn9DwnLpOws1qcZXiVb96pbKPEG5JMpDk,19286
7
- spells/enums.py,sha256=DL7e1xDEvrsTMbA7vJB_Et1DaYkyO4rIEzvIQDz3MZk,4871
8
- spells/extension.py,sha256=DVzIedggeGfkD6BD5g-dko9l9BoPgmXWvcQ3NWdEG0U,6991
9
- spells/external.py,sha256=2qNZal_t4Dd1TU8hDGiZG6g4Vely4NALB6uwQbuMtmM,11510
10
- spells/filter.py,sha256=J-YTOOAzOQpvIX29tviYL04RVoOUlfsbjBXoQBDCEdQ,3380
11
- spells/log.py,sha256=73cYwVqEN5GiEMKNy_stDMApFNEeyVVJMeecVzdAEXU,1844
12
- spells/manifest.py,sha256=dOUmj2uZZ17vCWpFwv7B5F6wOIWnoQdZkEB9SDKdx9M,8310
13
- spells/schema.py,sha256=DbMvV8PIThJTp0Xzp_XIorlW6JhE1ud1kWRGf5SQ4_c,6406
14
- spells_mtg-0.9.4.dist-info/METADATA,sha256=QFFmNQRbq3TIPQR4bJl1qJYg1OApV49-FSCISrpWf2E,46731
15
- spells_mtg-0.9.4.dist-info/WHEEL,sha256=thaaA2w1JzcGC48WYufAs8nrYZjJm8LqNfnXFOFyCC4,90
16
- spells_mtg-0.9.4.dist-info/entry_points.txt,sha256=a9Y1omdl9MdnKuIj3aOodgrp-zZII6OCdvqwgP6BFvI,63
17
- spells_mtg-0.9.4.dist-info/licenses/LICENSE,sha256=tS54XYbJSgmq5zuHhbsQGbNQLJPVgXqhF5nu2CSRMig,1068
18
- spells_mtg-0.9.4.dist-info/RECORD,,