Theflame47 commited on
Commit
5237ea9
·
verified ·
1 Parent(s): 4db81f7

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +190 -136
app.py CHANGED
@@ -42,7 +42,9 @@ def _req(method: str, path: str, json_body: Optional[Dict[str, Any]] = None) ->
42
  )
43
  if r.status_code >= 400:
44
  raise RuntimeError(f"HTTP {r.status_code}: {r.text[:2000]}")
45
- return r.json()
 
 
46
 
47
 
48
  def _log(logs: List[str], msg: str):
@@ -195,20 +197,23 @@ def _pick_shop_id(shops_resp: Any) -> str:
195
  if not isinstance(items, list) or not items:
196
  raise RuntimeError("No shops found on /v1/shops.json for this token.")
197
 
198
- preferred_name = os.environ.get("PRINTIFY_SHOP_NAME", "Atheria")
 
 
 
 
 
 
 
199
 
200
  for s in items:
201
  if not isinstance(s, dict):
202
  continue
203
- title = s.get("title") or s.get("name")
204
- if title and title.strip().lower() == preferred_name.strip().lower():
205
- return str(s["id"])
206
-
207
- for s in items:
208
- if isinstance(s, dict) and s.get("id"):
209
  return str(s["id"])
210
 
211
- raise RuntimeError("Could not pick a shop id from response.")
212
 
213
 
214
  def _upload_grid_from_file(logs: List[str]) -> Dict[str, Any]:
@@ -217,14 +222,16 @@ def _upload_grid_from_file(logs: List[str]) -> Dict[str, Any]:
217
  raise RuntimeError(f"Grid image not found at {path}")
218
 
219
  with open(path, "rb") as f:
220
- b64 = base64.b64encode(f.read()).decode("ascii")
 
 
221
 
222
  payload = {
223
  "file_name": GRID_FILENAME,
224
  "contents": b64,
225
  }
226
 
227
- _log(logs, f"UPLOADING_GRID path={path} bytes={len(b64)}")
228
  resp = _req("POST", "/v1/uploads/images.json", json_body=payload)
229
 
230
  if not isinstance(resp, dict) or not resp.get("id"):
@@ -247,13 +254,33 @@ def _scale_fill(ph_w: float, ph_h: float, img_w: float, img_h: float) -> float:
247
  return float(s)
248
 
249
 
250
- def _create_one_product_with_grid(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
251
  logs: List[str],
252
  shop_id: str,
253
  blob: Dict[str, Any],
254
  product_info: Dict[str, Any],
255
  upload: Dict[str, Any],
256
- currency: str,
257
  ) -> Dict[str, Any]:
258
  bp_id = (blob.get("blueprint") or {}).get("id")
259
  provider_id = (blob.get("provider") or {}).get("id")
@@ -264,49 +291,66 @@ def _create_one_product_with_grid(
264
  if not isinstance(variants, list) or not variants:
265
  raise RuntimeError("No variants to create product with.")
266
 
267
- v = variants[0]
268
- variant_id = v.get("id")
269
- if variant_id is None:
270
- raise RuntimeError("Variant missing id.")
271
-
272
- placeholders = v.get("placeholders") or []
273
  img_id = upload.get("id")
274
  img_w = float(upload.get("width") or 0)
275
  img_h = float(upload.get("height") or 0)
276
 
277
- ph_payload = []
278
- for ph in placeholders:
279
- if not isinstance(ph, dict):
280
- continue
281
- pos = ph.get("position")
282
- pw = ph.get("width")
283
- phh = ph.get("height")
284
- if not pos or pw is None or phh is None:
285
  continue
286
- try:
287
- pwf = float(pw)
288
- phf = float(phh)
289
- except Exception:
290
  continue
291
- scale = _scale_fill(pwf, phf, img_w, img_h)
292
- ph_payload.append({
293
- "position": pos,
294
- "images": [{
295
- "id": img_id,
296
- "x": 0.5,
297
- "y": 0.5,
298
- "scale": scale,
299
- "angle": 0,
300
- }],
301
  })
302
- _log(
303
- logs,
304
- f"FILL_PLACEHOLDER pos={pos} ph={int(pwf)}x{int(phf)} "
305
- f"img={int(img_w)}x{int(img_h)} scale={round(scale, 6)}",
306
- )
307
 
308
- if not ph_payload:
309
- raise RuntimeError("No placeholders for chosen variant.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
310
 
311
  title = product_info.get("name") or "Printify Grid Test"
312
  description = product_info.get("description") or ""
@@ -316,21 +360,14 @@ def _create_one_product_with_grid(
316
  "description": description,
317
  "blueprint_id": int(bp_id),
318
  "print_provider_id": int(provider_id),
319
- "variants": [{
320
- "id": int(variant_id),
321
- "price": 1,
322
- "is_enabled": True,
323
- }],
324
- "print_areas": [{
325
- "variant_ids": [int(variant_id)],
326
- "placeholders": ph_payload,
327
- }],
328
  }
329
 
330
  _log(
331
  logs,
332
- f"PHASE_B_CREATE shop_id={shop_id} "
333
- f"blueprint_id={bp_id} provider_id={provider_id} variant_id={variant_id}",
334
  )
335
 
336
  created = _req(
@@ -345,6 +382,82 @@ def _create_one_product_with_grid(
345
  return created
346
 
347
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
348
  def run(currency: str) -> Generator[Tuple[str, str], None, None]:
349
  logs: List[str] = []
350
  result: Dict[str, Any] = {}
@@ -404,99 +517,44 @@ def phase_b(currency: str) -> Generator[Tuple[str, str], None, None]:
404
 
405
  product_info = _build_product(blob, currency or "USD", logs)
406
  result["phaseAProduct"] = product_info
 
407
  yield flush()
408
 
409
  upload = _upload_grid_from_file(logs)
410
  result["gridUpload"] = upload
411
  yield flush()
412
 
413
- created = _create_one_product_with_grid(
414
  logs,
415
  shop_id=shop_id,
416
  blob=blob,
417
  product_info=product_info,
418
  upload=upload,
419
- currency=currency or "USD",
420
  )
421
  result["createdProduct"] = created
422
- _log(logs, "PHASE_B_DONE")
423
  yield flush()
424
 
425
- except Exception as e:
426
- _log(logs, f"ERROR: {e}")
427
- result = {"error": str(e)}
428
- yield flush()
429
 
430
-
431
- def phase_c(currency: str, product_id: str) -> Generator[Tuple[str, str], None, None]:
432
- logs: List[str] = []
433
- result: Dict[str, Any] = {}
434
-
435
- def flush():
436
- return "\n".join(logs), json.dumps(result, indent=2)
437
-
438
- try:
439
- _log(logs, "PHASE_C_START")
440
  yield flush()
441
 
442
- if not product_id or not str(product_id).strip():
443
- raise RuntimeError("Missing product_id.")
444
-
445
- shops = _req("GET", "/v1/shops.json")
446
- _log(logs, f"SHOP_LIST {json.dumps(shops)}")
447
- yield flush()
448
-
449
- shop_id = _pick_shop_id(shops)
450
- _log(logs, f"SHOP_ID {shop_id}")
451
- yield flush()
452
-
453
- pid = str(product_id).strip()
454
- _log(logs, f"PHASE_C_GET_PRODUCT product_id={pid}")
455
- prod = _req("GET", f"/v1/shops/{shop_id}/products/{pid}.json")
456
- result["productDetails"] = prod
457
  yield flush()
458
 
459
- variants = prod.get("variants") or []
460
- if not isinstance(variants, list) or not variants:
461
- raise RuntimeError("Product details missing variants.")
462
-
463
- update_variants = []
464
- for v in variants:
465
- vid = v.get("id")
466
- cost = v.get("cost")
467
- if vid is None or cost is None:
468
- _log(logs, f"COST_MISSING variant_id={vid} cost={cost}")
469
- continue
470
- try:
471
- vid_i = int(vid)
472
- cost_i = int(cost)
473
- except Exception:
474
- _log(logs, f"COST_BAD variant_id={vid} cost={cost}")
475
- continue
476
-
477
- update_variants.append({
478
- "id": vid_i,
479
- "price": cost_i,
480
- "is_enabled": bool(v.get("is_enabled")),
481
- })
482
-
483
- _log(
484
- logs,
485
- f"PRICE_RECONCILE variant_id={vid_i} cost={cost_i} "
486
- f"old_price={v.get('price')} enabled={bool(v.get('is_enabled'))}",
487
- )
488
-
489
- if not update_variants:
490
- raise RuntimeError("No variants with cost found to reconcile pricing.")
491
-
492
- payload = {"variants": update_variants}
493
-
494
- _log(logs, f"PHASE_C_UPDATE_PRICES count={len(update_variants)}")
495
- updated = _req("PUT", f"/v1/shops/{shop_id}/products/{pid}.json", json_body=payload)
496
- result["priceUpdateResponse"] = updated
497
  yield flush()
498
 
499
- _log(logs, "PHASE_C_DONE")
 
 
500
  yield flush()
501
 
502
  except Exception as e:
@@ -509,16 +567,12 @@ with gr.Blocks(title="Printify Catalog Probe") as demo:
509
  gr.Markdown("Extract and normalize ONE Printify blueprint/provider into a structured JSON object.")
510
 
511
  currency = gr.Textbox(label="Currency", value="USD")
512
- product_id = gr.Textbox(label="Product ID (for Phase C)", value="")
513
-
514
- btn = gr.Button("Run (A)")
515
- btn_b = gr.Button("Phase B (Create Test)")
516
- btn_c = gr.Button("Phase C (Pricing Reconciliation)")
517
  logs = gr.Textbox(label="Logs", lines=18)
518
  out = gr.Textbox(label="Output JSON", lines=18)
519
 
520
  btn.click(run, inputs=[currency], outputs=[logs, out])
521
  btn_b.click(phase_b, inputs=[currency], outputs=[logs, out])
522
- btn_c.click(phase_c, inputs=[currency, product_id], outputs=[logs, out])
523
 
524
  demo.queue().launch()
 
42
  )
43
  if r.status_code >= 400:
44
  raise RuntimeError(f"HTTP {r.status_code}: {r.text[:2000]}")
45
+ if r.text:
46
+ return r.json()
47
+ return {}
48
 
49
 
50
  def _log(logs: List[str], msg: str):
 
197
  if not isinstance(items, list) or not items:
198
  raise RuntimeError("No shops found on /v1/shops.json for this token.")
199
 
200
+ forced = (os.environ.get("PRINTIFY_SHOP_ID") or "").strip()
201
+ if forced:
202
+ for s in items:
203
+ if isinstance(s, dict) and str(s.get("id")) == forced:
204
+ return str(s["id"])
205
+ raise RuntimeError(f"Preferred shop '{forced}' not found; refusing to fallback.")
206
+
207
+ preferred_name = os.environ.get("PRINTIFY_SHOP_NAME", "Atheria").strip().lower()
208
 
209
  for s in items:
210
  if not isinstance(s, dict):
211
  continue
212
+ title = (s.get("title") or s.get("name") or "").strip().lower()
213
+ if title and title == preferred_name:
 
 
 
 
214
  return str(s["id"])
215
 
216
+ raise RuntimeError(f"Preferred shop name '{os.environ.get('PRINTIFY_SHOP_NAME','Atheria')}' not found; refusing to fallback.")
217
 
218
 
219
  def _upload_grid_from_file(logs: List[str]) -> Dict[str, Any]:
 
222
  raise RuntimeError(f"Grid image not found at {path}")
223
 
224
  with open(path, "rb") as f:
225
+ raw = f.read()
226
+
227
+ b64 = base64.b64encode(raw).decode("ascii")
228
 
229
  payload = {
230
  "file_name": GRID_FILENAME,
231
  "contents": b64,
232
  }
233
 
234
+ _log(logs, f"UPLOADING_GRID path={path} bytes={len(raw)} b64len={len(b64)}")
235
  resp = _req("POST", "/v1/uploads/images.json", json_body=payload)
236
 
237
  if not isinstance(resp, dict) or not resp.get("id"):
 
254
  return float(s)
255
 
256
 
257
+ def _log_catalog_variants(logs: List[str], product_info: Dict[str, Any], limit: int = 50):
258
+ variants = product_info.get("variants") or []
259
+ total = len(variants) if isinstance(variants, list) else 0
260
+ _log(logs, f"CATALOG_VARIANTS_TOTAL {total}")
261
+ if not isinstance(variants, list):
262
+ return
263
+ n = 0
264
+ for v in variants:
265
+ if not isinstance(v, dict):
266
+ continue
267
+ vid = v.get("id")
268
+ size = v.get("size")
269
+ color = v.get("color")
270
+ ph = v.get("placeholders") or []
271
+ phn = len(ph) if isinstance(ph, list) else 0
272
+ _log(logs, f"CATALOG_VARIANT id={vid} size={size} color={color} placeholders={phn}")
273
+ n += 1
274
+ if n >= limit:
275
+ break
276
+
277
+
278
+ def _create_product_all_variants_with_grid(
279
  logs: List[str],
280
  shop_id: str,
281
  blob: Dict[str, Any],
282
  product_info: Dict[str, Any],
283
  upload: Dict[str, Any],
 
284
  ) -> Dict[str, Any]:
285
  bp_id = (blob.get("blueprint") or {}).get("id")
286
  provider_id = (blob.get("provider") or {}).get("id")
 
291
  if not isinstance(variants, list) or not variants:
292
  raise RuntimeError("No variants to create product with.")
293
 
 
 
 
 
 
 
294
  img_id = upload.get("id")
295
  img_w = float(upload.get("width") or 0)
296
  img_h = float(upload.get("height") or 0)
297
 
298
+ variants_payload = []
299
+ print_areas_payload = []
300
+
301
+ for v in variants:
302
+ if not isinstance(v, dict):
 
 
 
303
  continue
304
+ vid = v.get("id")
305
+ if vid is None:
 
 
306
  continue
307
+
308
+ variants_payload.append({
309
+ "id": int(vid),
310
+ "price": 1,
311
+ "is_enabled": True,
 
 
 
 
 
312
  })
 
 
 
 
 
313
 
314
+ placeholders = v.get("placeholders") or []
315
+ if not isinstance(placeholders, list) or not placeholders:
316
+ continue
317
+
318
+ ph_payload = []
319
+ for ph in placeholders:
320
+ if not isinstance(ph, dict):
321
+ continue
322
+ pos = ph.get("position")
323
+ pw = ph.get("width")
324
+ phh = ph.get("height")
325
+ if not pos or pw is None or phh is None:
326
+ continue
327
+ try:
328
+ pwf = float(pw)
329
+ phf = float(phh)
330
+ except Exception:
331
+ continue
332
+ scale = _scale_fill(pwf, phf, img_w, img_h)
333
+ ph_payload.append({
334
+ "position": pos,
335
+ "images": [{
336
+ "id": img_id,
337
+ "x": 0.5,
338
+ "y": 0.5,
339
+ "scale": scale,
340
+ "angle": 0,
341
+ }],
342
+ })
343
+
344
+ if ph_payload:
345
+ print_areas_payload.append({
346
+ "variant_ids": [int(vid)],
347
+ "placeholders": ph_payload,
348
+ })
349
+
350
+ if not variants_payload:
351
+ raise RuntimeError("No valid variant ids to create product with.")
352
+ if not print_areas_payload:
353
+ raise RuntimeError("No placeholders payload generated for any variant.")
354
 
355
  title = product_info.get("name") or "Printify Grid Test"
356
  description = product_info.get("description") or ""
 
360
  "description": description,
361
  "blueprint_id": int(bp_id),
362
  "print_provider_id": int(provider_id),
363
+ "variants": variants_payload,
364
+ "print_areas": print_areas_payload,
 
 
 
 
 
 
 
365
  }
366
 
367
  _log(
368
  logs,
369
+ f"PHASE_B_CREATE shop_id={shop_id} blueprint_id={bp_id} provider_id={provider_id} "
370
+ f"variants_payload={len(variants_payload)} print_areas_payload={len(print_areas_payload)}",
371
  )
372
 
373
  created = _req(
 
382
  return created
383
 
384
 
385
+ def _get_product(logs: List[str], shop_id: str, product_id: str) -> Dict[str, Any]:
386
+ prod = _req("GET", f"/v1/shops/{shop_id}/products/{product_id}.json")
387
+ if not isinstance(prod, dict):
388
+ raise RuntimeError(f"Unexpected product response: {str(prod)[:500]}")
389
+ variants = prod.get("variants") or []
390
+ _log(logs, f"SHOP_VARIANTS_TOTAL {len(variants) if isinstance(variants, list) else 0}")
391
+ if isinstance(variants, list):
392
+ for v in variants:
393
+ if not isinstance(v, dict):
394
+ continue
395
+ _log(
396
+ logs,
397
+ f"SHOP_VARIANT id={v.get('id')} enabled={v.get('is_enabled')} "
398
+ f"cost={v.get('cost')} price={v.get('price')}",
399
+ )
400
+ return prod
401
+
402
+
403
+ def _margin_pct() -> float:
404
+ raw = (os.environ.get("PRINTIFY_MARGIN_PCT") or "").strip()
405
+ if not raw:
406
+ return 0.0
407
+ try:
408
+ return float(raw)
409
+ except Exception:
410
+ return 0.0
411
+
412
+
413
+ def _update_prices_to_cost_plus_margin(
414
+ logs: List[str],
415
+ shop_id: str,
416
+ product_id: str,
417
+ product: Dict[str, Any],
418
+ ) -> Dict[str, Any]:
419
+ variants = product.get("variants") or []
420
+ if not isinstance(variants, list) or not variants:
421
+ raise RuntimeError("No variants found on created product for price update.")
422
+
423
+ m = _margin_pct()
424
+ payload_variants = []
425
+ for v in variants:
426
+ if not isinstance(v, dict):
427
+ continue
428
+ vid = v.get("id")
429
+ cost = v.get("cost")
430
+ if vid is None or cost is None:
431
+ continue
432
+ try:
433
+ cost_cents = int(cost)
434
+ except Exception:
435
+ continue
436
+ price_cents = int(round(cost_cents * (1.0 + m)))
437
+ if price_cents < 1:
438
+ price_cents = 1
439
+ payload_variants.append({
440
+ "id": int(vid),
441
+ "price": price_cents,
442
+ "is_enabled": True,
443
+ })
444
+ _log(logs, f"PRICE_SET id={vid} cost={cost_cents} price={price_cents} margin_pct={m}")
445
+
446
+ if not payload_variants:
447
+ raise RuntimeError("No variant prices could be computed from costs.")
448
+
449
+ upd = _req(
450
+ "PUT",
451
+ f"/v1/shops/{shop_id}/products/{product_id}.json",
452
+ json_body={"variants": payload_variants},
453
+ )
454
+ if not isinstance(upd, dict):
455
+ raise RuntimeError(f"Unexpected update response: {str(upd)[:500]}")
456
+
457
+ _log(logs, f"PRICE_UPDATE_DONE variants={len(payload_variants)}")
458
+ return upd
459
+
460
+
461
  def run(currency: str) -> Generator[Tuple[str, str], None, None]:
462
  logs: List[str] = []
463
  result: Dict[str, Any] = {}
 
517
 
518
  product_info = _build_product(blob, currency or "USD", logs)
519
  result["phaseAProduct"] = product_info
520
+ _log_catalog_variants(logs, product_info, limit=75)
521
  yield flush()
522
 
523
  upload = _upload_grid_from_file(logs)
524
  result["gridUpload"] = upload
525
  yield flush()
526
 
527
+ created = _create_product_all_variants_with_grid(
528
  logs,
529
  shop_id=shop_id,
530
  blob=blob,
531
  product_info=product_info,
532
  upload=upload,
 
533
  )
534
  result["createdProduct"] = created
 
535
  yield flush()
536
 
537
+ created_id = str(created.get("id") or "")
538
+ if not created_id:
539
+ raise RuntimeError("Created product response missing id.")
 
540
 
541
+ prod1 = _get_product(logs, shop_id, created_id)
542
+ result["createdProductReadback"] = prod1
 
 
 
 
 
 
 
 
543
  yield flush()
544
 
545
+ _log(
546
+ logs,
547
+ f"COMPARE_COUNTS catalog={len(product_info.get('variants') or [])} shop={len(prod1.get('variants') or [])}",
548
+ )
 
 
 
 
 
 
 
 
 
 
 
549
  yield flush()
550
 
551
+ upd = _update_prices_to_cost_plus_margin(logs, shop_id, created_id, prod1)
552
+ result["priceUpdateResponse"] = upd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
553
  yield flush()
554
 
555
+ prod2 = _get_product(logs, shop_id, created_id)
556
+ result["finalProductReadback"] = prod2
557
+ _log(logs, "PHASE_B_DONE")
558
  yield flush()
559
 
560
  except Exception as e:
 
567
  gr.Markdown("Extract and normalize ONE Printify blueprint/provider into a structured JSON object.")
568
 
569
  currency = gr.Textbox(label="Currency", value="USD")
570
+ btn = gr.Button("Run")
571
+ btn_b = gr.Button("Phase B (Test)")
 
 
 
572
  logs = gr.Textbox(label="Logs", lines=18)
573
  out = gr.Textbox(label="Output JSON", lines=18)
574
 
575
  btn.click(run, inputs=[currency], outputs=[logs, out])
576
  btn_b.click(phase_b, inputs=[currency], outputs=[logs, out])
 
577
 
578
  demo.queue().launch()