pyselfupdate 0.4.0__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.4.0 → pyselfupdate-0.4.1}/PKG-INFO +3 -3
- {pyselfupdate-0.4.0 → pyselfupdate-0.4.1}/README.md +2 -2
- {pyselfupdate-0.4.0 → pyselfupdate-0.4.1}/pyproject.toml +16 -1
- {pyselfupdate-0.4.0 → pyselfupdate-0.4.1}/pyproject.toml.orig +8 -2
- {pyselfupdate-0.4.0 → pyselfupdate-0.4.1}/src/pyselfupdate/errors.py +1 -1
- {pyselfupdate-0.4.0 → pyselfupdate-0.4.1}/src/pyselfupdate/github.py +47 -5
- {pyselfupdate-0.4.0 → pyselfupdate-0.4.1}/src/pyselfupdate/install.py +1 -1
- {pyselfupdate-0.4.0 → pyselfupdate-0.4.1}/src/pyselfupdate/state.py +1 -1
- {pyselfupdate-0.4.0 → pyselfupdate-0.4.1}/src/pyselfupdate/updater.py +2 -2
- {pyselfupdate-0.4.0 → pyselfupdate-0.4.1}/src/pyselfupdate/version.py +2 -2
- {pyselfupdate-0.4.0 → pyselfupdate-0.4.1}/src/pyselfupdate/__init__.py +0 -0
- {pyselfupdate-0.4.0 → pyselfupdate-0.4.1}/src/pyselfupdate/config.py +0 -0
- {pyselfupdate-0.4.0 → pyselfupdate-0.4.1}/src/pyselfupdate/notifier.py +0 -0
- {pyselfupdate-0.4.0 → pyselfupdate-0.4.1}/src/pyselfupdate/py.typed +0 -0
- {pyselfupdate-0.4.0 → pyselfupdate-0.4.1}/src/pyselfupdate/source.py +0 -0
- {pyselfupdate-0.4.0 → 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.4.
|
|
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
|
|
@@ -234,7 +234,7 @@ atomically:
|
|
|
234
234
|
```
|
|
235
235
|
|
|
236
236
|
State, not config and not cache: it persists across runs, it is not authored by
|
|
237
|
-
the user, and deleting it changes
|
|
237
|
+
the user, and deleting it changes behavior rather than merely costing a
|
|
238
238
|
recompute. That is `XDG_STATE_HOME` by the Base Directory specification, and it
|
|
239
239
|
is where `gh` puts the same thing.
|
|
240
240
|
|
|
@@ -263,6 +263,6 @@ names it, and the `NO_AUTO_UPDATE` contract. They do not share an API, because
|
|
|
263
263
|
Version precedence is deliberately identical across all three, so a tool and
|
|
264
264
|
its siblings never disagree about which release is newer.
|
|
265
265
|
|
|
266
|
-
##
|
|
266
|
+
## License
|
|
267
267
|
|
|
268
268
|
MIT
|
|
@@ -210,7 +210,7 @@ atomically:
|
|
|
210
210
|
```
|
|
211
211
|
|
|
212
212
|
State, not config and not cache: it persists across runs, it is not authored by
|
|
213
|
-
the user, and deleting it changes
|
|
213
|
+
the user, and deleting it changes behavior rather than merely costing a
|
|
214
214
|
recompute. That is `XDG_STATE_HOME` by the Base Directory specification, and it
|
|
215
215
|
is where `gh` puts the same thing.
|
|
216
216
|
|
|
@@ -239,6 +239,6 @@ names it, and the `NO_AUTO_UPDATE` contract. They do not share an API, because
|
|
|
239
239
|
Version precedence is deliberately identical across all three, so a tool and
|
|
240
240
|
its siblings never disagree about which release is newer.
|
|
241
241
|
|
|
242
|
-
##
|
|
242
|
+
## License
|
|
243
243
|
|
|
244
244
|
MIT
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "pyselfupdate"
|
|
3
|
-
version = "0.4.
|
|
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 = [
|
|
@@ -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.4.
|
|
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" }
|
|
@@ -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.
|
|
@@ -5,7 +5,7 @@ any tool can read any other tool's state and a single dashboard can glob
|
|
|
5
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
|
"""
|
|
@@ -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
|