pyselfupdate 0.3.2__tar.gz → 0.4.1__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.
- {pyselfupdate-0.3.2 → pyselfupdate-0.4.1}/PKG-INFO +17 -10
- {pyselfupdate-0.3.2 → pyselfupdate-0.4.1}/README.md +15 -8
- {pyselfupdate-0.3.2 → pyselfupdate-0.4.1}/pyproject.toml +17 -2
- {pyselfupdate-0.3.2 → pyselfupdate-0.4.1}/pyproject.toml.orig +9 -3
- {pyselfupdate-0.3.2 → pyselfupdate-0.4.1}/src/pyselfupdate/errors.py +1 -1
- {pyselfupdate-0.3.2 → pyselfupdate-0.4.1}/src/pyselfupdate/github.py +47 -5
- {pyselfupdate-0.3.2 → pyselfupdate-0.4.1}/src/pyselfupdate/install.py +1 -1
- {pyselfupdate-0.3.2 → pyselfupdate-0.4.1}/src/pyselfupdate/state.py +45 -5
- {pyselfupdate-0.3.2 → pyselfupdate-0.4.1}/src/pyselfupdate/updater.py +2 -2
- {pyselfupdate-0.3.2 → pyselfupdate-0.4.1}/src/pyselfupdate/version.py +2 -2
- {pyselfupdate-0.3.2 → pyselfupdate-0.4.1}/src/pyselfupdate/__init__.py +0 -0
- {pyselfupdate-0.3.2 → pyselfupdate-0.4.1}/src/pyselfupdate/config.py +0 -0
- {pyselfupdate-0.3.2 → pyselfupdate-0.4.1}/src/pyselfupdate/notifier.py +0 -0
- {pyselfupdate-0.3.2 → pyselfupdate-0.4.1}/src/pyselfupdate/py.typed +0 -0
- {pyselfupdate-0.3.2 → pyselfupdate-0.4.1}/src/pyselfupdate/source.py +0 -0
- {pyselfupdate-0.3.2 → pyselfupdate-0.4.1}/src/pyselfupdate/typercmd.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: pyselfupdate
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.1
|
|
4
4
|
Summary: Self-update and update notification for Python CLIs installed with uv tool
|
|
5
5
|
Keywords: uv,cli,self-update,update-notifier,release
|
|
6
6
|
Author: Chris Birch
|
|
@@ -18,7 +18,7 @@ Requires-Dist: typer>=0.12.0 ; extra == 'typer'
|
|
|
18
18
|
Requires-Python: >=3.11
|
|
19
19
|
Project-URL: Repository, https://github.com/datapointchris/pyselfupdate
|
|
20
20
|
Project-URL: Issues, https://github.com/datapointchris/pyselfupdate/issues
|
|
21
|
-
Project-URL: Changelog, https://github.com/datapointchris/pyselfupdate/
|
|
21
|
+
Project-URL: Changelog, https://github.com/datapointchris/pyselfupdate/releases
|
|
22
22
|
Provides-Extra: typer
|
|
23
23
|
Description-Content-Type: text/markdown
|
|
24
24
|
|
|
@@ -218,7 +218,8 @@ command, and nothing above the `Source` protocol learns either name.
|
|
|
218
218
|
|
|
219
219
|
## State
|
|
220
220
|
|
|
221
|
-
`${XDG_STATE_HOME:-~/.local/state}/<tool>/autoupdate
|
|
221
|
+
`${XDG_STATE_HOME:-~/.local/state}/<tool>/autoupdate-<machine>.json`, written
|
|
222
|
+
atomically:
|
|
222
223
|
|
|
223
224
|
```json
|
|
224
225
|
{
|
|
@@ -233,10 +234,17 @@ command, and nothing above the `Source` protocol learns either name.
|
|
|
233
234
|
```
|
|
234
235
|
|
|
235
236
|
State, not config and not cache: it persists across runs, it is not authored by
|
|
236
|
-
the user, and deleting it changes
|
|
237
|
+
the user, and deleting it changes behavior rather than merely costing a
|
|
237
238
|
recompute. That is `XDG_STATE_HOME` by the Base Directory specification, and it
|
|
238
239
|
is where `gh` puts the same thing.
|
|
239
240
|
|
|
241
|
+
`<machine>` is the bare lowercased hostname, from `state.machine()`. It is in
|
|
242
|
+
the name because both fields that vary — the version installed here, and the
|
|
243
|
+
instant this box last checked — describe one machine. A state directory shared
|
|
244
|
+
between machines, by a file syncer or a network home directory, otherwise has
|
|
245
|
+
two writers on one path and reports whichever wrote last as the state of all of
|
|
246
|
+
them.
|
|
247
|
+
|
|
240
248
|
The timestamp is written **before** the network call. `gh` stamps only on
|
|
241
249
|
success, so a rate-limited or offline user re-hits the API on every invocation
|
|
242
250
|
until the window resets; an interval exists to bound the request rate, and only
|
|
@@ -244,11 +252,10 @@ this ordering actually does that.
|
|
|
244
252
|
|
|
245
253
|
## Siblings
|
|
246
254
|
|
|
247
|
-
The same two-layer design in other languages.
|
|
248
|
-
|
|
249
|
-
`NO_AUTO_UPDATE` contract
|
|
250
|
-
|
|
251
|
-
provides the update half only.
|
|
255
|
+
The same two-layer design in other languages. All three carry both layers, and
|
|
256
|
+
they share the `autoupdate-<machine>.json` schema, the machine derivation that
|
|
257
|
+
names it, and the `NO_AUTO_UPDATE` contract. They do not share an API, because
|
|
258
|
+
"update" means a different operation in each.
|
|
252
259
|
|
|
253
260
|
- [goselfupdate](https://github.com/datapointchris/goselfupdate) — replaces a Go binary
|
|
254
261
|
- [bashselfupdate](https://github.com/datapointchris/bashselfupdate) — moves a git checkout to its newest tag
|
|
@@ -256,6 +263,6 @@ provides the update half only.
|
|
|
256
263
|
Version precedence is deliberately identical across all three, so a tool and
|
|
257
264
|
its siblings never disagree about which release is newer.
|
|
258
265
|
|
|
259
|
-
##
|
|
266
|
+
## License
|
|
260
267
|
|
|
261
268
|
MIT
|
|
@@ -194,7 +194,8 @@ command, and nothing above the `Source` protocol learns either name.
|
|
|
194
194
|
|
|
195
195
|
## State
|
|
196
196
|
|
|
197
|
-
`${XDG_STATE_HOME:-~/.local/state}/<tool>/autoupdate
|
|
197
|
+
`${XDG_STATE_HOME:-~/.local/state}/<tool>/autoupdate-<machine>.json`, written
|
|
198
|
+
atomically:
|
|
198
199
|
|
|
199
200
|
```json
|
|
200
201
|
{
|
|
@@ -209,10 +210,17 @@ command, and nothing above the `Source` protocol learns either name.
|
|
|
209
210
|
```
|
|
210
211
|
|
|
211
212
|
State, not config and not cache: it persists across runs, it is not authored by
|
|
212
|
-
the user, and deleting it changes
|
|
213
|
+
the user, and deleting it changes behavior rather than merely costing a
|
|
213
214
|
recompute. That is `XDG_STATE_HOME` by the Base Directory specification, and it
|
|
214
215
|
is where `gh` puts the same thing.
|
|
215
216
|
|
|
217
|
+
`<machine>` is the bare lowercased hostname, from `state.machine()`. It is in
|
|
218
|
+
the name because both fields that vary — the version installed here, and the
|
|
219
|
+
instant this box last checked — describe one machine. A state directory shared
|
|
220
|
+
between machines, by a file syncer or a network home directory, otherwise has
|
|
221
|
+
two writers on one path and reports whichever wrote last as the state of all of
|
|
222
|
+
them.
|
|
223
|
+
|
|
216
224
|
The timestamp is written **before** the network call. `gh` stamps only on
|
|
217
225
|
success, so a rate-limited or offline user re-hits the API on every invocation
|
|
218
226
|
until the window resets; an interval exists to bound the request rate, and only
|
|
@@ -220,11 +228,10 @@ this ordering actually does that.
|
|
|
220
228
|
|
|
221
229
|
## Siblings
|
|
222
230
|
|
|
223
|
-
The same two-layer design in other languages.
|
|
224
|
-
|
|
225
|
-
`NO_AUTO_UPDATE` contract
|
|
226
|
-
|
|
227
|
-
provides the update half only.
|
|
231
|
+
The same two-layer design in other languages. All three carry both layers, and
|
|
232
|
+
they share the `autoupdate-<machine>.json` schema, the machine derivation that
|
|
233
|
+
names it, and the `NO_AUTO_UPDATE` contract. They do not share an API, because
|
|
234
|
+
"update" means a different operation in each.
|
|
228
235
|
|
|
229
236
|
- [goselfupdate](https://github.com/datapointchris/goselfupdate) — replaces a Go binary
|
|
230
237
|
- [bashselfupdate](https://github.com/datapointchris/bashselfupdate) — moves a git checkout to its newest tag
|
|
@@ -232,6 +239,6 @@ provides the update half only.
|
|
|
232
239
|
Version precedence is deliberately identical across all three, so a tool and
|
|
233
240
|
its siblings never disagree about which release is newer.
|
|
234
241
|
|
|
235
|
-
##
|
|
242
|
+
## License
|
|
236
243
|
|
|
237
244
|
MIT
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "pyselfupdate"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.4.1"
|
|
4
4
|
description = "Self-update and update notification for Python CLIs installed with uv tool"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
keywords = [
|
|
@@ -36,7 +36,7 @@ typer = ["typer>=0.12.0"]
|
|
|
36
36
|
[project.urls]
|
|
37
37
|
Repository = "https://github.com/datapointchris/pyselfupdate"
|
|
38
38
|
Issues = "https://github.com/datapointchris/pyselfupdate/issues"
|
|
39
|
-
Changelog = "https://github.com/datapointchris/pyselfupdate/
|
|
39
|
+
Changelog = "https://github.com/datapointchris/pyselfupdate/releases"
|
|
40
40
|
|
|
41
41
|
[dependency-groups]
|
|
42
42
|
dev = [
|
|
@@ -66,6 +66,9 @@ skips = [
|
|
|
66
66
|
[tool.codespell]
|
|
67
67
|
skip = "*.lock"
|
|
68
68
|
check-filenames = true
|
|
69
|
+
check-hidden = true
|
|
70
|
+
builtin = "clear,rare"
|
|
71
|
+
uri-ignore-words-list = "*"
|
|
69
72
|
|
|
70
73
|
[tool.mypy]
|
|
71
74
|
pretty = true
|
|
@@ -212,6 +215,18 @@ managed = [
|
|
|
212
215
|
[
|
|
213
216
|
"codespell",
|
|
214
217
|
"check-filenames",
|
|
218
|
+
],
|
|
219
|
+
[
|
|
220
|
+
"codespell",
|
|
221
|
+
"check-hidden",
|
|
222
|
+
],
|
|
223
|
+
[
|
|
224
|
+
"codespell",
|
|
225
|
+
"builtin",
|
|
226
|
+
],
|
|
227
|
+
[
|
|
228
|
+
"codespell",
|
|
229
|
+
"uri-ignore-words-list",
|
|
215
230
|
],
|
|
216
231
|
[
|
|
217
232
|
"pytest",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "pyselfupdate"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.4.1"
|
|
4
4
|
description = "Self-update and update notification for Python CLIs installed with uv tool"
|
|
5
5
|
authors = [{ name = "Chris Birch", email = "datapointchris@gmail.com" }]
|
|
6
6
|
license = { text = "MIT" }
|
|
@@ -34,7 +34,7 @@ typer = ["typer>=0.12.0"]
|
|
|
34
34
|
[project.urls]
|
|
35
35
|
Repository = "https://github.com/datapointchris/pyselfupdate"
|
|
36
36
|
Issues = "https://github.com/datapointchris/pyselfupdate/issues"
|
|
37
|
-
Changelog = "https://github.com/datapointchris/pyselfupdate/
|
|
37
|
+
Changelog = "https://github.com/datapointchris/pyselfupdate/releases"
|
|
38
38
|
|
|
39
39
|
[dependency-groups]
|
|
40
40
|
dev = [
|
|
@@ -58,6 +58,9 @@ skips = ["B311", "B404", "B603"]
|
|
|
58
58
|
[tool.codespell]
|
|
59
59
|
skip = '*.lock'
|
|
60
60
|
check-filenames = true
|
|
61
|
+
check-hidden = true
|
|
62
|
+
builtin = "clear,rare"
|
|
63
|
+
uri-ignore-words-list = "*"
|
|
61
64
|
|
|
62
65
|
|
|
63
66
|
[tool.mypy]
|
|
@@ -138,7 +141,7 @@ typeCheckingMode = "standard"
|
|
|
138
141
|
analyzeUnannotatedFunctions = true # or pylance stops analyzing in vscode
|
|
139
142
|
|
|
140
143
|
[tool.forge]
|
|
141
|
-
# Keys the standard owns, written by
|
|
144
|
+
# Keys the shared toolchain standard owns, written by its generator.
|
|
142
145
|
# Dropping one from the template removes it here on the next sync; a key absent
|
|
143
146
|
# from this list belongs to the project and is never touched. Do not hand-edit.
|
|
144
147
|
managed = [
|
|
@@ -159,6 +162,9 @@ managed = [
|
|
|
159
162
|
["pyright", "typeCheckingMode"],
|
|
160
163
|
["pyright", "analyzeUnannotatedFunctions"],
|
|
161
164
|
["codespell", "check-filenames"],
|
|
165
|
+
["codespell", "check-hidden"],
|
|
166
|
+
["codespell", "builtin"],
|
|
167
|
+
["codespell", "uri-ignore-words-list"],
|
|
162
168
|
["pytest", "ini_options", "verbosity_assertions"],
|
|
163
169
|
["pytest", "ini_options", "minversion"],
|
|
164
170
|
["pytest", "ini_options", "testpaths"],
|
|
@@ -18,7 +18,7 @@ class LocalInstallError(SelfUpdateError):
|
|
|
18
18
|
"""The tool was installed from a local path or as an editable checkout.
|
|
19
19
|
|
|
20
20
|
Reinstalling would discard a working copy for a release that may be older,
|
|
21
|
-
with no way to tell which is newer. This is the
|
|
21
|
+
with no way to tell which is newer. This is the analog of goselfupdate's
|
|
22
22
|
ErrDevBuild.
|
|
23
23
|
"""
|
|
24
24
|
|
|
@@ -6,6 +6,7 @@ this package is that adding it to a project adds nothing else.
|
|
|
6
6
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
|
+
import http.client
|
|
9
10
|
import json
|
|
10
11
|
import os
|
|
11
12
|
import shlex
|
|
@@ -19,6 +20,7 @@ from dataclasses import dataclass
|
|
|
19
20
|
from dataclasses import field
|
|
20
21
|
from datetime import UTC
|
|
21
22
|
from datetime import datetime
|
|
23
|
+
from typing import IO
|
|
22
24
|
|
|
23
25
|
from pyselfupdate.errors import NoReleaseError
|
|
24
26
|
from pyselfupdate.errors import SelfUpdateError
|
|
@@ -29,6 +31,49 @@ API = 'https://api.github.com'
|
|
|
29
31
|
DEFAULT_TIMEOUT = 10.0
|
|
30
32
|
|
|
31
33
|
|
|
34
|
+
def _origin(url: str) -> tuple[str, str]:
|
|
35
|
+
"""Scheme and authority, which is what decides whether a credential travels.
|
|
36
|
+
|
|
37
|
+
Port is part of it. Two ports on one host are two origins, and treating them
|
|
38
|
+
as one is how a redirect to a service sharing a hostname gets the token.
|
|
39
|
+
"""
|
|
40
|
+
split = urllib.parse.urlsplit(url)
|
|
41
|
+
return split.scheme, split.netloc
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class _CredentialScopedRedirects(urllib.request.HTTPRedirectHandler):
|
|
45
|
+
"""Drop `Authorization` when a redirect leaves the origin it was sent to.
|
|
46
|
+
|
|
47
|
+
`urllib.request` carries the header to the new host instead. Measured across
|
|
48
|
+
3.11, 3.13 and 3.14 with two local servers: a `Bearer` sent to the first
|
|
49
|
+
arrives intact at the second on every one of them. So this is the library's
|
|
50
|
+
to do, at every version it supports, rather than something a floor bump ends.
|
|
51
|
+
|
|
52
|
+
It matters here because `API` is a reassignable module attribute and
|
|
53
|
+
`headers` is caller-supplied, which is what makes a redirect off GitHub
|
|
54
|
+
something other than hypothetical.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
def redirect_request(
|
|
58
|
+
self,
|
|
59
|
+
req: urllib.request.Request,
|
|
60
|
+
fp: IO[bytes],
|
|
61
|
+
code: int,
|
|
62
|
+
msg: str,
|
|
63
|
+
headers: http.client.HTTPMessage,
|
|
64
|
+
newurl: str,
|
|
65
|
+
) -> urllib.request.Request | None:
|
|
66
|
+
redirected = super().redirect_request(req, fp, code, msg, headers, newurl)
|
|
67
|
+
if redirected is None or _origin(req.full_url) == _origin(newurl):
|
|
68
|
+
return redirected
|
|
69
|
+
redirected.headers = {name: value for name, value in redirected.headers.items() if name.lower() != 'authorization'}
|
|
70
|
+
return redirected
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
_OPENER = urllib.request.build_opener(_CredentialScopedRedirects)
|
|
74
|
+
"""Built once. Passing a `HTTPRedirectHandler` subclass replaces the default one."""
|
|
75
|
+
|
|
76
|
+
|
|
32
77
|
# B105 reads any name carrying "token" as a credential. Both hold the name of a
|
|
33
78
|
# variable and the text of a command; neither ever holds a secret.
|
|
34
79
|
TOKEN_COMMAND_ENV = 'GITHUB_TOKEN_COMMAND' # nosec B105
|
|
@@ -215,7 +260,7 @@ class GitHubSource:
|
|
|
215
260
|
def _get(self, path: str):
|
|
216
261
|
url = f'{API}{path}'
|
|
217
262
|
|
|
218
|
-
# urlopen
|
|
263
|
+
# urlopen honors file:, ftp: and data: as readily as http:. API is a
|
|
219
264
|
# constant here, but it is a module attribute a caller can reassign --
|
|
220
265
|
# tests do exactly that -- so the scheme is checked rather than assumed.
|
|
221
266
|
# Without this, setting it to a file: URL turns a release check into an
|
|
@@ -235,10 +280,7 @@ class GitHubSource:
|
|
|
235
280
|
request.add_header(name, value)
|
|
236
281
|
|
|
237
282
|
try:
|
|
238
|
-
|
|
239
|
-
# on urlopen regardless of the scheme guard above. That guard, and
|
|
240
|
-
# test_a_non_http_scheme_is_refused, are the actual defence.
|
|
241
|
-
with urllib.request.urlopen(request, timeout=self.timeout) as response: # noqa: S310 # nosec B310
|
|
283
|
+
with _OPENER.open(request, timeout=self.timeout) as response:
|
|
242
284
|
return json.load(response)
|
|
243
285
|
except urllib.error.HTTPError as error:
|
|
244
286
|
raise _http_error(self.owner, self.repo, error, authenticated=bool(token)) from error
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Reading and rewriting a uv tool installation.
|
|
2
2
|
|
|
3
|
-
This is the part with no
|
|
3
|
+
This is the part with no analog in goselfupdate. A Go tool updates by
|
|
4
4
|
replacing one file; a uv tool updates by rebuilding the virtual environment its
|
|
5
5
|
own interpreter is running inside, which is why `update` must be the last thing
|
|
6
6
|
a process does before it exits or re-execs.
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
"""The per-tool state file.
|
|
1
|
+
"""The per-tool, per-machine state file.
|
|
2
2
|
|
|
3
3
|
One schema, shared byte-for-byte with goselfupdate and bashselfupdate, so that
|
|
4
4
|
any tool can read any other tool's state and a single dashboard can glob
|
|
5
|
-
`~/.local/state/*/autoupdate
|
|
5
|
+
`~/.local/state/*/autoupdate-*.json` with no per-tool knowledge.
|
|
6
6
|
|
|
7
7
|
State, not config and not cache: it persists across runs, it is not authored by
|
|
8
|
-
the user, and deleting it changes
|
|
8
|
+
the user, and deleting it changes behavior rather than merely costing a
|
|
9
9
|
recompute. That is `XDG_STATE_HOME` by the Base Directory specification, and it
|
|
10
10
|
is where `gh` puts the same thing.
|
|
11
11
|
"""
|
|
@@ -14,6 +14,7 @@ from __future__ import annotations
|
|
|
14
14
|
|
|
15
15
|
import json
|
|
16
16
|
import os
|
|
17
|
+
import socket
|
|
17
18
|
from dataclasses import asdict
|
|
18
19
|
from dataclasses import dataclass
|
|
19
20
|
from datetime import UTC
|
|
@@ -21,7 +22,45 @@ from datetime import datetime
|
|
|
21
22
|
from pathlib import Path
|
|
22
23
|
|
|
23
24
|
SCHEMA = 1
|
|
24
|
-
|
|
25
|
+
|
|
26
|
+
#: Names the file when the host cannot be read, so a filename is always
|
|
27
|
+
#: well-formed and two unidentifiable boxes collide only with each other.
|
|
28
|
+
UNKNOWN_MACHINE = 'unknown'
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def machine() -> str:
|
|
32
|
+
"""The box this process runs on: bare hostname, no domain, lowercased.
|
|
33
|
+
|
|
34
|
+
goselfupdate and bashselfupdate derive it the same way, so the three write
|
|
35
|
+
interleaved files a single reader can enumerate.
|
|
36
|
+
"""
|
|
37
|
+
try:
|
|
38
|
+
return canonical_machine(socket.gethostname())
|
|
39
|
+
except OSError:
|
|
40
|
+
return UNKNOWN_MACHINE
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def canonical_machine(name: str) -> str:
|
|
44
|
+
"""A hostname from any source, reduced to the form `machine` records.
|
|
45
|
+
|
|
46
|
+
A hostname reaches a reader fully qualified as often as bare, so a
|
|
47
|
+
comparison against a recorded name has to canonicalize both sides or it
|
|
48
|
+
fails on every host.
|
|
49
|
+
"""
|
|
50
|
+
return name.strip().lower().split('.')[0] or UNKNOWN_MACHINE
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def filename(machine_name: str) -> str:
|
|
54
|
+
"""The file one machine writes inside a tool's state directory.
|
|
55
|
+
|
|
56
|
+
The machine is part of the name because a state directory is a synced
|
|
57
|
+
directory on some installations, and a file syncer has no merge: two boxes
|
|
58
|
+
writing one path leaves one winner plus a conflict copy nobody reads. Both
|
|
59
|
+
fields this file carries — the version installed here and the instant this
|
|
60
|
+
box last checked — describe one machine, so the split costs nothing and
|
|
61
|
+
makes the collision unreachable.
|
|
62
|
+
"""
|
|
63
|
+
return f'autoupdate-{machine_name}.json'
|
|
25
64
|
|
|
26
65
|
|
|
27
66
|
@dataclass
|
|
@@ -54,7 +93,8 @@ def state_home() -> Path:
|
|
|
54
93
|
|
|
55
94
|
|
|
56
95
|
def state_path(tool: str) -> Path:
|
|
57
|
-
|
|
96
|
+
"""Where this machine's state for a tool lives."""
|
|
97
|
+
return state_home() / tool / filename(machine())
|
|
58
98
|
|
|
59
99
|
|
|
60
100
|
def read(tool: str) -> State:
|
|
@@ -23,7 +23,7 @@ from pyselfupdate.source import Release
|
|
|
23
23
|
class Result:
|
|
24
24
|
"""What an update found, whether or not it installed anything."""
|
|
25
25
|
|
|
26
|
-
# The running version,
|
|
26
|
+
# The running version, canonicalized with a leading "v".
|
|
27
27
|
current: str
|
|
28
28
|
|
|
29
29
|
# The version now installed, or the one `update` would install after a
|
|
@@ -129,7 +129,7 @@ def changelog(config: Config, from_version: str, to_version: str) -> list[str]:
|
|
|
129
129
|
|
|
130
130
|
|
|
131
131
|
def _current_version(config: Config) -> str:
|
|
132
|
-
"""The running version,
|
|
132
|
+
"""The running version, canonicalized, rejecting one that is not a release.
|
|
133
133
|
|
|
134
134
|
An install that tracks a git branch rather than a tag reports whatever
|
|
135
135
|
version was in pyproject.toml when it was built, which says nothing about
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
Implemented here rather than taken from `packaging` so that this package has no
|
|
4
4
|
third-party dependencies. `packaging` also implements PEP 440, whose precedence
|
|
5
|
-
rules differ from semver's in ways that matter: it
|
|
5
|
+
rules differ from semver's in ways that matter: it normalizes `1.0.0-rc.1` to
|
|
6
6
|
`1.0.0rc1` and orders `1.0.0.post1` above `1.0.0`, neither of which describes a
|
|
7
7
|
git tag.
|
|
8
8
|
|
|
9
9
|
Precedence follows https://semver.org section 11, and is byte-for-byte the same
|
|
10
|
-
|
|
10
|
+
behavior as goselfupdate's semver.go so that the two libraries never disagree
|
|
11
11
|
about which of two releases is newer.
|
|
12
12
|
"""
|
|
13
13
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|