Skip to content

Rigour 2.3.0

Choose a tag to compare

@pudo pudo released this 30 Jul 11:42
· 71 commits to main since this release

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 form 2026-07-29T08:54:49 instead of appending an offset suffix, making it the exact inverse of iso_datetime(). The same logical field was shipping in two shapes across the stack — followthemoney wrote updated_at with +00:00, zavod wrote it without — because callers who wanted the truncated form bypassed datetime_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:30 offset 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 from naive_now() (or zavod's Version.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 trusting urlparse, 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 None rather than falling through to the dot heuristic, which used to invent a host from the path (mailto:foo@bar.com became http://mailto:foo@bar.com/). Neither mailto: nor file: 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/telecoms and their long forms).

Internal

  • Modernised type annotations across the package to PEP 585 / 604 (list[str], X | None), matching the >= 3.10 floor, and sorted import blocks and __all__. No behaviour change. One public signature moved with it: __eq__ now takes object rather than Any, matching object.__eq__.
  • LangStr.__new__ is bound to a TypeVar, so a subclass constructed through it type-checks as the subclass rather than as LangStr.
  • genscripts formats generated tables with ruff instead of black; output is byte-identical, and the generated data files are unchanged.
  • Dependency bumps: serde 1.0.229, serde_json 1.0.151, actions/setup-python 7.

Upgrade notes

  • datetime_iso() output no longer carries a +00:00 suffix. Consumers comparing serialized timestamps as strings, or asserting on the offset-bearing form, need to adjust. Parsing back through iso_datetime() is unaffected.
  • clean_url() rejects more inputs, returning None for prose, bare decimals and mailto: / 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