repomatic.deps.dep_report module¶
Shared rendering of dependency-update reports.
The markdown diff, held-back, and cooldown-bypass tables, the release-notes
sections, and the comparison URLs that every updater’s PR body and terminal
output route through: sync-uv-lock, sync-deps, sync-dep-sources, the
three version-sync bumpers (sync-tool-versions, sync-action-pins,
sync-workflow-pins), and audit --fix.
The computations stay with their datasources (repomatic.deps.uv for the lock,
repomatic.release.version_sync for GitHub/PyPI/npm); this module only renders
their results.
- repomatic.deps.dep_report.RELEASE_NOTES_MAX_LENGTH = 2000¶
Maximum characters per package release body before truncation.
- repomatic.deps.dep_report.link_name(name, name_urls)[source]¶
Render a table’s subject cell, linked when a URL is known for it.
- repomatic.deps.dep_report.markdown_section(heading, note, headers, rows)[source]¶
Assemble a report section: heading, optional intro, and a table.
The one place the section layout is defined, so every updater’s PR body keeps the same shape. The alignment row is derived from headers rather than written out, which is what stops a column from being added to one and not the other.
- Parameters:
heading (
str) – Full heading line, emoji included, without the##. Omitted when empty, for a caller embedding the table under a title of its own (the release PR’s blocker banner sits inside a[!CAUTION]blockquote that already says what it is).note (
str) – Intro paragraph shown between heading and table. Omitted when empty.rows (
list[tuple[str,...]]) – One tuple of pre-rendered cells per row, each as long as headers.
- Return type:
- Returns:
The rendered markdown, with no trailing newline.
- repomatic.deps.dep_report.format_upload_date(iso_datetime)[source]¶
Format an ISO 8601 datetime as a human-readable date string.
- repomatic.deps.dep_report.format_released(raw_upload, reference)[source]¶
Format an upload time as a date, optionally with a relative hint.
- Parameters:
- Return type:
- Returns:
A string like
2026-06-24 (2 days ago), the bare date when reference isNone, or empty when raw_upload is empty.
- repomatic.deps.dep_report.format_eligible(eligible, today)[source]¶
Render an eligibility date with a human-readable countdown.
- repomatic.deps.dep_report.pypi_name_urls(changes)[source]¶
Map each changed package name to its PyPI project URL.
Convenience for
format_diff_table()’sname_urlswhen the changes come from a PyPI-resolved source (sync-uv-lock,fix-vulnerable-deps).
- repomatic.deps.dep_report.format_exclude_newer_note(exclude_newer)[source]¶
Render the uv
exclude-newercutoff sentence for a diff table.The
format_diff_table()counterpart forsync-uv-lockandfix-vulnerable-deps, which gate on uv’s absoluteexclude-newertimestamp. The relative-cooldown updaters (repomatic.release.version_sync) render their ownminimum-release-agenote instead.- Parameters:
exclude_newer (
str) – ISO 8601 datetime from the lock’s[options].exclude-newer, as carried byrepomatic.deps.uv.LockFile.exclude_newer, or empty.- Return type:
- Returns:
A one-line markdown note, or empty when exclude_newer is empty.
- repomatic.deps.dep_report.format_diff_table(changes, upload_times=None, cooldown_note='', comparison_urls=None, reference_date=None, name_urls=None, heading='Updated packages', subject='Package', released_overrides=None)[source]¶
Format version changes as a markdown table with heading.
The shared PR-body table for every dependency updater (
sync-uv-lock,fix-vulnerable-deps,sync-tool-versions,sync-action-pins,sync-workflow-pins) so they all render identically.When
upload_timesis provided, a “Released” column is added so reviewers can visually verify that all updated packages respect the cooldown. A row whose version was decided outside that cooldown check (the upstream toolkit’s lockstep-aligned pin) marks itself throughreleased_overridesinstead of showing a date, so the exemption reads as deliberate rather than as missing data. Whencooldown_noteis provided, that pre-rendered sentence (the absoluteexclude-newercutoff for uv, or the relativeminimum-release-agecutoff for the version-sync updaters) is shown above the table.- Parameters:
changes (
list[tuple[str,str,str]]) – List of(name, old_version, new_version)tuples as returned byrepomatic.deps.uv.diff_lock_versions().upload_times (
dict[str,str] |None) – Optional mapping of package names to ISO 8601 upload-time strings, as returned byrepomatic.deps.uv.parse_lock_upload_times().cooldown_note (
str) – Optional pre-rendered markdown sentence describing the cooldown cutoff, shown above the table. Build it withformat_exclude_newer_note()(uv) orrepomatic.release.version_sync.format_cooldown_note()(version-sync).comparison_urls (
dict[str,str] |None) – Optional mapping of names to comparison URLs, linked on the change cell (seebuild_comparison_urls()).reference_date (
date|None) – When set, each “Released” date gains a relative hint (2026-06-24 (2 days ago)) measured from this date.name_urls (
dict[str,str] |None) – Optional mapping of names to a URL the name links to (PyPI, GitHub, npm). Names absent from the mapping render plain. Passpypi_name_urls()for PyPI-sourced changes.heading (
str) – Noun after## 🆙(e.g.Updated tools).subject (
str) – Header for the first (name) column (e.g.Tool,Action).released_overrides (
dict[str,str] |None) – Optional mapping of names to literal markdown replacing their “Released” cell. An override on a changed name also forces the column on, even withoutupload_times; entries for unchanged names are ignored.
- Return type:
- Returns:
A markdown string with a
## 🆙 {heading}heading and table, or an empty string if there are no changes.
- class repomatic.deps.dep_report.HeldBackPackage(name, locked_version, available_version, released, eligible)[source]¶
Bases:
objectA newer release withheld from the lock by the
exclude-newercooldown.Built by
repomatic.deps.uv.compute_held_back_packages()for the ## Held back by cooldown report section: a package has already published a newer version, but it is still inside the cooldown window, souv lock --upgradekeeps the olderlocked_version.- released: str¶
Upload date of
available_version(YYYY-MM-DD), or empty when the lock records no upload time (a git or path source).
- eligible: str¶
Date
available_versionleaves the cooldown and becomes lockable, with a human-readable countdown (2026-06-25 (in 4 days)), or empty when it cannot be computed.
- repomatic.deps.dep_report.EXCLUDE_NEWER_HELD_BACK_NOTE = 'Newer releases already published but withheld because they are still inside the [`exclude-newer`](https://docs.astral.sh/uv/reference/settings/#exclude-newer) cooldown window.'¶
Intro paragraph for the
sync-uv-lockheld-back section.The
repomatic.release.version_syncupdaters pass their ownminimum-release-agewording toformat_held_back_table()instead.
- repomatic.deps.dep_report.HELD_BACK_COLUMNS = ('Locked', 'Available', 'Released', 'Eligible')¶
Held-back columns following the caller-supplied subject column.
Shared with
repomatic.sync_ops.print_held_back_table(), so a run’s markdown PR body and its terminal table name the same columns in the same order instead of drifting apart as two hand-kept literals.
- repomatic.deps.dep_report.build_held_back(name, pinned, available, available_date, min_age, today)[source]¶
Assemble a
HeldBackPackagerow from raw selection data.The formatting half of the version-sync held-back report:
repomatic.release.version_sync.select_held_back()picks the withheld candidate, and this turns its raw version and upload date into the samereleased/eligiblestringsrepomatic.deps.uv.compute_held_back_packages()produces for uv, soformat_held_back_table()renders both identically. Unlike the uv path, no second resolution is needed: the candidates are already in hand from the datasource sweep.- Parameters:
name (
str) – Display name (package, action slug, or tool).pinned (
str) – Version this run settled on (held in place by the cooldown).available (
str) – The newer version still inside the cooldown window.available_date (
str) – Upload date of available (YYYY-MM-DD), or empty.min_age (
timedelta) – Theminimum-release-agecooldown width.today (
date) – Reference date for the relative countdown.
- Return type:
- Returns:
A populated
HeldBackPackage.
- repomatic.deps.dep_report.format_held_back_table(held_back, note='Newer releases already published but withheld because they are still inside the [`exclude-newer`](https://docs.astral.sh/uv/reference/settings/#exclude-newer) cooldown window.', *, name_urls=None, subject='Package')[source]¶
Format cooldown-withheld releases as a markdown section.
Shared by every cooldown-gated updater:
sync-uv-lock(rows fromrepomatic.deps.uv.compute_held_back_packages()) and the version-sync commands (rows frombuild_held_back()), so the section renders identically.- Parameters:
held_back (
list[HeldBackPackage]) – Withheld releases asHeldBackPackagerows.note (
str) – Intro paragraph describing the cooldown. Defaults to the uvexclude-newerwording; version-sync passes itsminimum-release-agewording.name_urls (
dict[str,str] |None) – Optional mapping of names to a URL the name links to (PyPI, GitHub, npm). Names absent from the mapping render plain.subject (
str) – Header for the first column (e.g.Action,Tool).
- Return type:
- Returns:
A markdown string with a
## ⏸️ Held back by cooldownheading and table, or an empty string when held_back is empty.
- repomatic.deps.dep_report.BYPASS_NEEDS_RELEASE = 'needs release'¶
Expiry placeholder for a freeze holding an unreleased version.
A fixed-timestamp
exclude-newer-packageentry whose held version has no upload time in the lock (a git, path, or otherwise unpublished source) can never age past the rollingexclude-newercutoff on its own: the freeze only ends once the package ships a release the lock can adopt. The markdown report renders the marker in italics to set it apart from real dates.
- class repomatic.deps.dep_report.BypassForecast(name, held_version, expires)[source]¶
Bases:
objectA cooldown-bypass freeze and the date it self-clears.
Built by
repomatic.deps.uv.compute_bypass_forecasts()(freezes still active) andrepomatic.deps.uv.compute_pruned_forecasts()(freezes the run just cleared) for the## ❄️ Cooldown bypassesreport section: a fixed-timestampexclude-newer-packageentry holdsnameatheld_versionuntil that version ages past theexclude-newercutoff, at which pointsync-uv-lockprunes the entry and the package resumes normal cooldown resolution.- expires: str¶
Date the freeze expires and the entry is pruned, with a human-readable countdown (
2026-07-08 (in 2 days), in the past for an already-cleared freeze),BYPASS_NEEDS_RELEASEwhen the held version has no upload time in the lock, or empty when there is no rollingexclude-newerspan to forecast against.
- repomatic.deps.dep_report.BYPASS_SECTION_NOTE = 'Packages pulled in ahead of the cooldown by an [`exclude-newer-package`](https://docs.astral.sh/uv/reference/settings/#exclude-newer-package) freeze. Each entry is cleared from `pyproject.toml` automatically once its held version ages past the `exclude-newer` cutoff.'¶
Intro paragraph for the
sync-uv-lockcooldown-bypasses section.
- repomatic.deps.dep_report.BYPASS_COLUMNS = ('Package', 'Held at', 'Held until')¶
Columns of the cooldown-bypass table.
Shared with
repomatic.sync_ops.print_bypass_table()for the reasonHELD_BACK_COLUMNSis.
- repomatic.deps.dep_report.format_bypass_section(forecasts, pruned=None, frozen=None, *, name_urls=None)[source]¶
Format the cooldown-bypass lifecycle as a single markdown table.
The
sync-uv-lockreport section coveringexclude-newer-packagefreezes. Every lifecycle state is a row in one table so the section scans like the## 🆙 Updated packagesone: freezes still active render plain, entries this run rewrote into freeze cutoffs are labelled📌 frozen:, and expired entries this run removed frompyproject.tomlare labelled🧹 cleared:, keeping the version and expiry data the freeze had. A freeze holding an unreleased version is labelled🚧 unreleased:and itsBYPASS_NEEDS_RELEASEexpiry renders in italics.- Parameters:
forecasts (
list[BypassForecast]) – Active freezes fromrepomatic.deps.uv.compute_bypass_forecasts().pruned (
list[BypassForecast] |None) – Expired entries the run removed, snapshot byrepomatic.deps.uv.compute_pruned_forecasts()before the prune.frozen (
list[str] |None) – Names of the entries the run rewrote into freeze cutoffs; their forecasts rows get the📌 frozen:label.name_urls (
dict[str,str] |None) – Optional mapping of names to a URL the name links to. Names absent from the mapping render plain.
- Return type:
- Returns:
A markdown string with a
## ❄️ Cooldown bypassesheading and table, or an empty string when there is no row to report.
- repomatic.deps.dep_report.fetch_release_notes(changes)[source]¶
Fetch release notes for all updated packages.
For each package with a new version, discovers the GitHub repository via PyPI and fetches the release notes from GitHub Releases for all versions in the range
(old, new]. Falls back to a changelog link from PyPIproject_urlswhen no GitHub Release exists.- Parameters:
changes (
list[tuple[str,str,str]]) – List of(name, old_version, new_version)tuples.- Return type:
- Returns:
A dict mapping package names to
(repo_url, versions)tuples whereversionsis a list of(tag, body)pairs sorted ascending. Only packages with at least one non-empty body are included. When a changelog URL is used as fallback,tagis empty andbodycontains a markdown link.
- repomatic.deps.dep_report.format_release_notes(notes)[source]¶
Render release notes as collapsible
<details>blocks.A
### Release notesheading (an h3, nesting the section under the PR body’s h2 update table) with one collapsible section per package, each version introduced by an h4 tag heading. Long release bodies are truncated toRELEASE_NOTES_MAX_LENGTHcharacters with a link to the full release.- Parameters:
notes (
dict[str,tuple[str,list[tuple[str,str]]]]) – A dict mapping package names to(repo_url, versions)tuples whereversionsis a list of(tag, body)pairs, as returned byfetch_release_notes().- Return type:
- Returns:
A markdown string with the release notes section, or an empty string if no notes are available.
- repomatic.deps.dep_report.build_comparison_urls(changes, notes)[source]¶
Build GitHub comparison URLs from version changes and release notes.
Uses the tag format discovered by
fetch_release_notes()to construct comparison URLs. Only packages with both old and new versions and a known GitHub repository are included.A package whose notes carry no tag at all is skipped. That happens when
fetch_release_notes()found no GitHub release for the range and fell back to a changelog link, which is positive evidence that the tags this URL would name do not exist: guessing avprefix there yields a 404 in the PR body.- Parameters:
- Return type:
- Returns:
Dict mapping package names to GitHub comparison URLs.