[object Object]

← back to Vahallan Onboard

auto-save: 2026-07-23T10:50:25 (1 files) — import-drafts.py

69f510b385a05d98ec21b901c2eb296144c652e9 · 2026-07-23 10:50:28 -0700 · Steve Abrams

Files touched

Diff

commit 69f510b385a05d98ec21b901c2eb296144c652e9
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Thu Jul 23 10:50:28 2026 -0700

    auto-save: 2026-07-23T10:50:25 (1 files) — import-drafts.py
---
 import-drafts.py | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 58 insertions(+)

diff --git a/import-drafts.py b/import-drafts.py
new file mode 100644
index 0000000..7f98204
--- /dev/null
+++ b/import-drafts.py
@@ -0,0 +1,58 @@
+#!/usr/bin/env python3
+"""Create Shopify DRAFT products for vahallan_catalog rows not yet on Shopify.
+Template = existing Vahallan products (sample-only $4.25, Size=Sample, display_variant).
+Resumable: skips rows with shopify_product_id set or already_on_shopify. 429-aware.
+Steve-approved 2026-07-23 (AskUserQuestion: 'Yes — create the drafts')."""
+import json, subprocess, time, urllib.request, sys
+
+TOKEN = next(l.split('=',1)[1].strip() for l in open('/Users/macstudio3/Projects/secrets-manager/.env')
+             if l.startswith('SHOPIFY_ADMIN_TOKEN='))
+API = 'https://designer-laboratory-sandbox.myshopify.com/admin/api/2024-10/products.json'
+
+def q(sql):
+    r = subprocess.run(['psql','-h','/tmp','-d','dw_unified','-tAF','\x1f','-c',sql],capture_output=True,text=True)
+    if r.returncode: sys.exit('psql error: '+r.stderr[:300])
+    return [ln.split('\x1f') for ln in r.stdout.strip().split('\n') if ln]
+
+raw = subprocess.run(['psql','-h','/tmp','-d','dw_unified','-tA','-c',
+    """SELECT coalesce(json_agg(json_build_array(id,mfr_sku,handle,pattern,title,description_html,tags,image_url,dw_sku) ORDER BY dw_sku),'[]')
+       FROM vahallan_catalog WHERE NOT already_on_shopify AND shopify_product_id IS NULL"""],
+    capture_output=True,text=True)
+if raw.returncode: sys.exit('psql error: '+raw.stderr[:300])
+rows = json.loads(raw.stdout)
+print(f'to import: {len(rows)}', flush=True)
+done=0; errs=0
+for rid,mfr,handle,pattern,title,desc,tags,img,dw in rows:
+    tagset = {t.strip() for t in tags.split(',') if t.strip()}
+    tagset |= {'vahallan','wallcovering','Vahallan '+pattern, title, 'display_variant','hand-crafted','Textured'}
+    payload = {'product':{
+        'title': f'{title} Wallcovering | Vahallan',
+        'body_html': desc,
+        'vendor': 'Vahallan',
+        'product_type': 'Wallcovering',
+        'status': 'draft',
+        'tags': ', '.join(sorted(tagset)),
+        'options': [{'name':'Size','values':['Sample']}],
+        'variants': [{'option1':'Sample','price':'4.25','sku':f'{dw}-Sample',
+                      'inventory_management':'shopify','inventory_policy':'continue',
+                      'requires_shipping':True,'taxable':True,'grams':0}],
+        'images': [{'src':img,'alt':title}] if img else [],
+    }}
+    for attempt in range(4):
+        req = urllib.request.Request(API, data=json.dumps(payload).encode(),
+              headers={'X-Shopify-Access-Token':TOKEN,'Content-Type':'application/json'})
+        try:
+            resp = urllib.request.urlopen(req, timeout=40)
+            pid = json.loads(resp.read())['product']['id']
+            q(f"UPDATE vahallan_catalog SET shopify_product_id={pid}, imported_at=now() WHERE id={rid}")
+            done+=1
+            if done % 25 == 0: print(f'{done}/{len(rows)} created', flush=True)
+            break
+        except urllib.error.HTTPError as e:
+            if e.code == 429:
+                time.sleep(float(e.headers.get('Retry-After','3'))+1); continue
+            print(f'ERR {dw} {handle}: {e.code} {e.read()[:200]}', flush=True); errs+=1; break
+        except Exception as e:
+            print(f'ERR {dw} {handle}: {e}', flush=True); time.sleep(5)
+    time.sleep(0.55)
+print(f'DONE created={done} errors={errs}', flush=True)

← bee1a49 Vahallan onboard: scrape (390), vahallan_catalog staging loa  ·  back to Vahallan Onboard  ·  vahallan quote-only fix: add global.width (30in) + quotes ta 2c6f72b →