main.py

4.7 kB · python · 146 lines

1import argparse2import sys3from catalog import fetch4from catalog import s35from catalog.correct import correct_products6from catalog.helpers import all_ids, live_ids, load_catalog, load_corrections7from catalog.helpers import load_product, sort_catalog8from catalog.parse import parse_products9from catalog.sort import sort_products1011AOP = "All-Over Print"12CUT_SEW = "CUT-SEW"1314def normalize(name: str) -> str:15    return name.replace("’", "'")1617def is_aop(item: dict) -> bool:18    return item["type"] == CUT_SEW and AOP in normalize(item["name"])1920# GATES2122def gate_rows(rows: list[dict], catalog: list[dict]) -> list[str]:23    live = {item["id"]: item for item in rows}24    corrections = load_corrections()["variants"]25    failures = []26    for row in catalog:27        id = row["id"]28        item = live.get(id)29        if item is None:30            failures.append(f"{id} {row['title']}: absent from the live catalog")31            continue32        if item["is_discontinued"]:33            failures.append(f"{id} {row['title']}: is_discontinued")34        techniques = [t["key"] for t in item["techniques"]]35        if techniques != ["cut-sew"]:36            failures.append(f"{id} {row['title']}: techniques {techniques}")37        if len(item["colors"]) != 1 and str(id) not in corrections:38            names = [c["name"] for c in item["colors"]]39            print(f"Colours: {id} {row['title']} - {names}")40    return failures4142def gate_products(ids: list[int]) -> list[str]:43    failures = []44    for id in ids:45        product = load_product(id)46        colors = {v.color for v in product.variants if not v.is_ignored}47        if len(colors) != 1:48            failures.append(f"{id} {product.title}: {len(colors)} live colours {sorted(colors)}")49        if not [p for p in product.placements if not p.is_ignored]:50            failures.append(f"{id} {product.title}: no live placement")51        if not [m for m in product.mockups if not m.is_ignored]:52            failures.append(f"{id} {product.title}: no live mockup")53    return failures5455def fail(failures: list[str]) -> None:56    if not failures:57        return58    print(f"Failed {len(failures)} checks")59    for line in failures:60        print(f"- {line}")61    sys.exit(1)6263# VERBS6465def build(args) -> None:66    catalog = load_catalog()67    ids = all_ids() if args.all else live_ids()68    if args.id:69        ids = args.id70    print(f"Building {len(ids)} of {len(catalog)} catalog rows")71    rows = fetch.load_raw_catalog(args.refetch)72    fail(gate_rows(rows, catalog))73    print(f"Gate: {len(catalog)} rows are live, cut-sew and current")74    sort_catalog()75    fetch.fetch_products(ids, args.refetch)76    parse_products(ids)77    sort_products(ids)78    correct_products(ids)79    fail(gate_products(ids))80    print(f"Gate: {len(ids)} products carry one colour, a placement and a mockup")81    if args.local:82        print("Local only, nothing uploaded")83        return84    s3.upload_products(ids)8586def new(args) -> None:87    rows = fetch.load_raw_catalog(args.refetch)88    known = set(all_ids())89    for item in sorted(rows, key=lambda i: i["id"]):90        if is_aop(item) and item["id"] not in known:91            print(f"{item['id']} - {normalize(item['name'])}")9293def gate(args) -> None:94    rows = fetch.load_raw_catalog(args.refetch)95    catalog = load_catalog()96    fail(gate_rows(rows, catalog))97    print(f"Gate: {len(catalog)} rows are live, cut-sew and current")9899def show(args) -> None:100    for row in load_catalog():101        mark = "LIVE" if row.get("live") else "    "102        print(f"{mark} {row['id']:>5} {row['category']:<12} {row['title']}")103104def clean(args) -> None:105    s3.delete_products()106107# CLI108109def parser() -> argparse.ArgumentParser:110    p = argparse.ArgumentParser(prog="catalog.main")111    subs = p.add_subparsers(dest="verb", required=True)112113    b = subs.add_parser("build")114    b.add_argument("--all", action="store_true")115    b.add_argument("--id", type=int, nargs="+")116    b.add_argument("--refetch", action="store_true")117    b.add_argument("--local", action="store_true")118    b.add_argument("--yes", action="store_true")119    b.set_defaults(run=build, gated=True)120121    n = subs.add_parser("new")122    n.add_argument("--refetch", action="store_true")123    n.set_defaults(run=new, gated=False)124125    g = subs.add_parser("gate")126    g.add_argument("--refetch", action="store_true")127    g.set_defaults(run=gate, gated=False)128129    s = subs.add_parser("show")130    s.set_defaults(run=show, gated=False)131132    c = subs.add_parser("clean")133    c.add_argument("--yes", action="store_true")134    c.set_defaults(run=clean, gated=True)135136    return p137138def main() -> None:139    args = parser().parse_args()140    if args.gated and not args.yes:141        print(f"PLAN {args.verb}. Add --yes to run it")142        sys.exit(1)143    args.run(args)144145if __name__ == "__main__":146    main()