pyselfupdate 0.5.4__tar.gz → 0.5.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: pyselfupdate
3
- Version: 0.5.4
3
+ Version: 0.5.5
4
4
  Summary: Self-update and update notification for Python CLIs installed with uv tool
5
5
  Project-URL: Repository, https://github.com/datapointchris/pyselfupdate
6
6
  Project-URL: Issues, https://github.com/datapointchris/pyselfupdate/issues
@@ -143,8 +143,8 @@ fail in ways that are hard to read.
143
143
 
144
144
  So make `update` the last thing the process does, then call `exit_now`. Or call
145
145
  `update_and_reexec`, which replaces the process with the release it installed,
146
- running the same command line. Its caller never sees the `Result`, so it writes
147
- `lock_warning` to stderr itself before the re-exec.
146
+ running the same command line. `update_and_reexec` writes `lock_warning` to
147
+ stderr itself before the re-exec, because its caller never sees the `Result`.
148
148
 
149
149
  `check` reports what `update` would install, and installs nothing:
150
150
 
@@ -120,8 +120,8 @@ fail in ways that are hard to read.
120
120
 
121
121
  So make `update` the last thing the process does, then call `exit_now`. Or call
122
122
  `update_and_reexec`, which replaces the process with the release it installed,
123
- running the same command line. Its caller never sees the `Result`, so it writes
124
- `lock_warning` to stderr itself before the re-exec.
123
+ running the same command line. `update_and_reexec` writes `lock_warning` to
124
+ stderr itself before the re-exec, because its caller never sees the `Result`.
125
125
 
126
126
  `check` reports what `update` would install, and installs nothing:
127
127
 
@@ -42,7 +42,7 @@ dev = [
42
42
  "mypy>=1.10.0",
43
43
  "pre-commit>=4.3.0",
44
44
  "pytest>=8.0.0",
45
- "ruff==0.12.5",
45
+ "ruff==0.17.1",
46
46
  "typer>=0.12.0",
47
47
  ]
48
48
 
@@ -95,6 +95,7 @@ exclude = [
95
95
  "build",
96
96
  "dist",
97
97
  ]
98
+ extend-exclude = ["*.md"]
98
99
 
99
100
  [tool.ruff.format]
100
101
  quote-style = "single"
@@ -158,12 +159,28 @@ major_on_zero = false
158
159
  typeCheckingMode = "standard"
159
160
  analyzeUnannotatedFunctions = true # or pylance stops analyzing in vscode
160
161
 
162
+ [tool.hatch.version]
163
+ source = "uv-dynamic-versioning"
164
+
165
+ [tool.uv-dynamic-versioning]
166
+ style = "semver"
167
+
168
+ # A new commit or tag changes the version, so it has to invalidate uv's cache.
169
+ [tool.uv]
170
+ cache-keys = [{ file = "pyproject.toml" }, { git = { commit = true, tags = true } }]
171
+
172
+ # The published sdist holds the package and its metadata, and none of the
173
+ # repo's tooling or tests. hatchling's default takes the whole tree.
174
+ [tool.hatch.build.targets.sdist]
175
+ only-include = ["src"]
176
+
161
177
  [tool.forge]
162
178
  # Keys the shared toolchain standard owns, written by its generator.
163
179
  # Dropping one from the template removes it here on the next sync; a key absent
164
180
  # from this list belongs to the project and is never touched. Do not hand-edit.
165
181
  managed = [
166
182
  ["ruff", "line-length"],
183
+ ["ruff", "extend-exclude"],
167
184
  ["ruff", "format", "quote-style"],
168
185
  ["ruff", "format", "indent-style"],
169
186
  ["ruff", "format", "skip-magic-trailing-comma"],
@@ -197,24 +214,6 @@ managed = [
197
214
  # and extra that names them, so a local run and CI cannot disagree.
198
215
  pinned = ["ruff"]
199
216
 
200
- # The release tag is the version. python-semantic-release finds no file to
201
- # stamp, so it tags the merge commit itself and writes no release commit.
202
- # uv_build cannot do this: its metadata has no dynamic version field.
203
- [tool.hatch.version]
204
- source = "uv-dynamic-versioning"
205
-
206
- [tool.uv-dynamic-versioning]
207
- style = "semver"
208
-
209
- # A new commit or tag changes the version, so it has to invalidate uv's cache.
210
- [tool.uv]
211
- cache-keys = [{ file = "pyproject.toml" }, { git = { commit = true, tags = true } }]
212
-
213
- # The published sdist holds the package and its metadata, and none of the
214
- # repo's tooling or tests, as uv_build's did.
215
- [tool.hatch.build.targets.sdist]
216
- only-include = ["src"]
217
-
218
217
  [build-system]
219
218
  requires = ["hatchling", "uv-dynamic-versioning"]
220
219
  build-backend = "hatchling.build"
@@ -95,6 +95,7 @@ def tool_dir() -> Path:
95
95
  `UV_TOOL_DIR` first. Otherwise uv's data directory: `%APPDATA%\\uv` on
96
96
  Windows, where uv ignores `XDG_DATA_HOME`, and `$XDG_DATA_HOME/uv` or
97
97
  `~/.local/share/uv` everywhere else.
98
+
98
99
  Resolved from the environment rather than by running `uv tool dir`, which
99
100
  costs a subprocess on a path that runs before every command.
100
101
  """
@@ -442,11 +443,11 @@ def _run(command: list[str], cwd: Path | None = None) -> subprocess.CompletedPro
442
443
 
443
444
 
444
445
  def _reason(completed: subprocess.CompletedProcess[str]) -> str:
445
- """Everything a failing command printed, rather than one line of it.
446
+ """Everything a failing command printed.
446
447
 
447
- uv puts a lock's schema error on its first line, git puts a missing ref on
448
- its last, and uv wraps a resolution failure so its last line is half a
449
- sentence.
448
+ No single line carries the reason: uv puts a lock's schema error on its
449
+ first line, git puts a missing ref on its last, and uv wraps a resolution
450
+ failure so its last line is half a sentence.
450
451
  """
451
452
  return (completed.stderr or completed.stdout or '').strip() or f'exit status {completed.returncode}'
452
453
 
@@ -124,10 +124,6 @@ def _asking_for_help() -> bool:
124
124
  screen. Nothing a notice is for happens on a help screen, and the check
125
125
  costs a releases-API call and a state write every time.
126
126
 
127
- It lands hardest on a reader that walks a tool's help. Reading every
128
- command's help is one invocation per command plus one for the root, and
129
- each of them reaches this.
130
-
131
127
  A `--` ends the scan, because everything after it is the command's own
132
128
  argument. Skipping when it should not have costs a missed notice, which is
133
129
  the cheap direction to be wrong in.
@@ -32,10 +32,39 @@ from pyselfupdate.updater import install_release
32
32
  from pyselfupdate.updater import require_updatable
33
33
 
34
34
 
35
+ def update_help(tool: str, name: str, markup_mode: str | None) -> str:
36
+ """The update command's help: one line on what it does, then annotated examples.
37
+
38
+ Each example keeps a line of its own under each of typer's markup modes,
39
+ which lay out a help's later paragraphs differently. Markdown joins a
40
+ paragraph's lines, so the examples sit in a fenced block there. Rich markup
41
+ keeps the lines as written and would print a fence's backticks. Without
42
+ rich, click rewraps every paragraph that does not open with its `\\b`
43
+ marker.
44
+ """
45
+ command = f'{tool} {name}'
46
+ width = len(command) + len(' --no-changelog') + 2
47
+ examples = '\n'.join(
48
+ f'{call:<{width}}{purpose}'
49
+ for call, purpose in (
50
+ (command, 'install the newest and list its commits'),
51
+ (f'{command} --check', 'only report whether a newer one exists'),
52
+ (f'{command} --no-changelog', 'install it without listing its commits'),
53
+ )
54
+ )
55
+ if markup_mode == 'markdown':
56
+ block = f'```text\n{examples}\n```'
57
+ elif markup_mode == 'rich':
58
+ block = examples
59
+ else:
60
+ block = f'\b\n{examples}'
61
+ return f'Update {tool} to the latest release\n\nExamples:\n\n{block}'
62
+
63
+
35
64
  def add_update_command(app: typer.Typer, config: Config, *, name: str = 'update') -> None:
36
65
  """Register `<tool> update` on an existing typer app."""
37
66
 
38
- @app.command(name=name, help=f'Update {config.tool} to the latest release')
67
+ @app.command(name=name, help=update_help(config.tool, name, app.rich_markup_mode))
39
68
  def update_command(
40
69
  check_only: bool = typer.Option(False, '--check', help='Report whether an update is available without installing it'),
41
70
  skip_changelog: bool = typer.Option(False, '--no-changelog', help='Do not list the commits between versions'),
@@ -52,17 +81,15 @@ def run_update(config: Config, *, check_only: bool = False, skip_changelog: bool
52
81
  library report an update in the same words.
53
82
 
54
83
  `check_only` reads the receipt first, as an update does, and refuses the
55
- same installs. A branch build's version can rank below the release it is
56
- ahead of, so a check that skipped the receipt would offer that older
57
- release as an update.
58
-
59
- The step order is the load-bearing part. Everything that reaches the network
60
- or imports a module happens before the install; after it, this process only
61
- prints what it already holds and exits without unwinding. Both halves of
62
- that come from a real failure. A tool that fetched its changelog after
63
- replacing its own environment died on its HTTP client's lazy import of a
64
- transport module. The release it had just installed did not depend on that
65
- module, so the install had deleted it.
84
+ same installs. `updater.installed_from_release` says why a version alone
85
+ cannot decide.
86
+
87
+ Everything that reaches the network or imports a module happens before the
88
+ install; after it, this process only prints what it already holds and exits
89
+ without unwinding. An import after the install can name a module the new
90
+ release no longer ships, which the install has already deleted. An HTTP
91
+ client's lazy import of its transport is one, and it raises ImportError
92
+ after the update has succeeded.
66
93
  """
67
94
  tool = config.tool
68
95
  try:
@@ -97,9 +97,8 @@ def installed_from_release(installation: Installation, tag_prefix: str) -> bool:
97
97
 
98
98
  An index install is. A git install is when its revision is a release tag:
99
99
  `tag_prefix` followed by a semantic version, the form a release's
100
- `install_ref` takes. uv writes `?rev=` for whatever followed the `@`.
101
- `@main` and `@v1.2.3` land in the same field, so only the value tells a
102
- branch from a tag.
100
+ `install_ref` takes. `Installation.revision` holds a branch or a commit in
101
+ the same field, so only the value tells them apart.
103
102
 
104
103
  A build from a branch reports a version that ranks wrongly against a
105
104
  release. Versioned from git tags, a branch three commits past v1.2.3 builds
File without changes
File without changes