~1,000 images, 165 products, and no click-by-click afternoon
Year
2026
Status
Internal tooling
Category
AI & Automation
Stack
Python 3.14 · Standard library only · WordPress REST API
01/Context
A first attempt to automate this through a hosted workflow tool failed for an infrastructure reason recorded in the code: the site's shared host blocks the automation vendor's cloud IP ranges. The pipeline had to be rebuilt to run from a local machine.
WordPress also cannot replace a media file's bytes in place. Refreshing a re-exported image is a delete-then-upload — a destructive operation nobody wanted to run blind across a thousand files on a flaky shared host.
02/At a glance
Problem
A seasonal drop meant getting roughly 1,000 images into WordPress under a strict naming convention, then telling each of 165 products which images are its own, in which order, with which colour label. Source filenames come from a design export and do not match the catalog's colourway names — colours appear in a different order, get abbreviated, and style names contain typos, so a naive string match silently binds the wrong swatch to the wrong colourway.
Built
Seven standalone Python CLI tools, standard library only, forming a staged pipeline from a design export to live collection pages — every writing tool dry-run by default.
Role
Sole developer and operator. Designed the naming convention, wrote the matching heuristics and the WordPress client, and ran the pipeline against production.
Changed
A drop that would otherwise be a click-by-click afternoon in the media library and ACF editor became a reviewable plan you approve and apply.
03/Architecture
Seven single-purpose CLIs over one shared matcher and one catalog CSV. Every tool that writes defaults to a dry run and requires an explicit --apply; destructive actions require two flags.
04/The system
Typo-tolerant matching, human-reviewed
A shared matcher uses token sequence and set matching with yarn-type and word-order tiebreaks to map a design-export filename onto a product and colourway from the catalog CSV.
It is deliberately not trusted blindly: every run prints its plan, and reviewing the matches is a documented operator step. The tool is confident enough to be useful and honest enough to be checked.
Delete-then-upload, one file at a time
The refresh tool compares local file sizes against the media library's recorded sizes and classifies every file as NEW, CHANGED or SAME. On apply, it deletes and re-uploads one file at a time, so a product is never imageless for more than a couple of seconds.
An interrupted refresh can simply be re-invoked; uploads skip what already exists.
Binding swatches in the catalog's own order
The linking tool writes the ACF payload for each product — style, yarn, code, price, collection, section, sort order, plus ten swatch image and name pairs in the catalog's own colour order — and picks the featured image to match swatch one.
Assignment uses a bitmask dynamic-programming pass so the whole set of swatches for a product is solved together rather than greedily one at a time.
Reconciliation as an artifact
Three read-only tools reconcile the live site against the catalog and write duplicate, missing and extra reports to disk. Those files are the record of what still needs a human — the pipeline's output includes its own to-do list.
05/What was hard
A destructive operation on an unreliable host
Delete-then-upload across a thousand files, on shared hosting, with no transaction. The mitigations are unglamorous and they are the whole project: classify before acting, act one file at a time, make re-running safe, and log every operation to an append-only file.
Names that almost match
Abbreviated colours, reordered words and typos in style names mean exact matching fails and fuzzy matching binds the wrong swatch. Token-based matching with tiebreaks got the hit rate high enough to be useful, and a mandatory dry-run review handles the rest.
Writing tools someone else can operate
Every script opens with a runbook in second person — what must be next to it, the exact commands for dry-run and full runs, what happens to odd cases, and what to run next. They were written to be handed over, not to be run only by their author.
06/Automation
Replaces renaming roughly 527 swatch files by hand, dragging around 1,000 files into the media library, and opening each of 165 product records to attach up to ten swatch images and ten colour labels in the right order. Deliberately left manual: the export, reviewing every dry-run plan, reviewing typo-tolerant matches, and deciding what to do with extras.
08/Direction & delivery
Safety-by-default applied across the whole toolkit rather than per script, with destructive actions gated behind two flags. Operational discipline shows in the retained artifacts: an append-only timestamped run log, and three audit output files kept in the working directory as the record of what still needs attention.
09/Scale
7
CLI tools
1,852
Lines of Python
0
Third-party dependencies
165
Products bound
517
Colourways
Objective size signals taken from the repository. No impact metrics are claimed that the source does not prove.
10/What I would tell the next person
Dry-run by default is not a nicety on a pipeline with delete semantics. It is the product.
Write the docstring as a runbook for the person who is not you.
When infrastructure blocks the elegant approach — a vendor's IP ranges blocked by shared hosting — rebuild rather than fight it.