Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -9,6 +9,7 @@ import gradio as gr
|
|
| 9 |
import base64
|
| 10 |
|
| 11 |
PRINTIFY_BASE = "https://api.printify.com"
|
|
|
|
| 12 |
GRID_FILENAME = "grid.png"
|
| 13 |
|
| 14 |
|
|
@@ -89,9 +90,7 @@ def _build_product(blob: Dict[str, Any], currency: str, logs: List[str]) -> Dict
|
|
| 89 |
|
| 90 |
snapshot = []
|
| 91 |
for v in variants:
|
| 92 |
-
cents = v.get("
|
| 93 |
-
if cents is None:
|
| 94 |
-
cents = v.get("price")
|
| 95 |
cents = int(cents) if isinstance(cents, (int, str)) and str(cents).isdigit() else None
|
| 96 |
|
| 97 |
ph = v.get("placeholders")
|
|
@@ -113,8 +112,8 @@ def _build_product(blob: Dict[str, Any], currency: str, logs: List[str]) -> Dict
|
|
| 113 |
"sku": v.get("sku"),
|
| 114 |
"size": (v.get("options") or {}).get("size"),
|
| 115 |
"color": (v.get("options") or {}).get("color"),
|
| 116 |
-
"
|
| 117 |
-
"
|
| 118 |
"placeholders": placeholders,
|
| 119 |
})
|
| 120 |
|
|
@@ -150,10 +149,8 @@ def _build_product(blob: Dict[str, Any], currency: str, logs: List[str]) -> Dict
|
|
| 150 |
if ph.get("position")
|
| 151 |
})
|
| 152 |
|
| 153 |
-
|
| 154 |
-
if
|
| 155 |
-
raise RuntimeError("No variant cost returned by API (expected 'cost' or 'price' in cents).")
|
| 156 |
-
min_price = round(min(all_cost_cents) / 100, 2)
|
| 157 |
|
| 158 |
colors = sorted({v["color"] for v in snapshot if v["color"]})
|
| 159 |
sizes = sorted({v["size"] for v in snapshot if v["size"]})
|
|
@@ -240,29 +237,6 @@ def _upload_grid_from_file(logs: List[str]) -> Dict[str, Any]:
|
|
| 240 |
)
|
| 241 |
return resp
|
| 242 |
|
| 243 |
-
with open(path, "rb") as f:
|
| 244 |
-
r = requests.post(
|
| 245 |
-
f"{PRINTIFY_BASE}/v1/uploads/images.json",
|
| 246 |
-
headers=_auth_headers(),
|
| 247 |
-
files={"file": (GRID_FILENAME, f, "image/png")},
|
| 248 |
-
timeout=60,
|
| 249 |
-
)
|
| 250 |
-
|
| 251 |
-
if r.status_code >= 400:
|
| 252 |
-
raise RuntimeError(f"UPLOAD_HTTP {r.status_code}: {r.text[:2000]}")
|
| 253 |
-
|
| 254 |
-
resp = r.json()
|
| 255 |
-
if not isinstance(resp, dict) or not resp.get("id"):
|
| 256 |
-
raise RuntimeError(f"Unexpected upload response: {str(resp)[:500]}")
|
| 257 |
-
|
| 258 |
-
_log(
|
| 259 |
-
logs,
|
| 260 |
-
f"GRID_UPLOAD id={resp.get('id')} "
|
| 261 |
-
f"file={resp.get('file_name') or resp.get('name')} "
|
| 262 |
-
f"w={resp.get('width')} h={resp.get('height')}",
|
| 263 |
-
)
|
| 264 |
-
return resp
|
| 265 |
-
|
| 266 |
|
| 267 |
def _scale_fill(ph_w: float, ph_h: float, img_w: float, img_h: float) -> float:
|
| 268 |
if ph_w <= 0 or ph_h <= 0 or img_w <= 0 or img_h <= 0:
|
|
@@ -344,7 +318,7 @@ def _create_one_product_with_grid(
|
|
| 344 |
"print_provider_id": int(provider_id),
|
| 345 |
"variants": [{
|
| 346 |
"id": int(variant_id),
|
| 347 |
-
"price":
|
| 348 |
"is_enabled": True,
|
| 349 |
}],
|
| 350 |
"print_areas": [{
|
|
@@ -356,8 +330,7 @@ def _create_one_product_with_grid(
|
|
| 356 |
_log(
|
| 357 |
logs,
|
| 358 |
f"PHASE_B_CREATE shop_id={shop_id} "
|
| 359 |
-
f"blueprint_id={bp_id} provider_id={provider_id} variant_id={variant_id}
|
| 360 |
-
f"price_cents={int(product_info.get('price') * 100)}",
|
| 361 |
)
|
| 362 |
|
| 363 |
created = _req(
|
|
@@ -455,16 +428,97 @@ def phase_b(currency: str) -> Generator[Tuple[str, str], None, None]:
|
|
| 455 |
yield flush()
|
| 456 |
|
| 457 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 458 |
with gr.Blocks(title="Printify Catalog Probe") as demo:
|
| 459 |
gr.Markdown("Extract and normalize ONE Printify blueprint/provider into a structured JSON object.")
|
| 460 |
|
| 461 |
currency = gr.Textbox(label="Currency", value="USD")
|
| 462 |
-
|
| 463 |
-
|
|
|
|
|
|
|
|
|
|
| 464 |
logs = gr.Textbox(label="Logs", lines=18)
|
| 465 |
out = gr.Textbox(label="Output JSON", lines=18)
|
| 466 |
|
| 467 |
btn.click(run, inputs=[currency], outputs=[logs, out])
|
| 468 |
btn_b.click(phase_b, inputs=[currency], outputs=[logs, out])
|
|
|
|
| 469 |
|
| 470 |
demo.queue().launch()
|
|
|
|
| 9 |
import base64
|
| 10 |
|
| 11 |
PRINTIFY_BASE = "https://api.printify.com"
|
| 12 |
+
DEFAULT_BASE_PRICE = 24.99
|
| 13 |
GRID_FILENAME = "grid.png"
|
| 14 |
|
| 15 |
|
|
|
|
| 90 |
|
| 91 |
snapshot = []
|
| 92 |
for v in variants:
|
| 93 |
+
cents = v.get("price")
|
|
|
|
|
|
|
| 94 |
cents = int(cents) if isinstance(cents, (int, str)) and str(cents).isdigit() else None
|
| 95 |
|
| 96 |
ph = v.get("placeholders")
|
|
|
|
| 112 |
"sku": v.get("sku"),
|
| 113 |
"size": (v.get("options") or {}).get("size"),
|
| 114 |
"color": (v.get("options") or {}).get("color"),
|
| 115 |
+
"priceCents": cents,
|
| 116 |
+
"price": round(cents / 100, 2) if cents is not None else None,
|
| 117 |
"placeholders": placeholders,
|
| 118 |
})
|
| 119 |
|
|
|
|
| 149 |
if ph.get("position")
|
| 150 |
})
|
| 151 |
|
| 152 |
+
all_cents = [v["priceCents"] for v in snapshot if v["priceCents"] is not None]
|
| 153 |
+
min_price = round(min(all_cents) / 100, 2) if all_cents else DEFAULT_BASE_PRICE
|
|
|
|
|
|
|
| 154 |
|
| 155 |
colors = sorted({v["color"] for v in snapshot if v["color"]})
|
| 156 |
sizes = sorted({v["size"] for v in snapshot if v["size"]})
|
|
|
|
| 237 |
)
|
| 238 |
return resp
|
| 239 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 240 |
|
| 241 |
def _scale_fill(ph_w: float, ph_h: float, img_w: float, img_h: float) -> float:
|
| 242 |
if ph_w <= 0 or ph_h <= 0 or img_w <= 0 or img_h <= 0:
|
|
|
|
| 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": [{
|
|
|
|
| 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(
|
|
|
|
| 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:
|
| 503 |
+
_log(logs, f"ERROR: {e}")
|
| 504 |
+
result = {"error": str(e)}
|
| 505 |
+
yield flush()
|
| 506 |
+
|
| 507 |
+
|
| 508 |
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()
|