Rigour 2.3.0
This release changes the timestamp wire format emitted by rigour.time, stops rigour.urls from turning free text into URL-shaped garbage, and expands the abbreviated organisation-type vocabulary used by name matching.
Highlights
Timestamps (rigour.time)
datetime_iso()now emits the truncated, naive-UTC form2026-07-29T08:54:49instead of appending an offset suffix, making it the exact inverse ofiso_datetime(). The same logical field was shipping in two shapes across the stack — followthemoney wroteupdated_atwith+00:00, zavod wrote it without — because callers who wanted the truncated form bypasseddatetime_iso()entirely.iso_datetime()reads both forms, so no reader breaks on the transition.- Aware datetimes in another zone are converted to UTC before truncation, and still warn; dropping a
+05:30offset in place would have silently shifted the timestamp. - Naive datetimes are now taken to be UTC. Previously they fell into the warn-then-
astimezone()branch, which Python resolves against the system local zone, so a naive-UTC value fromnaive_now()(or zavod'sVersion.dt) was shifted by the local offset. Fixes #263.
URL cleaning (rigour.urls)
clean_url()now screens the authority of a parsed URL instead of trustingurlparse, which accepts everything up to the first slash as the host and never rejects free text. A label typed into a spreadsheet's website column —"Social media: http://vk.com/sobolipress"— came back as"http://Social media: http://vk.com/sobolipress", and the dot heuristic made this reachable for any text containing a dot, including prose and decimals. The screen rejects whitespace, requires an extractable hostname and a valid port, accepts IP literals, and requires every label to carry an alphanumeric and stay within the DNS length limit, with a plausible public suffix on dotted hosts. Host labels are matched with a Unicode character class, so a domain is accepted both decoded (пример.рф) and as punycode.- A supported scheme with no authority now returns
Nonerather than falling through to the dot heuristic, which used to invent a host from the path (mailto:foo@bar.combecamehttp://mailto:foo@bar.com/). Neithermailto:norfile:is supported.
Name matching resources
- Added 72 abbreviated aliases for organisation types and industry domains of the kind found in registry filings and contractual documents:
corpn,assn,hldg/hldgs,svc/svcs/srvce/srvces,mgmt/mgt/mngmt,govt/gvt,cmte,agcy,natl,inst,fndn,ctr,grp,auth,proj,prod/prods,soln/solns,inds/indust,trdg,coml/comml,sys, plus sector terms (mfr/mfrs,med,hosp,chem,elec,pwr,telecom/telecomsand their long forms).
Internal
- Modernised type annotations across the package to PEP 585 / 604 (
list[str],X | None), matching the>= 3.10floor, and sorted import blocks and__all__. No behaviour change. One public signature moved with it:__eq__now takesobjectrather thanAny, matchingobject.__eq__. LangStr.__new__is bound to a TypeVar, so a subclass constructed through it type-checks as the subclass rather than asLangStr.genscriptsformats generated tables with ruff instead of black; output is byte-identical, and the generated data files are unchanged.- Dependency bumps:
serde1.0.229,serde_json1.0.151,actions/setup-python7.
Upgrade notes
datetime_iso()output no longer carries a+00:00suffix. Consumers comparing serialized timestamps as strings, or asserting on the offset-bearing form, need to adjust. Parsing back throughiso_datetime()is unaffected.clean_url()rejects more inputs, returningNonefor prose, bare decimals andmailto:/file:URLs. These previously came out as URLs that only looked well-formed, so what is lost was already broken. Reported against the sanctions release in opensanctions/followthemoney#339.
Full changelog: v2.2.3...v2.3.0