gwt-read sources¶
gwt-read reads several record sites through one shape: the same files, the same gate and cache, the same STOP rules. Each site is a source, declared once in a registry, and gwt-read builds the source's subcommands, its gate, its cache rules and its canary controls from that declaration.
| Source | Site | Page |
|---|---|---|
familysearch |
FamilySearch historical records | gwt-read |
pares |
PARES, the Spanish state archives' catalogue | gwt-read |
genae |
Genae, a volunteer index of parish records | gwt-read |
boe |
BOE and Gaceta de Madrid text search | gwt-read |
insee |
INSEE's French deaths files, offline | gwt-read |
openarch |
Open Archives: Dutch, Belgian and other archives' person records | openarch |
wikitree |
WikiTree, the shared one-world tree, public profiles | wikitree |
archiveorg |
Internet Archive: scanned books and registers, catalogue and full text | archiveorg |
matchid |
matchID: France's deaths since 1970, searched online | matchid |
riksarkivet |
Riksarkivet (Sweden): scanned church books and other volumes, with IIIF manifests | riksarkivet |
tna |
The National Archives (UK) Discovery: wills, service records, 2,500 UK archives | tna |
census1950 |
The 1950 US census name index (NARA), with the page scans | census1950 |
digitalnz |
Papers Past, New Zealand newspapers, through the DigitalNZ API | digitalnz |
dokuklik |
Dokuklik: Basque parish baptisms, marriages and burials | dokuklik |
hiski |
HisKi: Finnish parish registers index | hiski |
digitalarkivet |
Digitalarkivet: Norway's transcribed censuses and church books | digitalarkivet |
geneteka |
Geneteka: Polish parish and civil registers index | geneteka |
freeukgen |
FreeREG and FreeCEN: UK parish registers and censuses | freeukgen |
bvph |
BVPH: Spanish historical newspapers, searched inside their text | bvph |
delpher |
Delpher: Dutch newspapers 1618-1995, searched inside their text | delpher |
trove |
Trove: Australian newspapers, with your own free API key | trove |
kramerius |
Kramerius: Czech digital libraries' newspapers and books, searched inside their text | kramerius |
gwt read --help lists every source the installed version knows.
What a source declares¶
A source is one module, read/gwt_<id>.py. It sets SOURCE, the slug the ledger and each candidate carry, and SPEC:
| Key | What it is |
|---|---|
id |
the slug again; the subcommand is gwt read <id> |
help |
one line for gwt read --help |
pace |
{start, floor, ceil} seconds between requests, for the source's own gate (~/.cache/gwt-gate-<id>) |
cache_rules |
((url regex, seconds or None), ...): how long gwt-fscache keeps each kind of answer; None never expires |
controls |
[{argv: [...], min_count: N}]: canary searches, given as search options, that any working copy of the site answers with at least N rows |
result_markers |
regexes found only on a page holding at least one row of results |
negative_markers |
regexes of the site's own words for "nothing found" |
and the functions add_search_args(sp) and search(a, R), plus add_record_args(sp) and record(a, R) when the site has a record page. search asks for every page through R.get(url, headers, refresh=a.refresh, page=gwt_reader.page_check(SPEC)), writes the envelope and candidates with gwt_reader.write_search, prints the lines with gwt_reader.print_paths, and returns 0 with rows or 2 with none.
A source that reads a JSON API, not HTML pages, works the same way with one change: it passes expect=<a function that checks the answer's shape> to R.get instead of page=, as familysearch does with is_search. An answer of another shape is a STOP. It leaves result_markers and negative_markers empty, because with no marker every page classifies unknown, and tells "none" by the answer's own count field (zero rows in a well-shaped answer).
An open API calls gwt_reader.get_api(R, url, headers, expect=...) instead of R.get. It is the same call, except that a 5xx or no answer at all (a timeout, a refused connection) means the service is down: a wall (exit 3, the gate hears a hit), never an error a lane logs and moves past. A 4xx stays an error. openarch, wikitree, archiveorg and matchid read this way.
The page classifier¶
gwt_reader.classify(page, SPEC) gives one of three answers:
| Answer | When | What gwt-read does |
|---|---|---|
hits |
a result marker is on the page | writes the rows, exit 0 |
none |
no result marker, and a negative marker is | writes an empty candidates file, exit 2 |
unknown |
neither | a STOP: nothing written or cached, the gate pauses every lane, exit 3 |
So a page skeleton with no rows, a block page, or wording the site changed is never logged as "no results". Pick a result marker that only appears on a row (a row's class, a record link), never the empty container the skeleton also has.
Canaries¶
A canary is a public search with a known minimum: a very common name with a count the site has given for years, with room to spare. Never use a family's own surname or a person from someone's tree; the canary is in public code. gwt read <id> control runs them without the cache, and search --controls before|after|both runs them around one search (canary controls). An offline source has none.
Add a source¶
- Copy
read/_template.pytoread/gwt_<id>.pyand fill inSPECand the functions. The template reads an invented site, so it runs as written. - Add the module name to
SOURCE_MODULESinread/gwt_reader.py, and its line to[tool.hatch.build.targets.wheel.force-include]inpyproject.toml. The wheel check imports every module the registry names. - Write
read/test_gwt_<id>.pywith invented fixture pages underread/fixtures/<id>/: one with rows, one with the site's "nothing found" words, one skeleton. TheTemplateclass inread/test_registry.pyis the pattern. - Put the source's mutant rows in
read/mutants.d/<id>.txt(same format asmutants.txt), so sources added at the same time never edit one file. At least: the negative marker removed, the result marker removed, the pace made faster. - Write
docs/sources/<id>.md: what the site holds, its terms and robots.txt, the pace and why, the canary and its minimum, what a candidate is.
Before the first live run, read the site's robots.txt and terms, send the project User-Agent, and keep to a person's pace. A source that needs a login, solves a captcha or works around a block does not belong here.