Skip to content

Releases: datapointchris/pyselfupdate

v0.4.1

Choose a tag to compare

@github-actions github-actions released this 14 Sep 20:01

v0.4.1 (2026-09-14)

This release is published under the MIT License.

Bug Fixes

  • github: Stop a bearer token following a redirect off its origin (a0cbd44)

urllib.request carries Authorization to the new host. Measured on 3.11, 3.13 and 3.14 with two local servers: a bearer sent to the first arrives intact at the second on all three, so no floor bump retires this.

It is reachable rather than theoretical because API is a reassignable module attribute and headers is caller-supplied.

_CredentialScopedRedirects drops the header when scheme+authority changes, and the opener is built once. Port counts, so two ports on one host are two origins.

CLAUDE.md records why httpx2 was weighed and declined: six packages and about 57,000 lines, into five consumers that declare no HTTP client, to serve one JSON GET that downloads nothing.

Chores

  • Sync the generated CI config to toolchain 20 (874320f)

The stamp moves to 20, carrying generator changes made earlier and never fanned out: one comment names ruff as an installed tool, and the mypy opt-in comment states what decides it rather than what used to.

  • precommit: Read dot-named tracked files with -H (289f212)

codespell skips any file whose basename starts with a dot and exits 0 rather than reporting it read nothing, so .pre-commit-config.yaml and .editorconfig sat outside the check this hook installs.

  • precommit: Regenerate at forge toolchain 21 (a4382d1)

Puts codespell's en-GB_to_en-US dictionary on the hook, so American spelling is gated at commit rather than left to habit, and excludes delimited data files, whose values are the record rather than prose.

  • precommit: Regenerate at forge toolchain 23 (dbb297e)

Drops the en-GB_to_en-US dictionary from codespell. A commit cannot stop a British spelling being written, only sweep one that already exists; the british-spelling validator reports it on the line as it is typed.

  • precommit: Regenerate at toolchain 20 (4ff9abc)

The generated comment text was rewritten. The prose now describes each check in terms of the repo it runs in, rather than the wider set of repos the file is copied across. Two counts, a registry key and a sibling repo's name are gone from it, and a third-party module is now described by what it is instead of being named.

No hook, no rev pin and no setting changed. Only comment lines differ.

  • precommit: Regenerate the forge-managed configs (3b6be8f)

Brings the generated pre-commit config and the tool configs it reads up to the current templates.

markdownlint and prettier move to their YAML spellings. Both tools read those, and YAML can carry the comment marking the file as generated, which the JSON spelling cannot.

  • pyproject: Resync the standard tool sections (d689dfa)

Updates the comment above the managed-key record to describe what the record is for without naming the generator's command.

Continuous Integration

  • release: Queue release runs per branch rather than racing them (a5bc209)

Two pushes to main inside one run's window raced: the second moved the branch while the first was computing a version, so the release push landed on a ref that had already moved and the run failed on a non-fast-forward. The later run cut the correct tag, which makes the failure harmless and red.

Documentation

  • Spell canonicalized the American way (2904307)

Missed by codespell's dictionary and by the stem list, which only find stems someone chose in advance. An enumeration of every -ise/-isation candidate in the tree found it.

  • Use American spelling in comments and prose (1fdcee9)

The fleet standard is American spelling in prose and identifiers alike, gated at commit by codespell's en-GB_to_en-US dictionary. Every hit here is a comment, a docstring or a README line; no identifier, API field or test name changes.


Detailed Changes: v0.4.0...v0.4.1

v0.4.0

Choose a tag to compare

@github-actions github-actions released this 02 Sep 02:34

v0.4.0 (2026-09-02)

This release is published under the MIT License.

Chores

  • Sync the generated configs to toolchain 18 (f24c8c2)

Both stamped files come from the fleet's version declaration: the pre-commit config and the generated workflow. Nothing here is a repo decision.

Stamp 18 carries the refcheck hook at v0.6.0, a codespell exclude widened to go.mod, and — on a private repo — runs-on naming the self-hosted pool with the actionlint config that declares the label.

  • Sync the generated configs to toolchain 19 (35d7e60)

Both stamped files come from the fleet's version declaration: the pre-commit config and the generated workflow. Nothing here is a repo decision.

Stamp 19 passes --allow-parallel-runners to golangci-lint. A repo with two Go components runs two Lint jobs at once, and on a single self-hosted box the second one dies on the shared cache lock before linting anything.

  • precommit: Drop the commit-branding hook (af76d2b)

Claude Code suppresses its own commit and PR attribution through its attribution setting, which resolves an empty string to no trailer at all. A hook that strips the trailer afterwards has nothing left to remove.

  • release: Stop tracking a changelog no consumer reads (33fbbc4)

The release body is the changelog. python-semantic-release writes it from the commit subjects in the release, and changelog in this library reads those subjects back from the releases API rather than from a tracked file, so nothing in the package or in a consumer ever opened CHANGELOG.md.

The action's changelog input goes with it, and the project's Changelog URL now points at the releases page that holds the thing.

.markdownlintignore keeps its entry: it is a forge die shared across the fleet, and ignoring an absent file costs nothing.

Documentation

  • Cite the three standards this file was restating (fa5c093)

The offline test shape and the containment rule are now standards that name this repo as their source, and the terminal-injection reason is python.md's. What stays is the venv-import check, which is this repo's own second enforcement.

Features

  • state: Key the state filename to the machine that wrote it (64fd112)

Both fields this file carries describe one box: current_version is the version installed here, and checked_at_epoch gates this box's next check. A state directory that is shared between machines therefore had two writers on one path, and a file syncer resolves that to one winner plus a conflict copy nobody reads. A machine that had not run in a week stayed inside the interval and never learned it was behind, and the version notice compared another box's install against the latest release.

state.filename takes the machine and state.machine derives it the way the rest of the fleet does — bare hostname, domain dropped, lowercased, and 'unknown' when the host cannot be read. goselfupdate and bashselfupdate derive it identically, so the three still interleave in one directory and a reader still needs one glob.

Nothing carries the file at the old name forward. It holds a check timestamp and, in the shared case, another machine's version, so adopting it would propagate the fault being fixed. Starting clean costs one extra check per tool.


Detailed Changes: v0.3.2...v0.4.0

v0.3.2

Choose a tag to compare

@github-actions github-actions released this 22 Aug 16:20

v0.3.2 (2026-08-22)

This release is published under the MIT License.

Bug Fixes

  • Never check for updates while printing a help screen (f2b4c8b)

Click and Typer run a group's callback before answering a subcommand's
--help, so a notice called from that callback fires while a help screen is printed. Nothing a notice is for happens there, and the check costs a releases-API call and a state write every time.

It lands hardest on a reader that walks a tool's help. Measured 2026-08-22: reading doit's twenty commands meant twenty-one invocations, each one reaching this, and eighteen tools on that machine write their autoupdate state on plain --help.

The reason is a new Skip.HELP and it is checked in enabled(), so <tool> update --why reports it like every other reason.

A -- ends the scan, because everything after it is the command's own argument. Skipping when it should not have costs a missed notice, which is the cheap direction to be wrong.


Detailed Changes: v0.3.1...v0.3.2

v0.3.1

Choose a tag to compare

@github-actions github-actions released this 21 Aug 18:37

v0.3.1 (2026-08-21)

This release is published under the MIT License.

Bug Fixes

  • github: Tell bandit the token-command constants hold no token (2abb09e)

B105 reads any name carrying "token" as a credential, so TOKEN_COMMAND_ENV and DEFAULT_TOKEN_COMMAND were reported as hardcoded passwords. One holds the name of an environment variable and the other the text of a command.

bandit runs in a custom block of validate.yml and had no local counterpart, so the finding could only surface as a red push. The pre-commit hook runs the same command against the same config, which is what keeps the two from disagreeing.


Detailed Changes: v0.3.0...v0.3.1

v0.3.0

Choose a tag to compare

@github-actions github-actions released this 21 Aug 17:32

v0.3.0 (2026-08-21)

This release is published under the MIT License.

Build System

  • precommit: Resync to forge toolchain 14 (3b8ecf9)

Chores

  • lint: Disable SC1091/SC1090 from the forge toolchain (667b4fd)

  • pyproject: Raise assertion verbosity instead of test verbosity (8254919)

A failing assertion truncated its diff and printed "use -vv to show", so the reader re-ran the whole suite to see it. addopts = "-vv" answered that by raising test-list verbosity as well, which is a different question: a green run printed a line per test and said nothing. verbosity_assertions raises only the half that was wanted.

Written by the forge pyproject die.

Continuous Integration

  • Drop the duplicated lint job, keep bandit (4be5cab)

The bespoke lint job ran ruff, ruff format and mypy, all three of which the generated workflow already runs. It resolved ruff through uv run from the dev group's ruff>=0.7.0 floor, which the lock puts at 0.16.0, while validate.yml and the pre-commit hook both pin 0.12.5 — two linters four minor versions apart on one tree, free to disagree.

bandit was the only step the baseline did not cover, so it moves into a custom:after:python block rather than being lost. mypy coverage widens in the move: the baseline checks . where this checked src.

Same removal bashselfupdate had for shellcheck and shfmt, and the same place logsift's bandit went.

  • Regenerate validate.yml at toolchain 16 (23dbbb8)

Catches this repo up with the version manifest: StyLua pinned to a release rather than latest, a reworded bats discovery note, and double quotes in the node block. Only the blocks this repo declares are affected.

Triggers and job structure are unchanged.

  • Rename bespoke workflow to Bespoke CI (9664c4d)

The generated validate.yml also declares name: CI, so one commit produced two indistinguishable CI rows carrying the same createdAt to the second. Ordering that pair by timestamp is undefined, so a failing bespoke run read as green whenever the baseline passed. This repo is where that was measured: no-dependencies failed while the generated job passed on the same sha, and a fleet sweep reported the repo green.

Display name only. Required status checks match job names, and a release gate names the workflow file rather than its name.

Documentation

  • Cite the standards without a machine path (a7453e8)

The citation carried an absolute path from one machine's layout. What a reader needs is the file and the section, and those do not move.

  • Cross-reference release.md instead of restating nine of its rules (2ffada4)

The self-update design rules were reproduced here with the same worked examples release.md already carries. Keep only what is specific to this repo — the 3.11 floor as a sanctioned exception, typed errors, and where the ordering matrix is asserted — and point at the standard for the rest.

Also corrects the claim that goselfupdate has no notify layer; it does, at autoupdate/autoupdate.go.

Features

  • github: Authenticate by default, resolved inside the source (0aff222)

The library refused to shell out to gh, on the principle that it should not spawn a subprocess a caller did not ask for. The consequence was that all seventeen consumers had to paste the same five-line helper, and eleven never did — so eleven tools were asking GitHub anonymously.

Anonymous is not no credential. It is 60 requests an hour charged per IP address, shared by every host behind one egress. Measured 2026-08-21 across one household: two machines checking on a timer held that pool at zero for whole hours and every tool that asked anonymously was refused, including on a laptop that had run nothing.

GitHubSource now resolves GITHUB_TOKEN_COMMAND, defaulting to gh auth token. One lever that both redirects and disables: set it to another command to use that, set it to empty to stay anonymous. Named for what it produces rather than for turning something off, because a NO_GH_TOKEN cannot say use this other source and reads as a claim about whether one exists rather than an instruction about whether to use one.

It lives on GitHubSource rather than Config because the credential is the host's business. A Source for another forge brings its own variable and its own command, and nothing above the Source protocol learns either.

The rate-limit message now names which ceiling was hit. One sentence for both told whoever hit the authenticated limit to supply a token the request already carried.

Tests default the command to empty, so the suite no longer passes or fails on whether the developer happens to be logged in to gh.


Detailed Changes: v0.2.2...v0.3.0

v0.2.2

Choose a tag to compare

@github-actions github-actions released this 04 Aug 22:04

v0.2.2 (2026-08-04)

This release is published under the MIT License.

Bug Fixes

  • Report the update in the verb that ran it (d534189)

The command is update, and --check and the daily notice both say "update", but the success and failure lines said "upgraded" and "upgrade failed". One command, one vocabulary.

Chores

  • Add .planning to gitignore (d10f941)

  • config: Adopt the standard pyright section (2bc0aa8)

Synced from forge pyproject template. With no [tool.pyright] section the editor LSP settings applied, and their ignore = ["*"] suppressed every diagnostic. A config file takes precedence over those settings, so basedpyright now reports against the same "standard" mode as the rest of the portfolio instead of reporting nothing.

  • config: Record the keys the pyproject sync owns (1f3e5a9)

forge now writes [tool.forge] managed, listing the exact keys the standard sets. Deletion on a later sync is scoped to that record, so dropping a key from the template retracts it here without having to guess which settings belong to this project.

Purely additive: nothing else in this file changed.

  • toolchain: Adopt the generated configs and CI (d0be7cf)

Brings the repo onto forge toolchain manifest 11.

bandit, refurb and pyupgrade drop out: pyupgrade is ruff's UP rules, already selected, and the other two are the manifest's deliberate narrowing to the rule set every repo actually runs.

Documentation

  • Flush dormant markdownlint violations (faa11a2)

markdownlint only runs on the files a commit touches, so unmodified docs accumulate violations invisibly. The toolchain sync bumps markdownlint to v0.47, which added MD060, and runs --all-files — surfacing every one of them at once, in the middle of an unrelated change.

Table separators are normalized to the compact | --- | style MD060 expects, which --fix cannot repair; everything else is markdownlint --fix.

  • Format the composed-update example as ruff wants it (9842930)

CI runs ruff format --check ., which formats python blocks inside markdown; the pre-commit hook only sees .py files, so aligned comments in a README example pass locally and fail there.

  • Stop normalizing the generated CHANGELOG (109706e)

semantic-release regenerates CHANGELOG.md on every release, so a markdownlint fix there is undone on the next one and comes back as a conflict when a local commit rebases onto the release.


Detailed Changes: v0.2.1...v0.2.2

v0.2.1

Choose a tag to compare

@github-actions github-actions released this 27 Jul 07:34

v0.2.1 (2026-07-27)

This release is published under the MIT License.

Bug Fixes

  • Do every fallible step before the install, then exit (14c6c69)

syncer 4.0.0's own update command crashed immediately after a successful upgrade: it fetched its changelog with httpx once uv had already rewritten the virtual environment underneath it, and httpx's lazy import of httpcore resolved against a directory where 4.3.0 had just dropped that dependency. The upgrade was fine; the process reporting on it was not.

run_update had the same shape. It fetched the changelog after installing, and the comment claiming the process ends there only flushed stdout before returning into typer. github.py uses urllib, so the fetch survives today, but a custom Source is public API and nothing stops one importing httpx.

Everything that touches the network or imports now happens before the install, and exit_now ends the process without unwinding through click's error handling or interpreter shutdown, both of which may import.

update() is unchanged for callers; it is now the composition of require_updatable, check and install_release, which is what lets run_update slot the changelog fetch between the second and third.

Chores

  • Keep markdownlint off the generated changelog (dc47b15)

semantic-release writes CHANGELOG.md from its own template, blank lines and all, so --fix rewrites it on every --all-files run and the change comes straight back at the next release.


Detailed Changes: v0.2.0...v0.2.1

v0.2.0

Choose a tag to compare

@github-actions github-actions released this 27 Jul 04:57

v0.2.0 (2026-07-27)

This release is published under the MIT License.

Documentation

  • State accurately what the siblings share today (2cc54f5)

Both new libraries claimed all three share the state schema and the NO_AUTO_UPDATE contract. goselfupdate has neither: it implements the update half only and has no notify layer, so the claim was false for a third of the family it described.

Features

  • Resolve an expensive token lazily with token_func (4c15a87)

A private repository needs a real token, and the usual source is the gh CLI — a subprocess. Assigning that to Config.token means it runs wherever the Config is built, and the notify gate resolves one on every invocation to then decline in microseconds. relate is the case that surfaced it: a private repo whose CLI would have paid a process spawn on every command.

token_func is consulted where the token is actually used, inside the request, so it runs only when a request is made. It sits last in the precedence chain, leaving an explicit token and both environment variables unaffected. Mirrors goselfupdate's Config.TokenFunc.

Refactoring

  • Drop the unwritten skip field from the state schema (d7e432c)

Nothing ever wrote it: the only assignment set it to the empty string, because a gate that declines to check deliberately does not write the file at all. That absence is what makes "no state file" observable proof the network was never touched, so a skip-reason field has no writer by design rather than by oversight.

Surfaced while writing the bash sibling, where shellcheck flagged the captured-and-unused reason that would have populated it. A documented schema field with no writer is worse than no field, and this schema is shared across all three libraries.

No behaviour changes, so no release is cut.


Detailed Changes: v0.1.0...v0.2.0

v0.1.0

Choose a tag to compare

@github-actions github-actions released this 27 Jul 02:41

v0.1.0 (2026-07-27)

This release is published under the MIT License.

Features

Update notification and self-update for Python CLIs installed with uv tool, and the Python sibling of goselfupdate. Two layers used independently: notify prints one line a day and never raises, update installs a release and raises on failure.

Zero runtime dependencies, enforced two ways in CI: the declared list must be empty, and every core module must import in a virtual environment containing nothing else. That is why semver comparison is implemented here rather than taken from packaging, and why uv's receipt is read with tomllib. typer is an extra, confined to typercmd.

The API deliberately diverges from goselfupdate. Replacing a Go binary is safe under a running process, which holds an inode rather than a path; uv tool install --force rebuilds the venv the interpreter is living in, so update must be the last thing a process does and there is no background mode. What the two libraries do share is the state file schema, the environment variable contract, and version precedence.

Dev installs are detected from uv's own receipt rather than a version string. The installer knows how a tool got there and the running program does not, so this refuses local, editable and branch-tracking installs by reading what uv recorded at install time.