Theflame47 commited on
Commit
4cdc6d5
·
verified ·
1 Parent(s): f3780db

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +188 -45
app.py CHANGED
@@ -2,7 +2,7 @@ import os
2
  import time
3
  import json
4
  import tempfile
5
- from typing import Dict, Any, List, Generator, Tuple
6
 
7
  import requests
8
  import gradio as gr
@@ -10,6 +10,8 @@ import gradio as gr
10
  PRINTIFY_BASE = "https://api.printify.com"
11
  DEFAULT_BASE_PRICE = 0.00
12
 
 
 
13
 
14
  def _now() -> str:
15
  return time.strftime("%H:%M:%S")
@@ -26,11 +28,12 @@ def _auth_headers() -> Dict[str, str]:
26
  return {"Authorization": f"Bearer {token}"}
27
 
28
 
29
- def _req(method: str, path: str) -> Any:
30
  r = requests.request(
31
  method,
32
  f"{PRINTIFY_BASE}{path}",
33
- headers=_auth_headers(),
 
34
  timeout=60,
35
  )
36
  if r.status_code >= 400:
@@ -42,6 +45,41 @@ def _log(logs: List[str], msg: str):
42
  logs.append(f"[{_now()}] {msg}")
43
 
44
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
  def _find_first_valid_pair(logs: List[str]) -> Dict[str, Any]:
46
  _log(logs, "Listing blueprints")
47
  blueprints = _req("GET", "/v1/catalog/blueprints.json")
@@ -177,6 +215,137 @@ def _build_product(blob: Dict[str, Any], currency: str, logs: List[str]) -> Dict
177
  }
178
 
179
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
180
  def run(currency: str) -> Generator[Tuple[str, str], None, None]:
181
  logs: List[str] = []
182
  result: Dict[str, Any] = {}
@@ -192,27 +361,7 @@ def run(currency: str) -> Generator[Tuple[str, str], None, None]:
192
  _log(logs, f"SHOP_LIST {json.dumps(shops)}")
193
  yield flush()
194
 
195
- uploads = _req("GET", "/v1/uploads.json?limit=1")
196
- latest = uploads[0] if isinstance(uploads, list) and uploads else None
197
-
198
- if latest:
199
- _log(
200
- logs,
201
- f"LATEST_UPLOAD id={latest.get('id')} "
202
- f"file={latest.get('file_name')} "
203
- f"w={latest.get('width')} h={latest.get('height')}"
204
- )
205
- result["latestUpload"] = {
206
- "id": latest.get("id"),
207
- "file_name": latest.get("file_name"),
208
- "width": latest.get("width"),
209
- "height": latest.get("height"),
210
- }
211
- else:
212
- _log(logs, "LATEST_UPLOAD none found")
213
-
214
- yield flush()
215
-
216
  fd, path = tempfile.mkstemp(prefix="shops_", suffix=".json")
217
  os.close(fd)
218
  with open(path, "w", encoding="utf-8") as f:
@@ -248,32 +397,26 @@ def phase_b(currency: str) -> Generator[Tuple[str, str], None, None]:
248
  _log(logs, f"SHOP_LIST {json.dumps(shops)}")
249
  yield flush()
250
 
251
- uploads = _req("GET", "/v1/uploads.json?limit=1")
252
- latest = uploads[0] if isinstance(uploads, list) and uploads else None
253
-
254
- if latest:
255
- _log(
256
- logs,
257
- f"LATEST_UPLOAD id={latest.get('id')} "
258
- f"file={latest.get('file_name')} "
259
- f"w={latest.get('width')} h={latest.get('height')}"
260
- )
261
- result["latestUpload"] = {
262
- "id": latest.get("id"),
263
- "file_name": latest.get("file_name"),
264
- "width": latest.get("width"),
265
- "height": latest.get("height"),
266
- }
267
- else:
268
- _log(logs, "LATEST_UPLOAD none found")
269
 
 
 
 
 
 
 
 
 
270
  yield flush()
271
 
272
  blob = _find_first_valid_pair(logs)
273
  yield flush()
274
 
275
- result = _build_product(blob, currency or "USD", logs)
276
- _log(logs, "PHASE_B_READY")
 
277
  yield flush()
278
 
279
  except Exception as e:
@@ -294,4 +437,4 @@ with gr.Blocks(title="Printify Catalog Probe") as demo:
294
  btn.click(run, inputs=[currency], outputs=[logs, out])
295
  btn_b.click(phase_b, inputs=[currency], outputs=[logs, out])
296
 
297
- demo.queue().launch()
 
2
  import time
3
  import json
4
  import tempfile
5
+ from typing import Dict, Any, List, Generator, Tuple, Optional
6
 
7
  import requests
8
  import gradio as gr
 
10
  PRINTIFY_BASE = "https://api.printify.com"
11
  DEFAULT_BASE_PRICE = 0.00
12
 
13
+ GRID_FILENAME = "grid.png"
14
+
15
 
16
  def _now() -> str:
17
  return time.strftime("%H:%M:%S")
 
28
  return {"Authorization": f"Bearer {token}"}
29
 
30
 
31
+ def _req(method: str, path: str, json_body: Optional[Dict[str, Any]] = None) -> Any:
32
  r = requests.request(
33
  method,
34
  f"{PRINTIFY_BASE}{path}",
35
+ headers={**_auth_headers(), "Content-Type": "application/json"} if json_body is not None else _auth_headers(),
36
+ json=json_body,
37
  timeout=60,
38
  )
39
  if r.status_code >= 400:
 
45
  logs.append(f"[{_now()}] {msg}")
46
 
47
 
48
+ def _as_list(resp: Any) -> List[Any]:
49
+ if isinstance(resp, list):
50
+ return resp
51
+ if isinstance(resp, dict):
52
+ for k in ("data", "items", "uploads", "results"):
53
+ v = resp.get(k)
54
+ if isinstance(v, list):
55
+ return v
56
+ return []
57
+
58
+
59
+ def _space_id_parts() -> Tuple[str, str]:
60
+ owner = os.environ.get("HF_SPACE_OWNER") or os.environ.get("SPACE_OWNER") or ""
61
+ name = os.environ.get("HF_SPACE_NAME") or os.environ.get("SPACE_NAME") or ""
62
+ if owner and name:
63
+ return owner, name
64
+
65
+ sid = os.environ.get("SPACE_ID") or os.environ.get("HF_SPACE_ID") or ""
66
+ if sid and "/" in sid:
67
+ a, b = sid.split("/", 1)
68
+ if a and b:
69
+ return a, b
70
+
71
+ # last-resort: hard error with actionable message
72
+ raise RuntimeError("Missing HF space identity. Set HF_SPACE_OWNER and HF_SPACE_NAME (or SPACE_ID=owner/name).")
73
+
74
+
75
+ def _hf_asset_url(rel_path: str) -> str:
76
+ owner, name = _space_id_parts()
77
+ rel_path = (rel_path or "").lstrip("/")
78
+ if not rel_path:
79
+ raise RuntimeError("Empty asset path for HF URL.")
80
+ return f"https://huggingface.co/spaces/{owner}/{name}/resolve/main/{rel_path}?download=1"
81
+
82
+
83
  def _find_first_valid_pair(logs: List[str]) -> Dict[str, Any]:
84
  _log(logs, "Listing blueprints")
85
  blueprints = _req("GET", "/v1/catalog/blueprints.json")
 
215
  }
216
 
217
 
218
+ def _pick_shop_id(shops_resp: Any) -> str:
219
+ shops = _as_list(shops_resp)
220
+ for s in shops:
221
+ if isinstance(s, dict) and s.get("id"):
222
+ return str(s["id"])
223
+ raise RuntimeError("No shops found on /v1/shops.json for this token.")
224
+
225
+
226
+ def _upload_grid_image(logs: List[str]) -> Dict[str, Any]:
227
+ url = _hf_asset_url(GRID_FILENAME)
228
+ _log(logs, f"GRID_URL {url}")
229
+
230
+ # Upload-by-URL (Printify fetches it)
231
+ resp = _req(
232
+ "POST",
233
+ "/v1/uploads/images.json",
234
+ json_body={"file_name": GRID_FILENAME, "url": url},
235
+ )
236
+
237
+ if not isinstance(resp, dict) or not resp.get("id"):
238
+ raise RuntimeError(f"Unexpected upload response shape: {str(resp)[:500]}")
239
+
240
+ _log(
241
+ logs,
242
+ f"GRID_UPLOAD id={resp.get('id')} file={resp.get('file_name') or resp.get('name')} w={resp.get('width')} h={resp.get('height')}",
243
+ )
244
+ return resp
245
+
246
+
247
+ def _scale_fill(ph_w: float, ph_h: float, img_w: float, img_h: float) -> float:
248
+ # Printify scale is defined relative to placeholder width, so:
249
+ # scale=1 means image width == placeholder width.
250
+ # For "fill to placeholder" (cover), increase scale if height would underfill.
251
+ if ph_w <= 0 or ph_h <= 0 or img_w <= 0 or img_h <= 0:
252
+ return 1.0
253
+ s = (ph_h * img_w) / (ph_w * img_h)
254
+ if s < 1.0:
255
+ s = 1.0
256
+ return float(s)
257
+
258
+
259
+ def _phase_b_create_one_product(
260
+ logs: List[str],
261
+ shop_id: str,
262
+ blob: Dict[str, Any],
263
+ upload: Dict[str, Any],
264
+ currency: str,
265
+ ) -> Dict[str, Any]:
266
+ bp_id = str((blob.get("blueprint") or {}).get("id"))
267
+ provider_id = str((blob.get("provider") or {}).get("id"))
268
+
269
+ variants = blob.get("variants") or []
270
+ if not isinstance(variants, list) or not variants:
271
+ raise RuntimeError("No variants in blob.")
272
+
273
+ # Minimal: one variant only for the first Phase B test.
274
+ v = variants[0]
275
+ variant_id = v.get("id")
276
+ if variant_id is None:
277
+ raise RuntimeError("First variant missing id.")
278
+
279
+ placeholders = v.get("placeholders")
280
+ if not isinstance(placeholders, list):
281
+ placeholders = []
282
+
283
+ img_id = upload.get("id")
284
+ img_w = upload.get("width")
285
+ img_h = upload.get("height")
286
+ try:
287
+ img_w = float(img_w) if img_w is not None else 0.0
288
+ img_h = float(img_h) if img_h is not None else 0.0
289
+ except Exception:
290
+ img_w, img_h = 0.0, 0.0
291
+
292
+ ph_payload = []
293
+ for ph in placeholders:
294
+ if not isinstance(ph, dict):
295
+ continue
296
+ pos = ph.get("position")
297
+ pw = ph.get("width")
298
+ phh = ph.get("height")
299
+ if not pos or pw is None or phh is None:
300
+ continue
301
+ try:
302
+ pwf = float(pw)
303
+ phf = float(phh)
304
+ except Exception:
305
+ continue
306
+ s = _scale_fill(pwf, phf, img_w, img_h)
307
+ ph_payload.append({
308
+ "position": pos,
309
+ "images": [{
310
+ "id": img_id,
311
+ "x": 0.5,
312
+ "y": 0.5,
313
+ "scale": s,
314
+ "angle": 0,
315
+ }],
316
+ })
317
+ _log(logs, f"FILL_PLACEHOLDER pos={pos} ph={int(pwf)}x{int(phf)} img={int(img_w)}x{int(img_h)} scale={round(s, 6)}")
318
+
319
+ if not ph_payload:
320
+ raise RuntimeError("No placeholders found on chosen variant; cannot place image.")
321
+
322
+ title = (blob.get("blueprintDetails") or {}).get("title") or "Printify Test Product"
323
+ description = (blob.get("blueprintDetails") or {}).get("description") or ""
324
+
325
+ payload = {
326
+ "title": title,
327
+ "description": description,
328
+ "blueprint_id": int(bp_id),
329
+ "print_provider_id": int(provider_id),
330
+ "variants": [{
331
+ "id": int(variant_id),
332
+ "price": 0, # keep minimal for test; adjust later
333
+ "is_enabled": True,
334
+ }],
335
+ "print_areas": [{
336
+ "variant_ids": [int(variant_id)],
337
+ "placeholders": ph_payload,
338
+ }],
339
+ }
340
+
341
+ _log(logs, f"PHASE_B_CREATE shop_id={shop_id} blueprint_id={bp_id} provider_id={provider_id} variant_id={variant_id}")
342
+ created = _req("POST", f"/v1/shops/{shop_id}/products.json", json_body=payload)
343
+ if not isinstance(created, dict):
344
+ raise RuntimeError(f"Unexpected create response: {str(created)[:500]}")
345
+ _log(logs, f"PHASE_B_CREATED product_id={created.get('id')}")
346
+ return created
347
+
348
+
349
  def run(currency: str) -> Generator[Tuple[str, str], None, None]:
350
  logs: List[str] = []
351
  result: Dict[str, Any] = {}
 
361
  _log(logs, f"SHOP_LIST {json.dumps(shops)}")
362
  yield flush()
363
 
364
+ # (keep your original shops temp file write exactly as-is)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
365
  fd, path = tempfile.mkstemp(prefix="shops_", suffix=".json")
366
  os.close(fd)
367
  with open(path, "w", encoding="utf-8") as f:
 
397
  _log(logs, f"SHOP_LIST {json.dumps(shops)}")
398
  yield flush()
399
 
400
+ shop_id = _pick_shop_id(shops)
401
+ _log(logs, f"SHOP_ID {shop_id}")
402
+ yield flush()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
403
 
404
+ upload = _upload_grid_image(logs)
405
+ result["gridUpload"] = {
406
+ "id": upload.get("id"),
407
+ "file_name": upload.get("file_name") or upload.get("name"),
408
+ "width": upload.get("width"),
409
+ "height": upload.get("height"),
410
+ "url": upload.get("preview_url") or upload.get("url"),
411
+ }
412
  yield flush()
413
 
414
  blob = _find_first_valid_pair(logs)
415
  yield flush()
416
 
417
+ created = _phase_b_create_one_product(logs, shop_id, blob, upload, currency or "USD")
418
+ result["createdProduct"] = created
419
+ _log(logs, "PHASE_B_DONE")
420
  yield flush()
421
 
422
  except Exception as e:
 
437
  btn.click(run, inputs=[currency], outputs=[logs, out])
438
  btn_b.click(phase_b, inputs=[currency], outputs=[logs, out])
439
 
440
+ demo.queue