epythet.provenance¶
Build provenance: which code, which version, which tools produced a site.
A documentation site is a snapshot. The reader wants to know whether it matches the repository they are looking at and the package they installed; the maintainer wants to know whether the latest push has been published yet (issue #7). This module collects that diagnosis once per build and the rest of epythet renders it in three places:
a one-line footer on the landing page (
built <UTC time> from <commit> (<branch>) · <package> <version> · about this build), appended to the rendered page byepythet.sphinx_ext;about-this-build.html, an orphan page (reachable from the footer, absent from the navigation) with the full diagnosis, rendered fromABOUT_PAGE_TEMPLATEor the project’sprovenance_template;build_info.jsonat the site root, the same data for machines, with stable keys and aschema_version; also listed inllms.txtand referenced at the top of the<package>.mdaggregate.
The [tool.epythet] provenance key is the seam: true (default) renders
all three, "minimal" renders the footer line and the JSON but no page,
false renders nothing.
Collection never fails a build. No git, no git binary, no network, a
detached HEAD: every source degrades to null fields plus an entry in the
warnings list, and the build prints one warning. The record is published,
so nothing local goes into it: remote URLs lose any credentials, path-shaped
remotes are dropped, git’s error text is scrubbed of paths, and the reproduce
lines name the clone by its remote, not by the local folder.
SOURCE_DATE_EPOCH (the reproducible-builds convention Sphinx honours too)
fixes the build time when set.
>>> from epythet.config import DocsConfig
>>> cfg = DocsConfig(project_dir="/nonexistent", name="pkg", version="1.0")
>>> info = collect_build_info(cfg, check_pypi=False)
>>> info["schema_version"], info["package"]["name"], info["git"]["available"]
(1, 'pkg', False)
>>> "about this build" in render_footer_line(info)
True
Module Attributes
Bumped when a key is renamed or removed; additions keep the version. |
|
File written at the site root. |
|
Source file (in docsrc) and document name of the full-diagnosis page. |
|
Environment variable carrying the collected JSON into the Sphinx process. |
|
Set to |
|
seconds since the epoch, fixes |
|
Seconds allowed for the PyPI lookup, in total; the build never waits longer. |
|
Seconds allowed for each git command. |
|
Characters of a commit hash shown in the footer and the summary. |
|
First line of the stamp prepended to the |
|
it is how epythet recognises its own file. |
|
The fields |
Functions
|
The about page as a |
|
The about page's template: |
|
Whether the docs can be trusted to match the repository and the package. |
|
Now in UTC, or the instant |
|
The GitHub Actions context, when the build runs there (else |
|
The directory |
|
The provenance record for one build of |
|
|
|
Which file the package metadata came from: the rule of |
|
The provenance line as plain text (no link). |
|
What git knows about |
|
The |
|
|
|
Parse the JSON the build process hands over in |
|
Remove provenance outputs a previous build left in |
|
The form of a remote URL that may appear on a public site, or |
|
The latest release of |
|
The |
|
List |
|
The Markdown source of |
|
The landing-page footer as one small HTML paragraph. |
|
The shell lines that rebuild this site from the same commit. |
|
The documentation choices as the build resolved them (theme, accent, generator...). |
|
Replace absolute paths in a diagnostic with |
|
The first |
|
Documented-module and documented-object counts from a Sphinx environment. |
|
A scheme URL without any |
Versions of epythet, Sphinx, docutils and Python in the build environment. |
|
|
Make a page template an orphan (out of the toctree) that carries the marker. |
|
Write |
- epythet.provenance.ABOUT_PAGE_FILENAME = 'about-this-build.md'¶
Source file (in docsrc) and document name of the full-diagnosis page.
- epythet.provenance.ABOUT_PAGE_TEMPLATE = '---\norphan: true\n---\n{marker}\n\n# About this build\n\n{summary}\n\n{alignment_block}\n\n## Source\n\n| | |\n|---|---|\n| Commit | {commit_cell} |\n| Branch | {branch} |\n| Tags at this commit | {tags} |\n| Working tree | {tree_state} |\n| Remote | {remote} |\n\n## Continuous integration\n\n{ci_block}\n\n## Tools\n\n| | |\n|---|---|\n| epythet | {epythet_version} |\n| Sphinx | {sphinx_version} |\n| docutils | {docutils_version} |\n| Python | {python_version} |\n\n## Configuration as resolved\n\n| | |\n|---|---|\n| theme | {theme} (Sphinx theme {html_theme}) |\n| accent | {accent} |\n| api_generator | {api_generator} |\n| ignore | {ignore} |\n| agent_outputs | {agent_outputs} |\n| aggregates | {aggregates} |\n| ai_artifacts | {ai_artifacts} |\n\n## Package on PyPI\n\n{pypi_block}\n\n## Reproduce\n\n```bash\n{reproduce}\n```\n\nThe same data, for machines: <a href="{build_info_filename}"><code>{build_info_filename}</code></a> (schema version {schema_version}).\n'¶
it is how epythet recognises its own file. Literal braces are doubled. Every value is already HTML-escaped (
render_about_page()), so a custom template may place the fields anywhere.- Type:
The Markdown source of the about page.
{marker}must stay
- epythet.provenance.AGGREGATE_STAMP_PREFIX = '> built '¶
First line of the stamp prepended to the
<package>.mdaggregate.
- epythet.provenance.BUILD_INFO_ENV = 'EPYTHET_BUILD_INFO'¶
Environment variable carrying the collected JSON into the Sphinx process.
- epythet.provenance.BUILD_INFO_FILENAME = 'build_info.json'¶
File written at the site root.
- epythet.provenance.DEFAULT_PYPI_TIMEOUT = 3.0¶
Seconds allowed for the PyPI lookup, in total; the build never waits longer.
- epythet.provenance.GIT_TIMEOUT = 10¶
Seconds allowed for each git command.
- epythet.provenance.PYPI_CHECK_ENV = 'EPYTHET_PYPI_CHECK'¶
Set to
0to skip the PyPI lookup (offline CI, tests).
- epythet.provenance.SCHEMA_VERSION = 1¶
Bumped when a key is renamed or removed; additions keep the version.
- epythet.provenance.SHORT_COMMIT_LENGTH = 7¶
Characters of a commit hash shown in the footer and the summary.
- epythet.provenance.SOURCE_DATE_EPOCH_ENV = 'SOURCE_DATE_EPOCH'¶
seconds since the epoch, fixes
built_at.- Type:
Reproducible-builds convention
- epythet.provenance.TEMPLATE_FIELDS = frozenset({'accent', 'agent_outputs', 'aggregates', 'ai_artifacts', 'alignment_block', 'api_generator', 'branch', 'build_info_filename', 'ci_block', 'commit_cell', 'docutils_version', 'epythet_version', 'html_theme', 'ignore', 'marker', 'pypi_block', 'python_version', 'remote', 'reproduce', 'schema_version', 'sphinx_version', 'summary', 'tags', 'theme', 'tree_state'})¶
The fields
render_about_page()fills; a custom template may use any subset.
- epythet.provenance.about_page(info, *, template='---\\norphan: true\\n---\\n{marker}\\n\\n# About this build\\n\\n{summary}\\n\\n{alignment_block}\\n\\n## Source\\n\\n| | |\\n|---|---|\\n| Commit | {commit_cell} |\\n| Branch | {branch} |\\n| Tags at this commit | {tags} |\\n| Working tree | {tree_state} |\\n| Remote | {remote} |\\n\\n## Continuous integration\\n\\n{ci_block}\\n\\n## Tools\\n\\n| | |\\n|---|---|\\n| epythet | {epythet_version} |\\n| Sphinx | {sphinx_version} |\\n| docutils | {docutils_version} |\\n| Python | {python_version} |\\n\\n## Configuration as resolved\\n\\n| | |\\n|---|---|\\n| theme | {theme} (Sphinx theme {html_theme}) |\\n| accent | {accent} |\\n| api_generator | {api_generator} |\\n| ignore | {ignore} |\\n| agent_outputs | {agent_outputs} |\\n| aggregates | {aggregates} |\\n| ai_artifacts | {ai_artifacts} |\\n\\n## Package on PyPI\\n\\n{pypi_block}\\n\\n## Reproduce\\n\\n```bash\\n{reproduce}\\n```\\n\\nThe same data, for machines: <a href="{build_info_filename}"><code>{build_info_filename}</code></a> (schema version {schema_version}).\\n')[source]¶
The about page as a
PageSpec.- Raises:
ConfigError – when
templatenames a field the renderer does not provide (literal braces must be doubled:{{).
- epythet.provenance.about_template(config)[source]¶
The about page’s template:
[tool.epythet] provenance_templateor the default.The key names a file relative to the project root, with the same contract as
ai_artifacts_template; the epythet marker is prepended when absent.- Raises:
ConfigError – when the file does not exist
- Return type:
- epythet.provenance.alignment(info)[source]¶
Whether the docs can be trusted to match the repository and the package.
alignedisTruewhen nothing suggests otherwise,Falsewhen a note says why they may differ,Nonewhen there is no git information to judge by.notesare the plain-language reasons, in the order shown.- Return type:
- epythet.provenance.build_time(environ=None)[source]¶
Now in UTC, or the instant
SOURCE_DATE_EPOCHnames when it is set.- Return type:
>>> build_time({"SOURCE_DATE_EPOCH": "0"}).strftime("%Y-%m-%d") '1970-01-01'
- epythet.provenance.ci_info(environ=None)[source]¶
The GitHub Actions context, when the build runs there (else
Nonefields).sha_in_historysays whether the event’s commit is in the built HEAD’s history; the publish action fast-forwards to the branch tip before building, so HEAD is normally a descendant ofGITHUB_SHA, not equal to it.- Return type:
>>> ci_info({"GITHUB_ACTIONS": "true", "GITHUB_REPOSITORY": "o/r", ... "GITHUB_RUN_ID": "42", "GITHUB_SHA": "abc", "GITHUB_REF": "refs/heads/main", ... "GITHUB_REF_NAME": "main"})["run_url"] 'https://github.com/o/r/actions/runs/42' >>> ci_info({})["provider"] is None True
- epythet.provenance.clone_dirname(remote)[source]¶
The directory
git clone <remote>creates.- Return type:
>>> clone_dirname("https://github.com/org/demo.git"), clone_dirname("git@github.com:o/r") ('demo', 'r')
- epythet.provenance.collect_build_info(config, *, check_pypi=None, pypi_timeout=3.0, dirty_exclude=None, environ=None, now=None)[source]¶
The provenance record for one build of
config’s project.- Parameters:
config – a
DocsConfigcheck_pypi (
bool|None) – query PyPI for the latest release;Nonemeans “unless theEPYTHET_PYPI_CHECKenvironment variable turns it off”pypi_timeout (
float) – seconds allowed for that querydirty_exclude (
tuple[str,...] |None) – project-relative paths left out of the dirty check, on top of the docs dir;Nonemeans the directories thegithub/gitlabtargets copy the site into (epythet.build.COPY_TARGETS)environ (
dict|None) – the environment to read CI variables from (default:os.environ)now (
datetime|None) – the build time (default:SOURCE_DATE_EPOCHif set, else now, UTC)
- Return type:
- Returns:
a JSON-serialisable dict; see the module docstring for the keys.
sitecounts areNonehere and filled in by the Sphinx extension, which knows what was documented.
- epythet.provenance.compare_versions(ours, latest)[source]¶
same/behind/aheadoflatest, orunknownwhen unparsable.- Return type:
>>> compare_versions("0.2.4", "0.2.5"), compare_versions("1.0", "1.0.0") ('behind', 'same') >>> compare_versions("0.3.0.dev1", "0.2.5"), compare_versions("x", "1") ('ahead', 'unknown')
- epythet.provenance.config_source(project_dir)[source]¶
Which file the package metadata came from: the rule of
epythet.config.
The provenance line as plain text (no link).
- Return type:
>>> info = {"built_at": "2026-09-15T14:02:00Z", "package": {"name": "dol", "version": "0.3.1"}, ... "git": {"short_commit": "a1b2c3d", "branch": "master", "dirty": True}} >>> footer_text(info) 'built 2026-09-15 14:02 UTC from a1b2c3d+dirty (master) · dol 0.3.1'
- epythet.provenance.git_info(project_dir, *, exclude=())[source]¶
What git knows about
project_dir: commit, branch, tags, dirty flag, remote.excludenames paths (relative to the project) left out of the dirty check; the build rewrites a committeddocsrc/, which must not count. Everything isNonewithavailablefalse when the directory is not a repository orgitis not installed. A detached HEAD hasbranchNone.remote_urlis the publishable form oforigin(no credentials, no local paths), seepublishable_remote().- Return type:
- epythet.provenance.github_web_url(remote)[source]¶
The
https://github.com/owner/repoform of a remote URL, orNone.>>> github_web_url("git@github.com:i2mint/epythet.git") 'https://github.com/i2mint/epythet' >>> github_web_url("https://github.com/i2mint/epythet/") 'https://github.com/i2mint/epythet' >>> github_web_url("https://gitlab.com/x/y.git") is None True
- epythet.provenance.human_time(iso)[source]¶
2026-09-15T14:02:00Z->2026-09-15 14:02 UTC.- Return type:
>>> human_time("2026-09-15T14:02:00Z") '2026-09-15 14:02 UTC'
- epythet.provenance.load_build_info(raw)[source]¶
Parse the JSON the build process hands over in
EPYTHET_BUILD_INFO.Anything that is not a record of this module’s schema is ignored, so a stale or foreign value in the environment never breaks a build.
>>> load_build_info('{"schema_version": 1, "git": {}}')["schema_version"] 1 >>> load_build_info('"str"') is None and load_build_info("{") is None True
- epythet.provenance.prune_site(html_dir, *, keep_page, keep_json)[source]¶
Remove provenance outputs a previous build left in
html_dir.Sphinx never cleans its output directory, so a project that turned
provenanceoff (or down to"minimal") would otherwise keep publishing a stale page or JSON. Returns the paths removed.- Return type:
- epythet.provenance.publishable_remote(url)[source]¶
The form of a remote URL that may appear on a public site, or
None.Credentials are dropped from scheme URLs, the user part from scp-style remotes, and path-shaped remotes (a local or
file://clone) are not published at all.>>> publishable_remote("https://me:ghp_secret@github.com/o/r.git") 'https://github.com/o/r.git' >>> publishable_remote("thor@myserver.local:repos/demo.git") 'myserver.local:repos/demo.git' >>> publishable_remote("git@github.com:o/r.git") 'git@github.com:o/r.git' >>> publishable_remote("/Users/me/bare/demo.git") is None True >>> publishable_remote("D:/repos/x.git") is None True >>> publishable_remote("file:///srv/git/demo.git") is None True
- epythet.provenance.pypi_info(name, version, *, timeout=3.0)[source]¶
The latest release of
nameon PyPI and howversionrelates to it.relationissame,behind,aheadorunknown(not on PyPI, unreachable, or unparsable versions). Any failure, including thetimeoutelapsing, is recorded inerrorand never raised.- Return type:
- epythet.provenance.pypi_latest_version(name, *, timeout=3.0)[source]¶
The
info.versionofhttps://pypi.org/pypi/<name>/json(Noneon 404).
- epythet.provenance.reference_from_agent_outputs(html_dir, info, *, package_name)[source]¶
List
build_info.jsoninllms.txtand stamp the top of<package>.md.- Return type:
- epythet.provenance.render_about_page(info, *, template='---\\norphan: true\\n---\\n{marker}\\n\\n# About this build\\n\\n{summary}\\n\\n{alignment_block}\\n\\n## Source\\n\\n| | |\\n|---|---|\\n| Commit | {commit_cell} |\\n| Branch | {branch} |\\n| Tags at this commit | {tags} |\\n| Working tree | {tree_state} |\\n| Remote | {remote} |\\n\\n## Continuous integration\\n\\n{ci_block}\\n\\n## Tools\\n\\n| | |\\n|---|---|\\n| epythet | {epythet_version} |\\n| Sphinx | {sphinx_version} |\\n| docutils | {docutils_version} |\\n| Python | {python_version} |\\n\\n## Configuration as resolved\\n\\n| | |\\n|---|---|\\n| theme | {theme} (Sphinx theme {html_theme}) |\\n| accent | {accent} |\\n| api_generator | {api_generator} |\\n| ignore | {ignore} |\\n| agent_outputs | {agent_outputs} |\\n| aggregates | {aggregates} |\\n| ai_artifacts | {ai_artifacts} |\\n\\n## Package on PyPI\\n\\n{pypi_block}\\n\\n## Reproduce\\n\\n```bash\\n{reproduce}\\n```\\n\\nThe same data, for machines: <a href="{build_info_filename}"><code>{build_info_filename}</code></a> (schema version {schema_version}).\\n')[source]¶
The Markdown source of
about-this-build.mdfor a collectedinfo.Values from the repository (branch, tags, remote, versions) are rendered as escaped inline HTML, never as Markdown: a ref name is user input.
- Return type:
The landing-page footer as one small HTML paragraph.
The commit links to GitHub when the remote is known;
about_hrefis the “about this build” link target (Noneto omit the link, asminimaldoes without a page: the JSON is linked instead). The style is inline on purpose: it must hold in every theme without a stylesheet of its own.- Return type:
- epythet.provenance.reproduce_command(config, git)[source]¶
The shell lines that rebuild this site from the same commit.
- Return type:
- epythet.provenance.resolved_config(config)[source]¶
The documentation choices as the build resolved them (theme, accent, generator…).
- Return type:
- epythet.provenance.scrub_paths(message)[source]¶
Replace absolute paths in a diagnostic with
<path>: the record is published.- Return type:
>>> scrub_paths("fatal: detected dubious ownership in repository at '/home/me/x'") "fatal: detected dubious ownership in repository at '<path>'" >>> scrub_paths("fatal: not a git repository (or any of the parent directories): .git") 'fatal: not a git repository (or any of the parent directories): .git'
- epythet.provenance.short_commit(sha)[source]¶
The first
SHORT_COMMIT_LENGTHcharacters of a commit hash.- Return type:
- epythet.provenance.site_counts(env)[source]¶
Documented-module and documented-object counts from a Sphinx environment.
- Return type:
- epythet.provenance.strip_credentials(url)[source]¶
A scheme URL without any
user:token@part (scp-style remotes pass through).- Return type:
>>> strip_credentials("https://me:ghp_secret@github.com/o/r.git") 'https://github.com/o/r.git'
- epythet.provenance.tool_versions()[source]¶
Versions of epythet, Sphinx, docutils and Python in the build environment.
- Return type:
- epythet.provenance.with_front_matter_and_marker(template)[source]¶
Make a page template an orphan (out of the toctree) that carries the marker.
YAML front matter must be the very first thing in the file, so the marker goes after it;
orphan: trueis added when the front matter lacks it, and front matter is created when there is none.- Return type:
>>> print(with_front_matter_and_marker("# Build\n")) --- orphan: true --- {marker} # Build
>>> print(with_front_matter_and_marker("---\ntitle: x\n---\n{marker}\n# B\n")) --- title: x orphan: true --- {marker} # B