Theflame47 commited on
Commit
1bc7285
·
verified ·
1 Parent(s): d1f4d33

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +145 -145
app.py CHANGED
@@ -1,162 +1,162 @@
1
- import faicons as fa
2
- import plotly.express as px
 
 
3
 
4
- # Load data and compute static values
5
- from shared import app_dir, tips
6
- from shinywidgets import render_plotly
7
 
8
- from shiny import reactive, render
9
- from shiny.express import input, ui
10
 
11
- bill_rng = (min(tips.total_bill), max(tips.total_bill))
12
 
13
- # Add page title and sidebar
14
- ui.page_opts(title="Restaurant tipping", fillable=True)
15
 
16
- with ui.sidebar(open="desktop"):
17
- ui.input_slider(
18
- "total_bill",
19
- "Bill amount",
20
- min=bill_rng[0],
21
- max=bill_rng[1],
22
- value=bill_rng,
23
- pre="$",
24
- )
25
- ui.input_checkbox_group(
26
- "time",
27
- "Food service",
28
- ["Lunch", "Dinner"],
29
- selected=["Lunch", "Dinner"],
30
- inline=True,
 
 
 
31
  )
32
- ui.input_action_button("reset", "Reset filter")
33
-
34
- # Add main content
35
- ICONS = {
36
- "user": fa.icon_svg("user", "regular"),
37
- "wallet": fa.icon_svg("wallet"),
38
- "currency-dollar": fa.icon_svg("dollar-sign"),
39
- "ellipsis": fa.icon_svg("ellipsis"),
40
- }
41
-
42
- with ui.layout_columns(fill=False):
43
- with ui.value_box(showcase=ICONS["user"]):
44
- "Total tippers"
45
-
46
- @render.express
47
- def total_tippers():
48
- tips_data().shape[0]
49
-
50
- with ui.value_box(showcase=ICONS["wallet"]):
51
- "Average tip"
52
-
53
- @render.express
54
- def average_tip():
55
- d = tips_data()
56
- if d.shape[0] > 0:
57
- perc = d.tip / d.total_bill
58
- f"{perc.mean():.1%}"
59
-
60
- with ui.value_box(showcase=ICONS["currency-dollar"]):
61
- "Average bill"
62
-
63
- @render.express
64
- def average_bill():
65
- d = tips_data()
66
- if d.shape[0] > 0:
67
- bill = d.total_bill.mean()
68
- f"${bill:.2f}"
69
-
70
-
71
- with ui.layout_columns(col_widths=[6, 6, 12]):
72
- with ui.card(full_screen=True):
73
- ui.card_header("Tips data")
74
-
75
- @render.data_frame
76
- def table():
77
- return render.DataGrid(tips_data())
78
-
79
- with ui.card(full_screen=True):
80
- with ui.card_header(class_="d-flex justify-content-between align-items-center"):
81
- "Total bill vs tip"
82
- with ui.popover(title="Add a color variable", placement="top"):
83
- ICONS["ellipsis"]
84
- ui.input_radio_buttons(
85
- "scatter_color",
86
- None,
87
- ["none", "sex", "smoker", "day", "time"],
88
- inline=True,
89
- )
90
-
91
- @render_plotly
92
- def scatterplot():
93
- color = input.scatter_color()
94
- return px.scatter(
95
- tips_data(),
96
- x="total_bill",
97
- y="tip",
98
- color=None if color == "none" else color,
99
- trendline="lowess",
100
- )
101
 
102
- with ui.card(full_screen=True):
103
- with ui.card_header(class_="d-flex justify-content-between align-items-center"):
104
- "Tip percentages"
105
- with ui.popover(title="Add a color variable"):
106
- ICONS["ellipsis"]
107
- ui.input_radio_buttons(
108
- "tip_perc_y",
109
- "Split by:",
110
- ["sex", "smoker", "day", "time"],
111
- selected="day",
112
- inline=True,
113
- )
114
-
115
- @render_plotly
116
- def tip_perc():
117
- from ridgeplot import ridgeplot
118
-
119
- dat = tips_data()
120
- dat["percent"] = dat.tip / dat.total_bill
121
- yvar = input.tip_perc_y()
122
- uvals = dat[yvar].unique()
123
-
124
- samples = [[dat.percent[dat[yvar] == val]] for val in uvals]
125
-
126
- plt = ridgeplot(
127
- samples=samples,
128
- labels=uvals,
129
- bandwidth=0.01,
130
- colorscale="viridis",
131
- colormode="row-index",
132
- )
133
 
134
- plt.update_layout(
135
- legend=dict(
136
- orientation="h", yanchor="bottom", y=1.02, xanchor="center", x=0.5
137
- )
 
 
 
 
 
 
 
 
 
 
 
 
138
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
139
 
140
- return plt
 
141
 
 
 
 
142
 
143
- ui.include_css(app_dir / "styles.css")
 
 
 
144
 
145
- # --------------------------------------------------------
146
- # Reactive calculations and effects
147
- # --------------------------------------------------------
148
 
 
 
149
 
150
- @reactive.calc
151
- def tips_data():
152
- bill = input.total_bill()
153
- idx1 = tips.total_bill.between(bill[0], bill[1])
154
- idx2 = tips.time.isin(input.time())
155
- return tips[idx1 & idx2]
156
 
 
157
 
158
- @reactive.effect
159
- @reactive.event(input.reset)
160
- def _():
161
- ui.update_slider("total_bill", value=bill_rng)
162
- ui.update_checkbox_group("time", selected=["Lunch", "Dinner"])
 
1
+ import os
2
+ import time
3
+ import json
4
+ from typing import Dict, Any, List, Generator, Tuple
5
 
6
+ import requests
7
+ import gradio as gr
 
8
 
9
+ PRINTIFY_BASE = "https://api.printify.com"
10
+ DEFAULT_BASE_PRICE = 24.99
11
 
 
12
 
13
+ def _now() -> str:
14
+ return time.strftime("%H:%M:%S")
15
 
16
+
17
+ def _sleep(ms: int):
18
+ time.sleep(ms / 1000)
19
+
20
+
21
+ def _auth_headers() -> Dict[str, str]:
22
+ token = os.environ.get("PRINTIFY_API_TOKEN")
23
+ if not token:
24
+ raise RuntimeError("Missing PRINTIFY_API_TOKEN (HF Space Secret).")
25
+ return {"Authorization": f"Bearer {token}"}
26
+
27
+
28
+ def _req(method: str, path: str) -> Any:
29
+ r = requests.request(
30
+ method,
31
+ f"{PRINTIFY_BASE}{path}",
32
+ headers=_auth_headers(),
33
+ timeout=60,
34
  )
35
+ if r.status_code >= 400:
36
+ raise RuntimeError(f"HTTP {r.status_code}: {r.text[:2000]}")
37
+ return r.json()
38
+
39
+
40
+ def _log(logs: List[str], msg: str):
41
+ logs.append(f"[{_now()}] {msg}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
 
44
+ def _find_first_valid_pair(logs: List[str]) -> Dict[str, Any]:
45
+ _log(logs, "Listing blueprints")
46
+ blueprints = _req("GET", "/v1/catalog/blueprints.json")
47
+
48
+ for bp in blueprints:
49
+ bp_id = str(bp["id"])
50
+ _log(logs, f"Blueprint {bp_id}: fetching providers")
51
+
52
+ providers = _req("GET", f"/v1/catalog/blueprints/{bp_id}/print_providers.json")
53
+ for p in providers:
54
+ p_id = str(p["id"])
55
+ _log(logs, f"Blueprint {bp_id} / Provider {p_id}: fetching variants")
56
+
57
+ vr = _req(
58
+ "GET",
59
+ f"/v1/catalog/blueprints/{bp_id}/print_providers/{p_id}/variants.json?show-out-of-stock=1",
60
  )
61
+ variants = vr.get("variants")
62
+ if isinstance(variants, list) and variants:
63
+ _log(logs, f"FOUND {len(variants)} variants")
64
+
65
+ return {
66
+ "blueprint": bp,
67
+ "provider": p,
68
+ "variants": variants,
69
+ "blueprintDetails": _req("GET", f"/v1/catalog/blueprints/{bp_id}.json"),
70
+ "providerDetails": _req("GET", f"/v1/catalog/print_providers/{p_id}.json"),
71
+ "shippingInfo": _req(
72
+ "GET",
73
+ f"/v1/catalog/blueprints/{bp_id}/print_providers/{p_id}/shipping.json",
74
+ ),
75
+ }
76
+
77
+ raise RuntimeError("No blueprint/provider pair with variants found.")
78
+
79
+
80
+ def _build_product(blob: Dict[str, Any], currency: str, logs: List[str]) -> Dict[str, Any]:
81
+ variants = blob["variants"]
82
+
83
+ snapshot = []
84
+ for v in variants:
85
+ cents = v.get("price")
86
+ cents = int(cents) if isinstance(cents, (int, str)) and str(cents).isdigit() else None
87
+ snapshot.append({
88
+ "id": v.get("id"),
89
+ "sku": v.get("sku"),
90
+ "size": (v.get("options") or {}).get("size"),
91
+ "color": (v.get("options") or {}).get("color"),
92
+ "priceCents": cents,
93
+ "price": round(cents / 100, 2) if cents is not None else None,
94
+ })
95
+
96
+ _log(logs, f"VARIANT_SNAPSHOT sample={json.dumps(snapshot[:20])}")
97
+
98
+ all_cents = [v["priceCents"] for v in snapshot if v["priceCents"] is not None]
99
+ min_price = round(min(all_cents) / 100, 2) if all_cents else DEFAULT_BASE_PRICE
100
+
101
+ colors = sorted({v["color"] for v in snapshot if v["color"]})
102
+ sizes = sorted({v["size"] for v in snapshot if v["size"]})
103
+
104
+ provider = blob["providerDetails"]
105
+ loc = provider.get("location") or {}
106
+ loc_str = ", ".join(x for x in [loc.get("city"), loc.get("country")] if x) or "empty"
107
+
108
+ description = (
109
+ (blob["blueprintDetails"].get("description") or "empty")
110
+ + f"\n\nFulfilled by {provider.get('title') or provider.get('name')} — {loc_str}"
111
+ )
112
+
113
+ return {
114
+ "name": blob["blueprintDetails"].get("title"),
115
+ "currency": currency,
116
+ "price": min_price,
117
+ "tags": ["Printify", f"Provider: {provider.get('title') or provider.get('name')}"],
118
+ "options": {
119
+ "Color": colors,
120
+ "Size": sizes,
121
+ },
122
+ "description": description,
123
+ "variants": snapshot,
124
+ "raw": blob,
125
+ }
126
+
127
+
128
+ def run(currency: str) -> Generator[Tuple[str, str], None, None]:
129
+ logs: List[str] = []
130
+ result: Dict[str, Any] = {}
131
+
132
+ def flush():
133
+ return "\n".join(logs), json.dumps(result, indent=2)
134
+
135
+ try:
136
+ _log(logs, "START")
137
+ yield flush()
138
 
139
+ blob = _find_first_valid_pair(logs)
140
+ yield flush()
141
 
142
+ result = _build_product(blob, currency or "USD", logs)
143
+ _log(logs, "DONE")
144
+ yield flush()
145
 
146
+ except Exception as e:
147
+ _log(logs, f"ERROR: {e}")
148
+ result = {"error": str(e)}
149
+ yield flush()
150
 
 
 
 
151
 
152
+ with gr.Blocks(title="Printify Catalog Probe") as demo:
153
+ gr.Markdown("Extract and normalize ONE Printify blueprint/provider into a structured JSON object.")
154
 
155
+ currency = gr.Textbox(label="Currency", value="USD")
156
+ btn = gr.Button("Run")
157
+ logs = gr.Textbox(label="Logs", lines=18)
158
+ out = gr.Textbox(label="Output JSON", lines=18)
 
 
159
 
160
+ btn.click(run, inputs=[currency], outputs=[logs, out])
161
 
162
+ demo.queue(concurrency_count=1).launch()