Spaces:
Sleeping
Sleeping
Update app.py
Browse files
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 |
-
|
|
|
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 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("
|
| 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 |
-
|
|
|
|
|
|
|
| 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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 278 |
-
|
| 279 |
-
|
| 280 |
-
|
| 281 |
-
|
| 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 |
-
|
| 287 |
-
|
| 288 |
-
phf = float(phh)
|
| 289 |
-
except Exception:
|
| 290 |
continue
|
| 291 |
-
|
| 292 |
-
|
| 293 |
-
"
|
| 294 |
-
"
|
| 295 |
-
|
| 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 |
-
|
| 309 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 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"
|
| 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 =
|
| 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 |
-
|
| 426 |
-
|
| 427 |
-
|
| 428 |
-
yield flush()
|
| 429 |
|
| 430 |
-
|
| 431 |
-
|
| 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 |
-
|
| 443 |
-
|
| 444 |
-
|
| 445 |
-
|
| 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 |
-
|
| 460 |
-
|
| 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 |
-
|
|
|
|
|
|
|
| 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 |
-
|
| 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()
|