pyselfupdate 0.3.2__tar.gz → 0.4.0__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.0}/PKG-INFO +15 -8
- {pyselfupdate-0.3.2 → pyselfupdate-0.4.0}/README.md +13 -6
- {pyselfupdate-0.3.2 → pyselfupdate-0.4.0}/pyproject.toml +2 -2
- {pyselfupdate-0.3.2 → pyselfupdate-0.4.0}/pyproject.toml.orig +2 -2
- {pyselfupdate-0.3.2 → pyselfupdate-0.4.0}/src/pyselfupdate/state.py +44 -4
- {pyselfupdate-0.3.2 → pyselfupdate-0.4.0}/src/pyselfupdate/__init__.py +0 -0
- {pyselfupdate-0.3.2 → pyselfupdate-0.4.0}/src/pyselfupdate/config.py +0 -0
- {pyselfupdate-0.3.2 → pyselfupdate-0.4.0}/src/pyselfupdate/errors.py +0 -0
- {pyselfupdate-0.3.2 → pyselfupdate-0.4.0}/src/pyselfupdate/github.py +0 -0
- {pyselfupdate-0.3.2 → pyselfupdate-0.4.0}/src/pyselfupdate/install.py +0 -0
- {pyselfupdate-0.3.2 → pyselfupdate-0.4.0}/src/pyselfupdate/notifier.py +0 -0
- {pyselfupdate-0.3.2 → pyselfupdate-0.4.0}/src/pyselfupdate/py.typed +0 -0
- {pyselfupdate-0.3.2 → pyselfupdate-0.4.0}/src/pyselfupdate/source.py +0 -0
- {pyselfupdate-0.3.2 → pyselfupdate-0.4.0}/src/pyselfupdate/typercmd.py +0 -0
- {pyselfupdate-0.3.2 → pyselfupdate-0.4.0}/src/pyselfupdate/updater.py +0 -0
- {pyselfupdate-0.3.2 → pyselfupdate-0.4.0}/src/pyselfupdate/version.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: pyselfupdate
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
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
|
{
|
|
@@ -237,6 +238,13 @@ the user, and deleting it changes behaviour 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
|
|
@@ -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
|
{
|
|
@@ -213,6 +214,13 @@ the user, and deleting it changes behaviour 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
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "pyselfupdate"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.4.0"
|
|
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 = [
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "pyselfupdate"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.4.0"
|
|
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 = [
|
|
@@ -1,8 +1,8 @@
|
|
|
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
8
|
the user, and deleting it changes behaviour rather than merely costing a
|
|
@@ -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:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|