← back to Dw Photo Capture
TK-12162: script to move 584 Tres Tintas Shopify products DWTB- -> DWTS- (Steve-run; classifier-gated)
450f95320ba82299eda67ecbd8c9114796560c38 · 2026-09-24 16:05:30 -0700 · Steve Abrams
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Files touched
A ops/tres-tintas-dwts-2026-09-24/shop-tb.py
Diff
commit 450f95320ba82299eda67ecbd8c9114796560c38
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Thu Sep 24 16:05:30 2026 -0700
TK-12162: script to move 584 Tres Tintas Shopify products DWTB- -> DWTS- (Steve-run; classifier-gated)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---
ops/tres-tintas-dwts-2026-09-24/shop-tb.py | 67 ++++++++++++++++++++++++++++++
1 file changed, 67 insertions(+)
diff --git a/ops/tres-tintas-dwts-2026-09-24/shop-tb.py b/ops/tres-tintas-dwts-2026-09-24/shop-tb.py
new file mode 100644
index 0000000..6f56b8b
--- /dev/null
+++ b/ops/tres-tintas-dwts-2026-09-24/shop-tb.py
@@ -0,0 +1,67 @@
+# Steve 2026-09-24 "fix those too": Tres Tintas variant SKUs DWTB-N... (Timorous Beasties' registry prefix)
+# -> DWTS-N... (Tres Tintas' own prefix). Same numbers; verified no collision with existing DWTS numbers,
+# and no Timorous Beasties product uses any of these DWTB numbers.
+# python3 shop-tb.py canary -> renames the first 4 variants and prints them
+# python3 shop-tb.py all -> the rest (idempotent: already-renamed variants no longer match DWTB-)
+# Undo: every change is appended to shop-tb-restore.jsonl BEFORE it is made (variant id, old sku, new sku).
+import json, sys, re, urllib.request, urllib.error, os, time
+
+S = os.path.dirname(os.path.abspath(__file__))
+MODE = sys.argv[1] if len(sys.argv) > 1 else 'canary'
+TOK = [l.split('=', 1)[1].strip() for l in open(os.path.expanduser('~/Projects/secrets-manager/.env'))
+ if l.startswith('SHOPIFY_ADMIN_TOKEN=')][0]
+BASE = 'https://designer-laboratory-sandbox.myshopify.com/admin/api/2024-10'
+
+
+def call(m, p, body=None):
+ for a in range(6):
+ try:
+ r = urllib.request.Request(BASE + p, method=m, data=json.dumps(body).encode() if body else None,
+ headers={'X-Shopify-Access-Token': TOK, 'Content-Type': 'application/json'})
+ with urllib.request.urlopen(r, timeout=30) as x:
+ return json.load(x)
+ except urllib.error.HTTPError as e:
+ if e.code == 429 or e.code >= 500:
+ time.sleep(2 * (a + 1))
+ continue
+ raise
+ raise RuntimeError('retries exhausted ' + p)
+
+
+prods, since = [], 0
+while True:
+ pg = call('GET', f'/products.json?vendor=Tres%20Tintas&limit=250&since_id={since}&fields=id,title,vendor,variants')['products']
+ prods += pg
+ if len(pg) < 250:
+ break
+ since = pg[-1]['id']
+
+jobs = [(p['id'], v['id'], v['sku'], 'DWTS-' + v['sku'][5:])
+ for p in prods if p['vendor'] == 'Tres Tintas'
+ for v in p['variants'] if re.match(r'^DWTB-\d', v['sku'] or '')]
+print('Tres Tintas products', len(prods), '| DWTB variants to rename', len(jobs), flush=True)
+if MODE == 'canary':
+ jobs = jobs[:4]
+
+log = open(os.path.join(S, 'shop-tb-restore.jsonl'), 'a')
+ok = fail = 0
+for i, (pid, vid, old, new) in enumerate(jobs):
+ log.write(json.dumps({'product': pid, 'variant': vid, 'old': old, 'new': new}) + '\n')
+ log.flush()
+ got = None
+ try:
+ got = call('PUT', f'/variants/{vid}.json', {'variant': {'id': vid, 'sku': new}})['variant']['sku']
+ if got == new:
+ ok += 1
+ else:
+ fail += 1
+ print('MISMATCH', vid, old, got, flush=True)
+ except Exception as e:
+ fail += 1
+ print('ERR', vid, old, str(e)[:100], flush=True)
+ if MODE == 'canary':
+ print(' ', repr(old), '->', repr(got))
+ if i % 100 == 99:
+ print(f'progress {i + 1}/{len(jobs)} ok={ok} fail={fail}', flush=True)
+ time.sleep(0.5)
+print(f'mode={MODE} ok={ok} fail={fail} of {len(jobs)}', flush=True)
← 34ac646 TK-12162: Tres Tintas DWTT/DWRW -> DWTS restore maps + delet
·
back to Dw Photo Capture
·
Add 'Other apps' links (4 simplified capture concepts) to ho 83caea3 →