unittest the spreadsheet-row to Sume bulk item builder, no network
A pure function that turns a spreadsheet row into a Sume bulk item, with four unittest cases for trimming, price format, a spend cap in range and blank SKUs.

Keep the code that turns a spreadsheet row into a Sume bulk item as a pure function, with no network call inside it, and test it with the standard library's unittest. A bulk create validates all items before any queue exists, so a wrong item costs you a whole 400 round trip, and a wrong but valid item costs real money. Both are cheaper to catch in a test that runs in a few milliseconds.
The file below holds the builder and four tests. Run it with python3 items_test.py; it needs no key, no network and no packages.
The builder and its tests
The builder returns one element for the items array. Each element is a normal single-run body, so the fields are those of POST /v1/formats/{handle}/{slug}/runs.
import unittest
def build_item(row, cap=3.0):
"""One spreadsheet row -> one bulk item body."""
sku = str(row["sku"]).strip()
if not sku:
raise ValueError("empty sku")
return {"instruction": f"15 second product video for {row['title'].strip()}",
"input": {"sku": sku, "price": f"{float(row['price']):.2f}",
"image_url": row["image"].strip()},
"generation_spend_cap_usd": cap}
class BuildItemTest(unittest.TestCase):
def test_trims_and_formats(self):
item = build_item({"sku": " A1 ", "title": " Mug ", "price": "9.5", "image": " https://e.com/a.jpg"})
self.assertEqual(item["input"], {"sku": "A1", "price": "9.50", "image_url": "https://e.com/a.jpg"})
self.assertIn("Mug", item["instruction"])
def test_cap_is_always_set_and_in_range(self):
item = build_item({"sku": "A1", "title": "t", "price": 1, "image": "u"})
self.assertTrue(0 < item["generation_spend_cap_usd"] <= 500)
def test_blank_sku_is_rejected(self):
with self.assertRaises(ValueError):
build_item({"sku": " ", "title": "t", "price": 1, "image": "u"})
def test_input_stays_flat_and_small(self):
item = build_item({"sku": "A1", "title": "t", "price": 1, "image": "u"})
self.assertLessEqual(len(item["input"]), 64)
if __name__ == "__main__":
unittest.main()What each test protects
The trim test guards the most common spreadsheet fault: a stray space that turns A1 and A1 into two SKUs in your ledger. The price test fixes the string form once, so the Format always sees 9.50 and never 9.5. Whatever shape you choose for input, you choose it, since the Format reads the keys it knows and ignores the rest.
The cap test asserts that every item has a generation_spend_cap_usd greater than 0 and at most 500. The API rejects 0, rejects anything above 500, and treats a missing cap as the Format's own default. A queue has no total cap of its own, so its worst case is the sum of the item caps, and a per-item cap set in this function is the only budget control a bulk batch has.
The blank-SKU test makes bad rows fail in your code with a row you can find, not later as a failed item with only an index. The last test keeps input flat: at most 64 top-level keys per item, and 2 MiB. If your sheet has more columns, nest them under one key.
| Field | Set by the builder | API rule |
|---|---|---|
instruction | Product title in a fixed sentence | At least one of instruction, input, previous_run_id, attachments |
input | sku, price, image_url | Object, at most 64 top-level keys and 2 MiB |
generation_spend_cap_usd | 3.0 unless overridden | Above 0 and at most 500 |
Extending it
Add a case per bug you meet in production: a row with an empty image cell, a price with a thousands separator, a title with a newline. Each becomes a named failing test before it becomes a fix. Run the suite in CI ahead of the step that calls the API, so a broken spreadsheet template never reaches a paid create.
Pair it with a body-level check for item count, concurrency and the 4 MiB limit, which no single-item test can see.
Keep the test data small and fake. Use example.com URLs and invented SKUs; the builder never fetches an image, so nothing in the suite touches a real asset. When a real row broke a production batch, copy only the shape of that row into the test, and leave the real product names and links out of the repository.
Sources
Related posts
More in Developers
- unsupported_capability names sume/auto: fix a Sume ad clip request
A 400 unsupported_capability on sume/auto hides the resolved model but lists accepted values in supported. How to fix duration, resolution or audio.
- Format run cap headroom: usage.cap limit, counted and remaining
usage.cap on a run receipt splits the spend-cap check into limit, counted and remaining USD micros. Read it to see how close a run is to failing.
- Validate Wan 3.0 reference limits in Python before submitting
wan-3.0 takes 10 images, 5 videos (15 s total, 16 fps minimum) and 5 audios (15 s total). A short Python check catches an over-limit manifest early.
- verifyWebhook in a fetch handler: four rules, 204 for unknown events
Use @sume-com/sdk verifyWebhook on the raw body, await it, treat false as 401 and answer unknown events with 204. A runnable handler for Workers, Deno and Node.
Written by Sume