pyselfupdate 0.5.3__tar.gz → 0.5.4__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.5.3 → pyselfupdate-0.5.4}/PKG-INFO +16 -2
- {pyselfupdate-0.5.3 → pyselfupdate-0.5.4}/README.md +15 -1
- {pyselfupdate-0.5.3 → pyselfupdate-0.5.4}/src/pyselfupdate/install.py +60 -6
- {pyselfupdate-0.5.3 → pyselfupdate-0.5.4}/src/pyselfupdate/updater.py +7 -1
- {pyselfupdate-0.5.3 → pyselfupdate-0.5.4}/.gitignore +0 -0
- {pyselfupdate-0.5.3 → pyselfupdate-0.5.4}/LICENSE +0 -0
- {pyselfupdate-0.5.3 → pyselfupdate-0.5.4}/pyproject.toml +0 -0
- {pyselfupdate-0.5.3 → pyselfupdate-0.5.4}/src/pyselfupdate/__init__.py +0 -0
- {pyselfupdate-0.5.3 → pyselfupdate-0.5.4}/src/pyselfupdate/config.py +0 -0
- {pyselfupdate-0.5.3 → pyselfupdate-0.5.4}/src/pyselfupdate/errors.py +0 -0
- {pyselfupdate-0.5.3 → pyselfupdate-0.5.4}/src/pyselfupdate/github.py +0 -0
- {pyselfupdate-0.5.3 → pyselfupdate-0.5.4}/src/pyselfupdate/notifier.py +0 -0
- {pyselfupdate-0.5.3 → pyselfupdate-0.5.4}/src/pyselfupdate/py.typed +0 -0
- {pyselfupdate-0.5.3 → pyselfupdate-0.5.4}/src/pyselfupdate/source.py +0 -0
- {pyselfupdate-0.5.3 → pyselfupdate-0.5.4}/src/pyselfupdate/state.py +0 -0
- {pyselfupdate-0.5.3 → pyselfupdate-0.5.4}/src/pyselfupdate/typercmd.py +0 -0
- {pyselfupdate-0.5.3 → pyselfupdate-0.5.4}/src/pyselfupdate/version.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: pyselfupdate
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.4
|
|
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
|
|
@@ -221,12 +221,26 @@ later `uv tool upgrade` stays held to them.
|
|
|
221
221
|
| Carries none | Installs unlocked and sets `lock_missing`. `lock_warning` names the tag, and `run_update` and `update_and_reexec` print it |
|
|
222
222
|
| Will not clone, uv will not export its lock, or git or uv is not on `PATH` | Raises `LockUnreadableError` and installs nothing |
|
|
223
223
|
|
|
224
|
-
An install from an index reads no lock, because a wheel carries none.
|
|
224
|
+
An install from an index reads no lock, because a wheel carries none. It
|
|
225
|
+
takes the release as a floor, `mytool>=2.0.0`, rather than a pin. uv records
|
|
226
|
+
the specifier in the receipt, and `uv tool upgrade` refuses to move an exact
|
|
227
|
+
pin.
|
|
225
228
|
|
|
226
229
|
An update reinstalls the tool's own extras, as in `mytool[fast]`, with their
|
|
227
230
|
dependencies held to the lock. Every `--with` and `--with-editable`
|
|
228
231
|
requirement is passed back to uv as well.
|
|
229
232
|
|
|
233
|
+
`uv tool install --force` reads nothing back from the receipt it replaces. So
|
|
234
|
+
an update passes back the receipt's `--python` and the flags naming where its
|
|
235
|
+
packages come from: `--index`, `--default-index`, `--index-url`,
|
|
236
|
+
`--extra-index-url`, `--find-links`, `--no-index`, `--index-strategy` and
|
|
237
|
+
`--keyring-provider`. Without them, a tool installed with
|
|
238
|
+
`--index https://pypi.example.com/simple/` resolves its update against PyPI
|
|
239
|
+
alone and is not found. An `explicit` index is left out, because only a config
|
|
240
|
+
file can declare one and the reinstall reads that file again. Dropping any
|
|
241
|
+
other recorded option changes how the tool resolves or builds, never whether it
|
|
242
|
+
is found.
|
|
243
|
+
|
|
230
244
|
## What may be updated is read from uv's receipt, not guessed from the version
|
|
231
245
|
|
|
232
246
|
uv writes `uv-receipt.toml` into the tool's directory at install time. It
|
|
@@ -198,12 +198,26 @@ later `uv tool upgrade` stays held to them.
|
|
|
198
198
|
| Carries none | Installs unlocked and sets `lock_missing`. `lock_warning` names the tag, and `run_update` and `update_and_reexec` print it |
|
|
199
199
|
| Will not clone, uv will not export its lock, or git or uv is not on `PATH` | Raises `LockUnreadableError` and installs nothing |
|
|
200
200
|
|
|
201
|
-
An install from an index reads no lock, because a wheel carries none.
|
|
201
|
+
An install from an index reads no lock, because a wheel carries none. It
|
|
202
|
+
takes the release as a floor, `mytool>=2.0.0`, rather than a pin. uv records
|
|
203
|
+
the specifier in the receipt, and `uv tool upgrade` refuses to move an exact
|
|
204
|
+
pin.
|
|
202
205
|
|
|
203
206
|
An update reinstalls the tool's own extras, as in `mytool[fast]`, with their
|
|
204
207
|
dependencies held to the lock. Every `--with` and `--with-editable`
|
|
205
208
|
requirement is passed back to uv as well.
|
|
206
209
|
|
|
210
|
+
`uv tool install --force` reads nothing back from the receipt it replaces. So
|
|
211
|
+
an update passes back the receipt's `--python` and the flags naming where its
|
|
212
|
+
packages come from: `--index`, `--default-index`, `--index-url`,
|
|
213
|
+
`--extra-index-url`, `--find-links`, `--no-index`, `--index-strategy` and
|
|
214
|
+
`--keyring-provider`. Without them, a tool installed with
|
|
215
|
+
`--index https://pypi.example.com/simple/` resolves its update against PyPI
|
|
216
|
+
alone and is not found. An `explicit` index is left out, because only a config
|
|
217
|
+
file can declare one and the reinstall reads that file again. Dropping any
|
|
218
|
+
other recorded option changes how the tool resolves or builds, never whether it
|
|
219
|
+
is found.
|
|
220
|
+
|
|
207
221
|
## What may be updated is read from uv's receipt, not guessed from the version
|
|
208
222
|
|
|
209
223
|
uv writes `uv-receipt.toml` into the tool's directory at install time. It
|
|
@@ -77,6 +77,11 @@ class Installation:
|
|
|
77
77
|
# editable back.
|
|
78
78
|
with_requirements: tuple[str, ...] = ()
|
|
79
79
|
|
|
80
|
+
# uv flags reproducing the receipt's Python and package sources, from
|
|
81
|
+
# `_source_flags`. `uv tool install --force` reads neither back from the
|
|
82
|
+
# receipt it replaces, so an update passes them itself.
|
|
83
|
+
source_flags: tuple[str, ...] = ()
|
|
84
|
+
|
|
80
85
|
# Names of the receipt requirements `_requirement_line` returns None for.
|
|
81
86
|
# `read_installation` records them rather than raising: the notify gate
|
|
82
87
|
# treats a failed read as a local install and prints no notice. `update`
|
|
@@ -122,7 +127,8 @@ def read_installation(tool: str) -> Installation:
|
|
|
122
127
|
except (OSError, tomllib.TOMLDecodeError) as error:
|
|
123
128
|
raise NotInstalledError(f'cannot read {receipt}: {error}') from error
|
|
124
129
|
|
|
125
|
-
|
|
130
|
+
recorded = payload.get('tool') or {}
|
|
131
|
+
requirements = recorded.get('requirements') or []
|
|
126
132
|
own = next((requirement for requirement in requirements if requirement.get('name') == tool), None)
|
|
127
133
|
if own is None:
|
|
128
134
|
raise NotInstalledError(f'{receipt} lists no requirement named {tool}')
|
|
@@ -140,7 +146,7 @@ def read_installation(tool: str) -> Installation:
|
|
|
140
146
|
|
|
141
147
|
def installed_as(kind: InstallKind, url: str = '', revision: str = '') -> Installation:
|
|
142
148
|
extras = tuple(own.get('extras') or ())
|
|
143
|
-
return Installation(tool, kind, url, revision, extras, tuple(rebuilt), tuple(unrebuildable))
|
|
149
|
+
return Installation(tool, kind, url, revision, extras, tuple(rebuilt), _source_flags(recorded), tuple(unrebuildable))
|
|
144
150
|
|
|
145
151
|
# A local checkout, however uv spelled it. Reinstalling one would
|
|
146
152
|
# discard the working copy the user is developing against.
|
|
@@ -156,6 +162,43 @@ def read_installation(tool: str) -> Installation:
|
|
|
156
162
|
return installed_as(InstallKind.INDEX)
|
|
157
163
|
|
|
158
164
|
|
|
165
|
+
def _source_flags(recorded: dict) -> tuple[str, ...]:
|
|
166
|
+
"""The uv flags that reinstall onto the receipt's Python from the receipt's package sources.
|
|
167
|
+
|
|
168
|
+
Without them a reinstall resolves against PyPI alone and moves to uv's
|
|
169
|
+
default Python. uv records a setting from a flag, the environment or a
|
|
170
|
+
config file alike, and cannot say which. An `explicit` or `flat` index only
|
|
171
|
+
a config file can declare, and that file is read again by the reinstall, so
|
|
172
|
+
it is left out. Dropping any other recorded option changes how the tool
|
|
173
|
+
resolves or builds, never whether its packages can be found.
|
|
174
|
+
"""
|
|
175
|
+
options = recorded.get('options') or {}
|
|
176
|
+
pairs: list[tuple[str, str]] = []
|
|
177
|
+
if recorded.get('python'):
|
|
178
|
+
pairs.append(('--python', str(recorded['python'])))
|
|
179
|
+
for index in options.get('index') or ():
|
|
180
|
+
if index.get('explicit') or index.get('format', 'simple') != 'simple' or not index.get('url'):
|
|
181
|
+
continue
|
|
182
|
+
if index.get('default'):
|
|
183
|
+
pairs.append(('--default-index', str(index['url'])))
|
|
184
|
+
else:
|
|
185
|
+
name = index.get('name')
|
|
186
|
+
pairs.append(('--index', f'{name}={index["url"]}' if name else str(index['url'])))
|
|
187
|
+
if options.get('index-url'):
|
|
188
|
+
pairs.append(('--index-url', str(options['index-url'])))
|
|
189
|
+
pairs += [('--extra-index-url', str(url)) for url in options.get('extra-index-url') or ()]
|
|
190
|
+
pairs += [('--find-links', str(location)) for location in options.get('find-links') or ()]
|
|
191
|
+
pairs += [(f'--{key}', str(options[key])) for key in ('index-strategy', 'keyring-provider') if options.get(key)]
|
|
192
|
+
if options.get('no-index'):
|
|
193
|
+
pairs.append(('--no-index', ''))
|
|
194
|
+
|
|
195
|
+
# A source declared in a config file is recorded again beside the flag that
|
|
196
|
+
# passes it back. Passing each source once holds it at two copies, rather
|
|
197
|
+
# than adding one to the receipt per update.
|
|
198
|
+
distinct = dict.fromkeys(pairs)
|
|
199
|
+
return tuple(part for flag, value in distinct for part in (flag, value) if part)
|
|
200
|
+
|
|
201
|
+
|
|
159
202
|
def _split_git(git: str) -> tuple[str, dict[str, str], str]:
|
|
160
203
|
"""A receipt's git source as its URL, its query and the commit after `#`.
|
|
161
204
|
|
|
@@ -221,11 +264,15 @@ def current_version(package: str) -> str:
|
|
|
221
264
|
|
|
222
265
|
|
|
223
266
|
def requirement_for(installation: Installation, ref: str) -> str:
|
|
224
|
-
"""The requirement string that installs `ref` of an already-installed tool, with the extras it has.
|
|
267
|
+
"""The requirement string that installs `ref` of an already-installed tool, with the extras it has.
|
|
268
|
+
|
|
269
|
+
An index install takes `ref` as a floor. uv records the specifier in the
|
|
270
|
+
receipt, and `uv tool upgrade` refuses to move an exact pin.
|
|
271
|
+
"""
|
|
225
272
|
named = f'{installation.tool}[{",".join(installation.extras)}]' if installation.extras else installation.tool
|
|
226
273
|
if installation.kind is InstallKind.GIT:
|
|
227
274
|
return f'{named} @ git+{installation.url}@{ref}'
|
|
228
|
-
return f'{named}
|
|
275
|
+
return f'{named}>={ref.removeprefix("v")}'
|
|
229
276
|
|
|
230
277
|
|
|
231
278
|
@dataclass(frozen=True)
|
|
@@ -355,7 +402,14 @@ def _pins_from_export(exported: str, extras: dict[str, tuple[str, ...]]) -> Pins
|
|
|
355
402
|
return Pins(tuple(constraints), tuple(overrides))
|
|
356
403
|
|
|
357
404
|
|
|
358
|
-
def run_install(
|
|
405
|
+
def run_install(
|
|
406
|
+
requirement: str,
|
|
407
|
+
*,
|
|
408
|
+
quiet: bool = True,
|
|
409
|
+
pins: Pins | None = None,
|
|
410
|
+
with_requirements: Sequence[str] = (),
|
|
411
|
+
source_flags: Sequence[str] = (),
|
|
412
|
+
) -> None:
|
|
359
413
|
"""Install a requirement over any existing tool of that name, held to `pins` when given.
|
|
360
414
|
|
|
361
415
|
`with_requirements` are requirements-file lines installed beside the tool,
|
|
@@ -374,7 +428,7 @@ def run_install(requirement: str, *, quiet: bool = True, pins: Pins | None = Non
|
|
|
374
428
|
listed = Path(scratch) / 'with.txt'
|
|
375
429
|
listed.write_text(''.join(f'{line}\n' for line in with_requirements), encoding='utf-8')
|
|
376
430
|
beside = ['--with-requirements', str(listed)]
|
|
377
|
-
command = [executable, 'tool', 'install', '--force', *held, *beside, requirement]
|
|
431
|
+
command = [executable, 'tool', 'install', '--force', *source_flags, *held, *beside, requirement]
|
|
378
432
|
if quiet:
|
|
379
433
|
command.insert(1, '--quiet')
|
|
380
434
|
completed = _run(command)
|
|
@@ -131,7 +131,13 @@ def install_release(config: Config, result: Result, installation: Installation,
|
|
|
131
131
|
|
|
132
132
|
ref = result.release.install_ref()
|
|
133
133
|
pins = read_lock(installation.url, ref, extras=installation.extras) if installation.kind is InstallKind.GIT else None
|
|
134
|
-
run_install(
|
|
134
|
+
run_install(
|
|
135
|
+
requirement_for(installation, ref),
|
|
136
|
+
quiet=quiet,
|
|
137
|
+
pins=pins,
|
|
138
|
+
with_requirements=installation.with_requirements,
|
|
139
|
+
source_flags=installation.source_flags,
|
|
140
|
+
)
|
|
135
141
|
|
|
136
142
|
return Result(
|
|
137
143
|
current=result.current,
|
|
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
|
|
File without changes
|
|
File without changes
|