← back to Dw Product Class

push_class.py

67 lines

import os, sys, json, time, urllib.request

API   = os.environ["SHOP_API"]
TOKEN = os.environ["TOKEN"]
INFILE= os.environ["INFILE"]
OKF   = os.environ["OKFILE"]
ERRF  = os.environ["ERRFILE"]
BATCH = int(os.environ.get("BATCH","25"))

MUT = ("mutation($mf:[MetafieldsSetInput!]!){metafieldsSet(metafields:$mf){"
       "metafields{id} userErrors{field message code}}}")

def post(payload):
    req = urllib.request.Request(API, data=json.dumps(payload).encode(),
        headers={"X-Shopify-Access-Token":TOKEN,"Content-Type":"application/json"})
    with urllib.request.urlopen(req, timeout=60) as r:
        return json.load(r)

rows=[]
with open(INFILE) as f:
    for line in f:
        line=line.rstrip("\n")
        if not line: continue
        gid,cls=line.split("\t")
        rows.append((gid,cls))

ok=open(OKF,"a"); err=open(ERRF,"a")
done=0; failed=0
i=0
while i < len(rows):
    chunk=rows[i:i+BATCH]
    mf=[{"ownerId":g,"namespace":"custom","key":"product_class",
         "type":"single_line_text_field","value":c} for g,c in chunk]
    attempt=0
    while True:
        attempt+=1
        try:
            res=post({"query":MUT,"variables":{"mf":mf}})
        except Exception as e:
            if attempt<=5:
                time.sleep(2*attempt); continue
            for g,c in chunk: err.write(f"{g}\t{c}\tHTTP:{e}\n"); failed+=1
            res=None; break
        if "errors" in res and res["errors"]:
            msg=json.dumps(res["errors"])[:200]
            if "THROTTLED" in msg and attempt<=8:
                time.sleep(1.5*attempt); continue
            for g,c in chunk: err.write(f"{g}\t{c}\tGQL:{msg}\n"); failed+=1
            break
        ue=res["data"]["metafieldsSet"]["userErrors"]
        if ue:
            for g,c in chunk: err.write(f"{g}\t{c}\tUE:{json.dumps(ue)[:150]}\n"); failed+=1
        else:
            for g,c in chunk: ok.write(g+"\n"); done+=1
        # throttle guard
        thr=res.get("extensions",{}).get("cost",{}).get("throttleStatus",{})
        avail=thr.get("currentlyAvailable",4000)
        if avail<600: time.sleep(1.0)
        break
    i+=BATCH
    if i % 500 == 0:
        ok.flush(); err.flush()
        print(f"  ...{i}/{len(rows)} processed (ok={done} fail={failed})", flush=True)

ok.close(); err.close()
print(f"DONE processed={len(rows)} ok={done} failed={failed}", flush=True)