Brick Work source layer (4.0)
The source clients write the 4.0 collection file (brickwork.db). A source is one provider's client: BrickLink, Rebrickable,
Brickset (and Brickognize for photos, with the Identify screen). The pipeline's pictures, metadata and market_price steps call
them through handlers (pipeline); the part lists are store4.jobs' (bricklink_subsets, rebrickable_set). The
design is in the 4.0 design (§ 3.2, § 3.8, § 5.2, § 6.8).
Credentials and limits
Keys come from the environment first (all of a source's variables), then from the encrypted source_key table (Fernet, secret
BW_SECRET_KEY; core.keys, installed at start as the provider store4.keys reads):
- BrickLink:
BW_BRICKLINK_CONSUMER_KEY,BW_BRICKLINK_CONSUMER_SECRET,BW_BRICKLINK_TOKEN_VALUE,BW_BRICKLINK_TOKEN_SECRET - Rebrickable:
BW_REBRICKABLE_API_KEY; Brickset:BW_BRICKSET_API_KEY
Daily budgets: BrickLink BW_BRICKLINK_DAILY_BUDGET (default 4,400, a margin under the account's 5,000), Rebrickable
BW_REBRICKABLE_DAILY_BUDGET (1,000), Brickset 100 (only getSets is charged; checkKey and getKeyUsageStats are not). BrickLink and
Rebrickable calls are at least one second apart. The counters are the rows of source_budget (source, UTC day, used, blocked_until,
last_attempt_at), shared with store4.jobs, which counts its own calls on the same rows. The Rebrickable catalogue pictures are plain
downloads and count against nothing. The Brickset metadata step batches: one getSets call (counted once) asks for up to 500 sets, the job in hand and the other queued or waiting jobs whose next step is Brickset metadata, and ends each of those jobs' step with its own result.
No key is written to a template, a log or an error text: replies are stored redacted, transport failures have fixed texts. Redirects are refused.
One call
Source.request (core/sources/base.py): refuse a disabled source (source.enabled) or a missing key before anything is built or counted;
count the call (reserve: the daily budget, a provider's pause, the spacing) or raise WaitingForBudget(retry_at); make the HTTP request
outside any write transaction (the count is committed before the request is sent, so a budget is never overspent: a process killed
mid-request has spent that call, and the restarted step sends it again, so one call per such kill is counted twice, on the provider's
side too; restart between jobs, or once the day's budget is used up, to lose none); keep the reply in source_record under the pipeline job (unique over source, kind, key and digest, so the
same reply twice is one record); normalise in one savepoint. A savepoint that fails rolls its writes back and keeps the reply with
status='failed'. A provider's own rate pause (HTTP 429 or "throttled", Brickset's "API limit exceeded") is written as
blocked_until and honoured by every client and job of the source. A 401 or 403 carrying the provider's own JSON error is KeyRejected;
an HTML or empty answer says nothing about the key.
What each source writes
| Source / operation | Local data |
|---|---|
| BrickLink price guide | price_observation (decimal strings, words for the condition, slot bricklink_new/bricklink_used, the region and currency settings, provider_key like P:3023/1 or S:6081-1) and price_current; an empty sold guide is stored and followed by the stock guide. An inferred BrickLink link the reply proves right becomes matched (method='bricklink_api', origin='api'). |
| BrickLink orders | the full reply in source_record; the order ledger is the orders piece's |
Brickset getSets |
item_property(source='brickset') (setID, theme, subtheme, year, pieces, minifigs, name, category, availability, the first and last date at LEGO) and the retail price_observation (guide='retail', slot retail, the retail_country setting: DE/EUR, US/USD, UK/GBP, CA/CAD). A set Brickset does not know gets item_provider_status(brickset, unmatched, no_match). |
| Rebrickable part record | the BrickLink evidence in external_ids: exactly one id is a matched/external_ids link, none or several item_provider_status(bricklink, unmatched, no_match); a manual link is never replaced and a confirmed one never downgraded. |
| Pictures | picture rows and files under data/images/<source>/<kind>/<sha256>.<ext>; see below. |
Brickset valuation observations (new/used) exist only in collections migrated from 3.3, which read them from Brickset's website. The v3 API's getSets has no value field (its only price is LEGOCom.<region>.retailPrice), so nothing in 4.x writes or refreshes them; they are kept as history and shown as legacy (#173).
BrickLink prices are for the item's resolved BrickLink key and colour (items.bricklink_key, design 3.2). An item without one is
unidentified: the step ends done with the code unidentified and nothing is called (design 3.8). Brickset is asked by a confirmed
brickset link, else the Rebrickable number of a Rebrickable-born set (6081-1), else the BrickLink number of a BrickLink-born set
(6081 is read as 6081-1); up to 500 numbers go in one call.
Pictures
Three sources (rebrickable_dump, bricklink, rebrickable_api), in the order of source_priority.pictures per kind (design 6.8; the
defaults are in the pipeline). core.pictures has the handler of each source, picture_known(db, item_id, color_id) (the
Add review's warning: a stored picture, a catalogue picture_url, a resolvable BrickLink number, or a Rebrickable key and number) and the
bulk refresh (state, selection, queue). A download is a plain GET of an https URL on a host of the source, no redirects, at most 20 MB,
verified as an image before anything is written; the file is written first, then the row (a picture:stored event). Thumbnails are
core.thumbnails, generated on first request and cached under data/thumbs.
Stored keys and the embedded worker
Keys can be entered in Manage > Catalogues & keys (core.keys.save, archive; each is an event with Undo, and an export copy loses
every key, keys.strip). drainer.start (the embedded worker) and brickwork worker register the source handlers and run the queue
until nothing is pending; BW_EMBEDDED_WORKER=0 leaves draining to brickwork worker.