DEV Community

Cover image for My requirements.txt Has Exactly Two Lines: Pillow and reportlab — Here's What That Bought Me"**

My requirements.txt Has Exactly Two Lines: Pillow and reportlab — Here's What That Bought Me"**

My requirements.txt Has Exactly Two Lines: Pillow and reportlab. Here's What That Bought Me

I learned this the expensive way. I had uploaded a batch of 39 products when a CSV came back rejected, and then a second one. My pipeline had resubmitted the same batch twice because nothing tracked what was already posted, and I came close to losing the account before I had sold anything at all. The scripts I wrote that week are the reason I now have a requirements file with two lines in it, both of them chosen deliberately.

The two-line rule

Here is the whole file:

pillow
reportlab
Enter fullscreen mode Exit fullscreen mode

Pillow renders the covers. reportlab renders the PDFs. Neither one is a framework, and neither one needs a cloud account. On a 1 vCPU, 2 GB server with no GPU, dependency weight is not a matter of taste. Every extra package is another thing that can fail at 2 a.m. while you're asleep.

The part that surprised me: make_metadata.py and upload_tracker.py don't need either package. They run on the standard library alone, no install at all. The two heaviest jobs in the pipeline turned out to be the two lightest, once I stopped reaching for a library first.

Platform CSVs reject you before a human ever sees your work

Each channel wants a different column layout, and none of them is forgiving. Adobe wants 5 columns, Vecteezy wants 4, Dreamstime wants 15. Get the header wrong by one field and the upload fails instantly, no review, no explanation.

make_metadata.py writes the header each platform demands and nothing else:

python make_metadata.py ./my-assets --platform adobe --out metadata.csv --ext .jpg
python make_metadata.py ./my-assets --platform dreamstime --out dreamstime.csv --ext .jpg
Enter fullscreen mode Exit fullscreen mode

If you pass --titles titles.json it uses real titles and keywords. Without it, the filename gets title-cased and used as a placeholder. That is fine for a first pass and wrong for a submission, so replace it before you hit upload.

Double posting is the fastest way to get flagged

This is the mistake from the top of this post, and the one I will not repeat. upload_tracker.py is a small ledger with three commands:

python upload_tracker.py pending items.txt
python upload_tracker.py done "asset-001.jpg"
python upload_tracker.py status
Enter fullscreen mode Exit fullscreen mode

Re-running a batch no longer double-posts, because the ledger already remembers. No clever design involved, just a file that refuses to forget.

The memory discipline that makes a 2 GB box viable

The full pipeline packaged 39 products, 856 MB of output, on a single core with 2 GB of RAM. It works because of a handful of habits:

  • One process at a time. Never fan out image jobs in parallel on a 1-core box.
  • Call gc.collect() after each asset to free memory between items.
  • Measure peak RSS, not average. resource.getrusage(RUSAGE_SELF).ru_maxrss tells you the real ceiling.
  • Process one file per subprocess for heavy render steps, so memory goes back to the OS.
  • Render at the resolution you need. A preview at 60 dpi is enough for inspection.

None of this is clever. All of it is the difference between a pipeline that finishes and one that gets killed halfway through a batch.

The four failures the kit actually removes

Making the art is the fun part. These are the four steps that stall solo makers:

  1. Metadata. make_metadata.py writes the exact CSV header each channel demands.
  2. Double posting. upload_tracker.py makes a repeat run impossible.
  3. Doc to product. md2pdf.py content.md product.pdf "My Product Title" produces a styled PDF in one command.
  4. Packaging. pack_product.py my-product "My Product" "A short subtitle" 9 writes the zip, a 1280×720 cover, and a 600×600 thumbnail. A zip with a real cover sells. A bare file does not.

The kit and the rest of the pipeline

This layer runs on a free stack. Hermes Agent handles orchestration, retries, and scheduling at $0. 9Router routes the LLM calls. The scripts are the packaging layer that sits underneath it all.

The numbers behind the rules: 39 products packaged, about 19,000 lines of pipeline code in the parent project, and 300+ pages of research behind the constraints the scripts enforce. All of it validated on 1 vCPU, 2 GB of RAM, no GPU.

If your requirements file has grown past two lines, that might be the problem. You can drop the ones that do the CSV work entirely: make_metadata.py and upload_tracker.py need nothing but Python. The two lines that remain have earned their place.

Get compliance metadata kit


Building digital products? Free guides & kits for digital product sellers.

Top comments (0)