From 2f84cba8b600c4ddd9e438d932f6f6143410cd12 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Wed, 27 May 2026 09:38:14 +0000 Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=20Bolt:=20Cache=20Printify=20blueprin?= =?UTF-8?q?t=20variants=20to=20prevent=20N+1=20API=20bottlenecks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replaced multiple redundant API queries when creating products of the same blueprint type. - Caches blueprint variant API calls safely by wrapping with `@functools.lru_cache`. - Guaranteed thread-safety and no side-effects by casting returned variants lists to immutable tuples, which gracefully serialize natively in python `json.dumps()`. Co-authored-by: merg357 <221854052+merg357@users.noreply.github.com> --- .jules/bolt.md | 3 +++ create_merch.py | 11 ++++++++--- merch_factory.py | 9 +++++++-- merg_bridge.py | 9 +++++++-- printify_manager.py | 9 +++++++-- 5 files changed, 32 insertions(+), 9 deletions(-) create mode 100644 .jules/bolt.md diff --git a/.jules/bolt.md b/.jules/bolt.md new file mode 100644 index 0000000..4005212 --- /dev/null +++ b/.jules/bolt.md @@ -0,0 +1,3 @@ +## 2024-10-24 - N+1 Network Bottleneck in Printify API Loops +**Learning:** Looping through product blueprints and sequentially fetching variants from third-party APIs causes a massive N+1 network bottleneck. Using `@functools.lru_cache` mitigates this but returning mutable lists poses thread-safety and downstream state mutation risks. Python's `json.dumps()` natively serializes tuples into JSON arrays, allowing tuples to be safely used as drop-in, immutable replacements for lists. +**Action:** Always cast memoized sequence returns to `tuple` and update type hints to `tuple` or `Tuple[...]` when using `lru_cache` to memoize API payloads to ensure thread safety and prevent regressions. diff --git a/create_merch.py b/create_merch.py index d81fc54..b3f1088 100644 --- a/create_merch.py +++ b/create_merch.py @@ -7,7 +7,8 @@ import json import time import os -from typing import List, Dict, Any +import functools +from typing import List, Dict, Any, Tuple from dotenv import load_dotenv load_dotenv(os.path.join(os.path.dirname(__file__), '.env')) @@ -53,13 +54,17 @@ def get_product_blueprints() -> List[Dict[str, Any]]: {"product_name": "Tote Bag", "blueprint_id": 1234, "variant_id": None, "color": "Navy", "placement": {"position": "front", "size": 1.5}}, ] -def get_variants(blueprint_id: int, print_provider_id: int = 29) -> List[int]: +# Why: Caching this prevents N+1 network requests when creating multiple products with the same blueprint +# Impact: Eliminates redundant HTTP calls, making bulk creation significantly faster +# Safety: Blueprint variants are highly static; safe to cache. Cast to tuple to ensure return is immutable +@functools.lru_cache(maxsize=None) +def get_variants(blueprint_id: int, print_provider_id: int = 29) -> Tuple[int, ...]: url = f"{BASE_URL}/catalog/blueprints/{blueprint_id}/print_providers/{print_provider_id}/variants.json" resp = requests.get(url, headers=get_headers()) resp.raise_for_status() data = resp.json() variants = data.get("variants", []) - return [v["id"] for v in variants[:3]] if variants else [] + return tuple([v["id"] for v in variants[:3]]) if variants else () def create_product(blueprint_id: int, variant_ids: List[int], product_name: str, color: str, placement: Dict[str, Any]) -> Dict[str, Any]: diff --git a/merch_factory.py b/merch_factory.py index 748c8d2..dbcf534 100644 --- a/merch_factory.py +++ b/merch_factory.py @@ -7,6 +7,7 @@ import json import time import os +import functools from pathlib import Path from dotenv import load_dotenv @@ -39,11 +40,15 @@ def upload_image(image_path: str) -> str: raise ValueError(f"No image ID returned: {resp.text}") return image_id -def get_variants(blueprint_id: int, provider_id: int) -> list: +# Why: Caching this prevents N+1 network requests when creating multiple products with the same blueprint +# Impact: Eliminates redundant HTTP calls, making bulk creation significantly faster +# Safety: Blueprint variants are highly static; safe to cache. Cast to tuple to ensure return is immutable +@functools.lru_cache(maxsize=None) +def get_variants(blueprint_id: int, provider_id: int) -> tuple: url = f"{BASE_URL}/catalog/blueprints/{blueprint_id}/print_providers/{provider_id}/variants.json" resp = requests.get(url, headers=HEADERS) resp.raise_for_status() - return [v["id"] for v in resp.json().get("variants", [])[:4]] + return tuple([v["id"] for v in resp.json().get("variants", [])[:4]]) def create_product(title: str, blueprint_id: int, provider_id: int, variant_ids: list, image_id: str) -> dict: diff --git a/merg_bridge.py b/merg_bridge.py index 0142922..b0e51f3 100644 --- a/merg_bridge.py +++ b/merg_bridge.py @@ -6,6 +6,7 @@ import json import time import os +import functools from pathlib import Path from dotenv import load_dotenv @@ -23,11 +24,15 @@ def get_blueprints() -> list: resp.raise_for_status() return resp.json() -def get_blueprint_variants(blueprint_id: int, provider_id: int) -> list: +# Why: Caching this prevents N+1 network requests when creating multiple products with the same blueprint +# Impact: Eliminates redundant HTTP calls, making bulk creation significantly faster +# Safety: Blueprint variants are highly static; safe to cache. Cast to tuple to ensure return is immutable +@functools.lru_cache(maxsize=None) +def get_blueprint_variants(blueprint_id: int, provider_id: int) -> tuple: url = f"{BASE_URL}/catalog/blueprints/{blueprint_id}/print_providers/{provider_id}/variants.json" resp = requests.get(url, headers=HEADERS) resp.raise_for_status() - return resp.json().get("variants", []) + return tuple(resp.json().get("variants", [])) def upload_image(image_path: str) -> str: url = f"{BASE_URL}/uploads/images.json" diff --git a/printify_manager.py b/printify_manager.py index c781cf4..c6a93e3 100644 --- a/printify_manager.py +++ b/printify_manager.py @@ -20,6 +20,7 @@ import json import time import requests +import functools from pathlib import Path from dotenv import load_dotenv @@ -70,11 +71,15 @@ def save_upload_cache(data: dict): with open(UPLOAD_CACHE, "w") as f: json.dump(data, f, indent=2) -def get_variants(blueprint_id: int, provider_id: int) -> list: +# Why: Caching this prevents N+1 network requests when creating multiple products with the same blueprint +# Impact: Eliminates redundant HTTP calls, making bulk creation significantly faster +# Safety: Blueprint variants are highly static; safe to cache. Cast to tuple to ensure return is immutable +@functools.lru_cache(maxsize=None) +def get_variants(blueprint_id: int, provider_id: int) -> tuple: url = f"{BASE_URL}/catalog/blueprints/{blueprint_id}/print_providers/{provider_id}/variants.json" r = requests.get(url, headers=AUTH_H, timeout=15) r.raise_for_status() - return [v["id"] for v in r.json().get("variants", [])[:4]] + return tuple([v["id"] for v in r.json().get("variants", [])[:4]]) def upload_image_file(path: str) -> str: """Upload a local PNG file to Printify. Returns image ID."""