Skip to content

Commit eb7529d

Browse files
authored
Merge pull request #4 from thrly/implement-argparse
This PR adds: Refactored interface using argparse to better manage subcommands (rather than previous complicated use of argv). This also enabled some better logic handing for the program via the subcommand handler functions. Clarified focus of app somewhat to viewing aggregate stats for MTG sets / search lists Updated CLI commands (now setlist, set, search) setlist now finds the 'current' release (i.e. the set most recently released) and will give stats for it with scry set latest Adds a stat chart for colour distribution comparisons. By default, search results now include all unique prints of cards, not just the unique card (i.e. it now shows variation prints in the numbers) Documentation improvements made both to README, and through the cli --help
2 parents 722f4fb + 5245d8e commit eb7529d

File tree

8 files changed

+338
-105
lines changed

8 files changed

+338
-105
lines changed

README.md

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,19 @@ Clone and install with `pipx install scry`
88

99
## use
1010

11-
- Draw a random card and add it to your database with `scry random`
12-
- Get a list of cards based on a scryfall [search query](https://scryfall.com/docs/syntax) and add them to your database:
13-
- `scry list "set:blb"` returns unique cards from the Bloomburrow set and shows stats for that list.
14-
- Request a list of set releases with: `scry setcodes`
15-
- Return stats for your entire database: `scry stats`
16-
- (Optional:) Clear your database: `scry clear`
11+
- Request a reference list of set releases with: `scry setlist`
12+
- Get stats for a specific set:
13+
- `scry set BLB` returns all cards from the _Bloomburrow_ set
14+
- `scry set latest` finds the most recent release.
15+
- Get stats for cards based on a scryfall [search query](https://scryfall.com/docs/syntax):
16+
- `scry search id:orzhov type:land legal:modern` returns all unique Orzhov Land cards that are legal in Modern format, and shows stats for that list.
17+
- Get help with `scry --help`
18+
19+
### local database
20+
21+
Scry creates a local sqlite database and adds your queried cards to it. This means you can build a larger collection of cards by executing multiple searches, and then view stats for the entire database with `scry stats`
22+
23+
To clear your database (for instance, to start a fresh collection to view stats on), run `scry clear` and confirm at the prompt.
1724

1825
## about
1926

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ readme = "README.md"
1111

1212
# defines the command and script that will be used to run
1313
[project.scripts]
14-
scry = "scry.cli:main"
14+
scry = "scry.main:main"
1515

1616
[project.urls]
1717
repository = "https://github.com/thrly/scry"

src/scry/cli.py

Lines changed: 189 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,81 +1,192 @@
1-
# add cards to db using scryfall
2-
# query and return stats
3-
4-
1+
import argparse
52
from datetime import datetime
6-
from sys import argv
3+
4+
from scry.request import find_current_release
75
from . import (
8-
create_table,
96
get_random_card,
107
insert_cards,
118
get_card_list,
129
db_stats,
1310
get_total_cards,
1411
clear_database,
1512
set_codes,
16-
db_connect,
1713
)
1814

1915

20-
def main():
21-
connection = db_connect()
22-
try:
23-
create_table(connection)
24-
25-
if len(argv) > 1:
26-
# TODO: swap argv for argparse
27-
req_type = argv[1]
28-
# use argument inputs from CLI: random (single card) or list (multiple cards)
29-
if req_type == "random":
30-
# optional query argument for random card, otherwise no constraint
31-
if 1 < len(argv) > 2:
32-
query = argv[1]
33-
else:
34-
query = ""
35-
# get a single random card, based on search parameters
36-
card = get_random_card(query) or []
37-
insert_cards(card, get_timestamp(), connection)
38-
print(get_total_cards(connection), "cards currently in database.")
39-
40-
elif req_type == "list":
41-
if len(argv) > 2:
42-
search_param = argv[2]
43-
card_list = get_card_list(search_param) or []
44-
stamp = get_timestamp()
45-
insert_cards(card_list, stamp, connection)
46-
print(get_total_cards(connection), "cards currently in database.")
47-
print(
48-
f"================================================\nSTATS for '{search_param}':"
49-
)
50-
print_stats(connection, stamp)
51-
52-
else:
53-
print("Lists need a query parameter (i.e. 'color:black set:BLB')")
54-
55-
elif req_type == "setcodes":
56-
for set_code in set_codes():
57-
if set_code[3] == "expansion" or set_code[3] == "commander":
58-
# extract year from YYYY-MM-DD
59-
date = datetime.fromisoformat(set_code[2])
60-
print(
61-
f"{set_code[0]} : {set_code[1]}\t{set_code[4]} cards\t{date.year}"
62-
)
63-
elif req_type == "clear":
64-
clear_database()
65-
66-
elif req_type == "stats":
67-
print(
68-
"================================================\nSTATS for ALL cards in database:"
69-
)
70-
print_stats(connection)
16+
def build_arg_parser() -> argparse.ArgumentParser:
17+
# setup parser and sub command parsers
18+
parser = argparse.ArgumentParser(
19+
description="🃏 Stats for card sets from Scryfall.",
20+
formatter_class=argparse.RawDescriptionHelpFormatter,
21+
epilog=(
22+
"""
23+
Examples:
24+
scry setlist
25+
scry set BLB
26+
scry set latest
27+
scry search t:creature c:green legal:modern
28+
"""
29+
),
30+
)
31+
subparsers = parser.add_subparsers(
32+
# dest="subcommand",
33+
title="subcommands",
34+
description="Basic scrying functions. Some require additional parameters.",
35+
required=True,
36+
)
37+
38+
# Define each subcommand:
39+
# RANDOM ------------------------
40+
random_parser = subparsers.add_parser(
41+
"random",
42+
help="Draw random cards from Scryfall",
43+
)
44+
random_parser.add_argument(
45+
"-n",
46+
"--number",
47+
type=int,
48+
default=1,
49+
help="Number of random cards to draw",
50+
)
51+
random_parser.set_defaults(func=handle_random)
52+
53+
# SEARCH ------------------------
54+
# TODO: bypass this completely and just run with `scry <search_query>`
55+
list_parser = subparsers.add_parser(
56+
"search", help="Returns a list of cards matching search parameters"
57+
)
58+
list_parser.add_argument(
59+
"search_query",
60+
nargs="+",
61+
help="Arguments as Scryfall-syntax search query (e.g. 't:creature c:green')",
62+
# TODO: is it possible to set a default value if no query arg is given? 't:land' etc.
63+
)
64+
list_parser.set_defaults(func=handle_search)
65+
66+
# SET ------------------------
67+
set_parser = subparsers.add_parser("set")
68+
set_parser.add_argument(
69+
"set_query",
70+
help="Specify setcode (run `scry setlist` for reference) or 'latest'",
71+
)
72+
set_parser.set_defaults(func=handle_set)
73+
74+
# SETLIST ------------------------
75+
setlist_parser = subparsers.add_parser(
76+
"setlist",
77+
help="Return list of sets with code, card total, and year of release",
78+
)
79+
setlist_parser.set_defaults(func=handle_setlist)
80+
81+
# STATS -----------------------
82+
stats_parser = subparsers.add_parser(
83+
"stats", help="Return stats for current database"
84+
)
85+
# TODO: Add additional optional args to filter search query: type, colour, set, etc.
86+
#
87+
# stats_parser.add_argument(
88+
# "-s", "--set", help="search database for cards matching this setcode"
89+
# )
90+
# stats_parser.add_argument(
91+
# "-ct", "--card-type", help="search database for cards matching this type"
92+
# )
93+
94+
stats_parser.set_defaults(func=handle_stats)
95+
96+
# CLEAR ------------------------
97+
clear_parser = subparsers.add_parser("clear", help="Clear the database")
98+
clear_parser.set_defaults(func=handle_clear)
99+
100+
return parser
101+
102+
103+
################
104+
# Command handlers
105+
#
106+
107+
108+
def handle_random(args, db_connection):
109+
if args.number == 1:
110+
print("Drawing a random card from Scryfall.com...")
111+
else:
112+
print(f"Drawing {args.number} random cards from Scryfall.com...")
113+
114+
query = "" # TODO: add optional search flag to random argparse
115+
116+
# get a single random card, based on search parameters
117+
card = get_random_card(query) or []
118+
insert_cards(card, get_timestamp(), db_connection)
119+
print(get_total_cards(db_connection), "cards currently in database.")
120+
121+
122+
def handle_search(args, db_connection):
123+
query = " ".join(args.search_query)
124+
print(f"Searching for cards matching: {query}")
125+
126+
card_list = get_card_list(query) or []
127+
stamp = get_timestamp()
128+
insert_cards(card_list, stamp, db_connection)
129+
# print(get_total_cards(db_connection), "cards currently in database.")
130+
print(f"{len(card_list)} cards found.")
131+
print_stats(db_connection, stamp)
132+
133+
134+
def handle_set(args, db_connection):
135+
# Search for a set of cards
136+
connection = db_connection
137+
query_setcode = ""
138+
if args.set_query.lower() == "latest":
139+
# Find the moce recent release and query stats for it
140+
current_set = find_current_release(set_codes())
141+
current_set_code = current_set.get("set_code")
142+
query_setcode = str(current_set_code)
143+
current_set_name = current_set.get("name")
144+
print(f"Stats for {current_set_name} ({current_set_code}):")
145+
else:
146+
query_setcode = str(args.set_query)
147+
print(f"Stats for {lookup_set_info("name", args.set_query.upper())}")
148+
149+
query = f"set:{query_setcode} unique:prints" # unique:prints includes variations within set
150+
151+
print(f" Released: {lookup_set_info("release_date", query_setcode.upper())}")
152+
153+
print(f" {lookup_set_info("card_count", query_setcode.upper())} cards in set")
154+
155+
card_list = get_card_list(query) or []
156+
stamp = get_timestamp()
157+
158+
insert_cards(card_list, stamp, connection)
159+
print_stats(connection, stamp)
160+
161+
162+
def handle_setlist(args, db_connection):
163+
print("All Main and Commander MTG expansion sets:\n")
164+
setlist = set_codes()
165+
current_set = find_current_release(setlist)
166+
for set_info in setlist:
167+
print(
168+
format_set_info(set_info),
169+
end="",
170+
)
171+
if set_info == current_set:
172+
print(" <- current release")
71173
else:
72-
print(
73-
"No valid search parameters. Try `scry random` or `scry list '<scryfall query>'"
74-
)
75-
except Exception as err:
76-
print("Error in __main__: ", err)
77-
finally:
78-
connection.close() # finally close db connection
174+
print()
175+
176+
177+
def handle_stats(args, db_connection):
178+
print("STATS for ALL cards in database:")
179+
print(get_total_cards(db_connection), "cards in database")
180+
print_stats(db_connection)
181+
182+
183+
def handle_clear(args, db_connection):
184+
# HACK: why does this only work with db_connection and args, even though neither
185+
# are required? Same with handle_setlist...
186+
clear_database()
187+
188+
189+
# Helper functions:
79190

80191

81192
def print_stats(connection, timestamp=None):
@@ -84,9 +195,18 @@ def print_stats(connection, timestamp=None):
84195
print(s)
85196

86197

87-
def get_timestamp():
88-
return datetime.now()
198+
def format_set_info(set_details) -> str:
199+
date = datetime.fromisoformat(set_details["release_date"])
200+
return f"{set_details["set_code"]: <5} {set_details["name"]:<38} {set_details["card_count"]:>6} cards {date.year:>10}"
201+
202+
203+
def lookup_set_info(info: str, set_code: str) -> str:
204+
setlist = set_codes()
205+
for s in setlist:
206+
if set_code == s["set_code"]:
207+
return s[info]
208+
return "Set info not found. Check the setcode is correct. You can request name, card_count, release_date"
89209

90210

91-
if __name__ == "__main__":
92-
main()
211+
def get_timestamp():
212+
return datetime.now()

src/scry/db_insert.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,6 @@ def insert_cards(cards: list, timestamp: datetime, connection) -> int:
1717
cursor.executemany(insert_query, rows)
1818
connection.commit()
1919

20-
if len(cards) > 1:
21-
print(f"Added {len(cards)} cards into database.")
22-
else:
23-
print(f"Added '{cards[0]['name']}' into database.")
24-
2520
return len(cards)
2621
except Exception as err:
2722
print(f"Error occured talking to database: {err}")

0 commit comments

Comments
 (0)