# Copyright (c) 2026 Krzysztof Rudnicki. MIT licence. """Release versions named after the popes, in order. Release *n* is pope *n*: 0.0.0 is Święty Piotr Apostoł, and the version is a pure function of the ordinal rather than a judgement about the size of a change. The one fixed point is Jan Paweł II at 2.13.7, which is what picks the two constants below -- eight patches per minor, and majors opening at ordinals 33 and 153: (264 - 153) = 111 = 13 * 8 + 7 -> 2.13.7 Both earlier majors happen to divide evenly by eight as well (32 = 4 * 8, 120 = 15 * 8), so no bracket needs a fudge factor. The anchors are not arbitrary either: 33 is Sylwester I, pope through the Edict of Milan and Nicaea, and 153 is Wiktor II, the first pope elected after the 1054 schism. """ from __future__ import annotations import json from dataclasses import dataclass from pathlib import Path from typing import Any, Final # Ordinals at which a new major version opens. Ascending, first entry 1. MAJOR_STARTS: Final = (1, 33, 153) PATCHES_PER_MINOR: Final = 8 # Which release this build is. Bump by one per release; everything else, # including the version string in pyproject.toml, follows from it. RELEASE_ORDINAL: Final = 11 _POPES_FILE: Final = Path(__file__).with_name("popes.json") @dataclass(frozen=True, slots=True) class Pope: """One entry of the papal list, as the version ladder needs it.""" ordinal: int name: str # The pl.wikipedia article title, which is name plus any "(papież)" # disambiguator -- so it is the URL slug, and name is not. title: str reign: str portrait: str latin: str def _load() -> tuple[Pope, ...]: """Read popes.json once, at import.""" document: dict[str, Any] = json.loads(_POPES_FILE.read_text(encoding="utf-8")) return tuple( Pope( ordinal=int(entry["ordinal"]), name=str(entry["name"]), title=str(entry["title"]), reign=str(entry["reign"]), portrait=str(entry["portrait"]), latin=str(entry["latin"]), ) for entry in document["popes"] ) POPES: Final = _load() def version_for(ordinal: int) -> str: """Return the dotted version string for the given papal ordinal. Raises: ValueError: if the ordinal is outside the known list. """ if not 1 <= ordinal <= len(POPES): message = f"no pope with ordinal {ordinal}; the list has {len(POPES)}" raise ValueError(message) major = max(index for index, start in enumerate(MAJOR_STARTS) if ordinal >= start) offset = ordinal - MAJOR_STARTS[major] return f"{major}.{offset // PATCHES_PER_MINOR}.{offset % PATCHES_PER_MINOR}" def pope_for(ordinal: int) -> Pope: """Return the pope with the given ordinal. Raises: ValueError: if the ordinal is outside the known list. """ if not 1 <= ordinal <= len(POPES): message = f"no pope with ordinal {ordinal}; the list has {len(POPES)}" raise ValueError(message) return POPES[ordinal - 1] def ordinal_for(version: str) -> int: """Return the papal ordinal a version string denotes. Raises: ValueError: if no release carries that version. """ for pope in POPES: if version_for(pope.ordinal) == version: return pope.ordinal message = f"no release carries version {version!r}" raise ValueError(message) __version__: Final = version_for(RELEASE_ORDINAL) POPE: Final = pope_for(RELEASE_ORDINAL)