Skip to content

Support markers on pyproject.toml and inline script metadata dependencies #256

Description

@fniessink

The problem

A dependency declared in a pyproject.toml or in a PEP 723 # /// script block gets four things reported about it — a new version, staleness, a yank, and a vulnerability — and not one of them can be steered for a single reference. A requirements.txt pin one line away in the same repository takes the full marker language.

The gap is widest for the warnings. A pin left on a yanked release is reported at every run, a version an advisory names is reported at every run, and a project that has assessed either and decided to live with it has no way to say so. The staleness warning now reaches dependencies declared without an exact pin as well, so it reaches more references than before and remains just as unsilenceable.

The file can hold a marker

Probed 2026-08-15:

  • TOML accepts a trailing comment on an array element, and tomllib parses the spec back without it: "humanize==4.15.0", # update-time: ignore reads as humanize==4.15.0.
  • uv reads a # /// script block whose dependency array carries such a comment without complaint: a block holding "humanize==4.10.0", # update-time: ignore reported both its dependencies normally under uv tree --script.

Freezing works too, which is not obvious: uv resolves the versions but Update-time writes the pins, and uv lock --upgrade leaves the pyproject.toml byte-identical. A pin Update-time declines to rewrite therefore stays where it is, because the pin itself constrains what uv resolves.

Which scopes hold something back

Scope Holds something back?
bare ignore, ignore[update] yes — Update-time writes these pins itself, so declining to rewrite one freezes it
stale, bare and with a threshold yes — the staleness check is Update-time's own
yanked yes for an exact pin, redundant for a dependency that pins no version
vulnerable, with an identifier or a level yes for an exact pin, redundant for a dependency that pins no version
a bound, allow[update<3.13] or ignore[minor-update] not as things stand — see below
cooldown not as things stand — see below
hash-drift never — no Python reference carries a hash

A bound filters the candidate versions before the highest is picked, and Update-time never sees candidates here: uv tree --outdated reports one version per dependency, the latest. A bound could only accept or reject that single version, so a reference on 3.12.8 bounded below 3.13 would stay on 3.12.8 rather than advancing to a freshly published 3.12.9, which is not what the bound promises everywhere else. A per-reference cooldown has the same problem, and uv applies the cooldown per run through exclude-newer rather than per dependency.

Both become possible if Update-time resolves these pins against PyPI itself — get_latest_version already takes a version bound and a cooldown — but that changes who chooses the version for a pyproject.toml, which is a decision of its own rather than a detail of this issue.

Scope

  • Read a marker for a dependency in a pyproject.toml and in a # /// script block, and honour the scopes marked "yes" above.
  • Report the rest as redundant or invalid, each in its own words, as the other file kinds already do.
  • Report a recognised marker and what it held back at DEBUG, as elsewhere.

The design constraint

Placement decides how far this reaches. A trailing comment works only when the array puts each dependency on a line of its own, and dependencies = ["a==1", "b==2"] gives neither dependency a line. A [tool.update-time] table keyed by dependency name has no such limit: it reaches a single-line array, and it is the idiomatic place for tool configuration in a Python project. It costs the property that makes the comment form readable, though, since the marker no longer sits beside the reference it governs, and a PEP 723 block would need the same table inside it.

The two file kinds are read by one reader, so whichever form is chosen has to work commented out inside a # /// script block as well as in plain TOML.

Out of scope

  • Bounds and a per-reference cooldown, for the reason above.
  • Markers in a package.json, which is strict JSON and needs a placement of its own.
  • Changing which dependencies are updated, or how uv resolves them.

Documentation

  • The per-type "Markers" answer under "Python dependencies", which says these dependencies take none.
  • The per-type "Yanked dependencies" and "Vulnerable dependencies" answers, which each say the pin takes no marker to silence the warning.
  • "Where to put a marker", which says a dependency updated through uv, npm, or pnpm takes no marker, and the overview under "What is updated", which says a delegated dependency takes no marker.
  • The redundant-marker and invalid-marker sections, for the scopes that decide nothing here.
  • A changelog entry under [Unreleased].

Open questions

  1. Trailing comment, [tool.update-time] table, or both? The comment cannot serve a single-line array, and the table cannot sit beside the reference.
  2. What does a marker mean for a dependency the file declares more than once — in the project's array and in a dependency group, say? The comment form answers per line; a table answers per name.
  3. Should yanked and vulnerable on a dependency that pins no version be reported as redundant here, in the same words a requirements.txt requirement without an exact pin already gets?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions