← 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 →