plesty.server.services.updates

Updating the application from inside it: Help ▸ Check for updates… (#29).

Two installs can update themselves, the two the installers make:

  • Windows — a virtual environment of its own, %LOCALAPPDATA%\PLESTY Bench\app, updated with pip install into it.

  • macOS and Linux — a uv tool environment (it carries uv’s uv-receipt.toml), updated with uv tool install --force.

Neither update runs inside the console. On Windows it cannot: the console is that environment’s interpreter, Windows will not replace a file a running process holds, and pip fails on the first one it meets. Elsewhere it could, but the console would go on running the old code until restarted. So the console hands the update to a helper outside the environment — assets/update.ps1 or assets/update.sh, copied out first — which waits for the console to exit and installs. Beside it the console starts the update window, assets/update_window.py, on the interpreter the environment was made from: it shows the install as it runs and, when it has finished, offers to open the new console. Where no window can be shown, the helper starts the console again itself.

The result is left in two files the console reads on its next start, so a failure is reported even if the window was closed early, and is kept as the last update, which Help ▸ About shows.

A console run from a source checkout is not an install, and is told so.

Attributes

PACKAGE

REQUIREMENT

PYPI_URL

INSTALL_DIR

UV_RECEIPT

PENDING

RECORD

CHECKOUT_NOTE

STARTUP_CHECK

CHECK_EVERY

Exceptions

UpdateError

The newest version could not be read, or the update could not be started.

Classes

Install

An install this console can update.

LastUpdate

What the helper left of the last update, read once on the next start.

UpdateRecord

The last update that finished, as Help ▸ About shows it.

Functions

installed_version(→ str)

The version of the console that is running; "dev" for a checkout never installed.

latest_version(→ str)

The newest release of the console on PyPI.

_fetch(→ bytes)

is_newer(→ bool)

Whether release candidate is later than current.

_release(→ tuple[int, Ellipsis] | None)

_is_prerelease(→ bool)

install_dir(→ pathlib.Path | None)

The Windows installer's directory, %LOCALAPPDATA%\PLESTY Bench, if any.

state_dir(→ pathlib.Path | None)

Where the update helper and its log go on this platform.

current_install(→ Install | None)

The install this console runs from, when it is one that can update itself.

describe_install(→ str)

How this console was installed, and where, in a line for Help ▸ About.

_same_path(→ bool)

_on_windows(→ bool)

Whether platform — this one when None — is Windows, as host decides.

update_log(→ pathlib.Path)

Where the helper writes its log; its .exit and .reason lie beside it.

launcher(→ pathlib.Path)

The console's launcher once the update is in: what Open PLESTY Bench starts.

window_python(→ pathlib.Path | None)

The interpreter to show the update window with, or None when it has no Tk.

start_update(→ pathlib.Path)

Hand the update to the helper, which runs once this console has exited.

_find_uv(→ str)

_beside(→ pathlib.Path)

last_update(→ LastUpdate | None)

What the last update left, once: its marker files are removed as they are read.

_keep(→ None)

Keep result, with what was asked for, as the last update.

update_record(→ UpdateRecord | None)

The last update that finished, or None when there has been none to tell of.

startup_update(→ str | None)

The release newer than installed to tell of when the console starts, or None.

Module Contents

plesty.server.services.updates.PACKAGE = 'plesty-server'
plesty.server.services.updates.REQUIREMENT = 'plesty-server[gui]'
plesty.server.services.updates.PYPI_URL = 'https://pypi.org/pypi/plesty-server/json'
plesty.server.services.updates.INSTALL_DIR = 'PLESTY Bench'
plesty.server.services.updates.UV_RECEIPT = 'uv-receipt.toml'
plesty.server.services.updates.PENDING = 'update.json'
plesty.server.services.updates.RECORD = 'last-update.json'
plesty.server.services.updates.CHECKOUT_NOTE = 'This copy runs from a source checkout, not from an install, so it cannot update itself. Update...
exception plesty.server.services.updates.UpdateError

Bases: RuntimeError

The newest version could not be read, or the update could not be started.

Initialize self. See help(type(self)) for accurate signature.

class plesty.server.services.updates.Install

An install this console can update.

Variables:
  • kind – windows (the installer’s environment) or uv-tool.

  • env – Its environment.

  • state – Where the helper and its log go — outside env.

kind: str
env: pathlib.Path
state: pathlib.Path
class plesty.server.services.updates.LastUpdate

What the helper left of the last update, read once on the next start.

Variables:
  • ok – Whether the install succeeded.

  • reason – Why it failed, in one line; empty on success.

  • log – The helper’s log, for the details.

  • shown – Whether the update window already showed the operator how it went.

ok: bool
reason: str
log: pathlib.Path
shown: bool = False
class plesty.server.services.updates.UpdateRecord

The last update that finished, as Help ▸ About shows it.

Variables:
  • from_version – The version that was installed before it; "" if unknown.

  • to_version – The version it installed, or tried to.

  • asked – When it was asked for, ISO 8601; "" if unknown.

  • finished – When the helper finished, ISO 8601.

  • ok – Whether the install succeeded.

  • reason – Why it failed; empty on success.

  • log – The helper’s log.

from_version: str
to_version: str
asked: str
finished: str
ok: bool
reason: str
log: str
plesty.server.services.updates.installed_version() → str

The version of the console that is running; "dev" for a checkout never installed.

Return type:

str

plesty.server.services.updates.latest_version(fetch: collections.abc.Callable[[str], bytes] | None = None) → str

The newest release of the console on PyPI.

Parameters:

fetch (collections.abc.Callable[[str], bytes] | None) – Reads a URL; a 10-second request by default.

Returns:

Its version.

Raises:

UpdateError – When PyPI cannot be reached or does not answer as expected.

Return type:

str

plesty.server.services.updates._fetch(url: str) → bytes
Parameters:

url (str)

Return type:

bytes

plesty.server.services.updates.is_newer(candidate: str, current: str) → bool

Whether release candidate is later than current.

Compared by their release numbers, which is all a PyPI release carries. A current that is not a plain release — dev, or a checkout’s 0.5.0.dev3+abc — is older than the release it leads to.

Parameters:
  • candidate (str)

  • current (str)

Return type:

bool

plesty.server.services.updates._release(version: str) → tuple[int, Ellipsis] | None
Parameters:

version (str)

Return type:

tuple[int, Ellipsis] | None

plesty.server.services.updates._is_prerelease(version: str) → bool
Parameters:

version (str)

Return type:

bool

plesty.server.services.updates.install_dir(environ: collections.abc.Mapping[str, str] | None = None) → pathlib.Path | None

The Windows installer’s directory, %LOCALAPPDATA%\PLESTY Bench, if any.

Parameters:

environ (collections.abc.Mapping[str, str] | None)

Return type:

pathlib.Path | None

plesty.server.services.updates.state_dir(environ: collections.abc.Mapping[str, str] | None = None, platform: str | None = None) → pathlib.Path | None

Where the update helper and its log go on this platform.

Windows: beside the install log (install_dir()). Elsewhere: the user’s state directory, $XDG_STATE_HOME/plesty-bench or ~/.local/state/plesty-bench — never inside the environment being replaced.

Parameters:
  • environ (collections.abc.Mapping[str, str] | None)

  • platform (str | None)

Return type:

pathlib.Path | None

plesty.server.services.updates.current_install(prefix: str | None = None, environ: collections.abc.Mapping[str, str] | None = None, platform: str | None = None) → Install | None

The install this console runs from, when it is one that can update itself.

Parameters:
  • prefix (str | None) – The running interpreter’s environment; sys.prefix by default.

  • environ (collections.abc.Mapping[str, str] | None) – The environment variables; this process’s by default.

  • platform (str | None) – The platform; this one by default.

Returns:

The Windows installer’s environment, %LOCALAPPDATA%\PLESTY Bench\app; a uv tool environment (one holding UV_RECEIPT); or None for anything else — a source checkout, above all.

Return type:

Install | None

plesty.server.services.updates.describe_install(install: Install | None, prefix: str | None = None) → str

How this console was installed, and where, in a line for Help ▸ About.

Parameters:
  • install (Install | None)

  • prefix (str | None)

Return type:

str

plesty.server.services.updates._same_path(one: pathlib.Path, other: pathlib.Path) → bool
Parameters:
  • one (pathlib.Path)

  • other (pathlib.Path)

Return type:

bool

plesty.server.services.updates._on_windows(platform: str | None) → bool

Whether platform — this one when None — is Windows, as host decides.

Parameters:

platform (str | None)

Return type:

bool

plesty.server.services.updates.update_log(base: pathlib.Path) → pathlib.Path

Where the helper writes its log; its .exit and .reason lie beside it.

Parameters:

base (pathlib.Path)

Return type:

pathlib.Path

plesty.server.services.updates.launcher(install: Install) → pathlib.Path

The console’s launcher once the update is in: what Open PLESTY Bench starts.

Windows: the copy the installer put in ~\.local\bin, which the shortcuts start, else the environment’s own. A uv tool: the environment’s, which uv tool install --force puts back in place.

Parameters:

install (Install)

Return type:

pathlib.Path

plesty.server.services.updates.window_python() → pathlib.Path | None

The interpreter to show the update window with, or None when it has no Tk.

The one the environment was made from: it lies outside the environment the update replaces. Asked whether it can import tkinter first, so an interpreter without it leaves the helper to start the console again.

Return type:

pathlib.Path | None

plesty.server.services.updates.start_update(version: str, install: Install, pid: int | None = None, spawn: collections.abc.Callable[Ellipsis, Any] | None = None, uv: collections.abc.Callable[[], str] | None = None, python: collections.abc.Callable[[], pathlib.Path | None] | None = None, installed: str | None = None, theme: collections.abc.Mapping[str, Any] | None = None) → pathlib.Path

Hand the update to the helper, which runs once this console has exited.

Parameters:
  • version (str) – The release to install.

  • install (Install) – The install to update (current_install()).

  • pid (int | None) – The console’s process, which the helper waits out; this one by default.

  • spawn (collections.abc.Callable[Ellipsis, Any] | None) – Starts the helper and the window; subprocess.Popen by default.

  • uv (collections.abc.Callable[[], str] | None) – Finds uv for a uv tool install; the bench’s own search by default.

  • python (collections.abc.Callable[[], pathlib.Path | None] | None) – Finds the interpreter for the update window, None for none; window_python() by default.

  • installed (str | None) – The version being replaced, for the record; this one by default.

  • theme (collections.abc.Mapping[str, Any] | None) – The console’s palette and fonts, for the window to be drawn in (tokens, ui_font, mono_font, mark); its own copy of the palette when None.

Returns:

The helper’s log.

Raises:

UpdateError – When the helper cannot be prepared or started.

Return type:

pathlib.Path

plesty.server.services.updates._find_uv() → str
Return type:

str

plesty.server.services.updates._beside(log: pathlib.Path, suffix: str) → pathlib.Path
Parameters:
  • log (pathlib.Path)

  • suffix (str)

Return type:

pathlib.Path

plesty.server.services.updates.last_update(base: pathlib.Path | None) → LastUpdate | None

What the last update left, once: its marker files are removed as they are read.

What it left is kept as the last update (update_record()) for Help ▸ About, which shows it for as long as no newer update finishes.

Parameters:

base (pathlib.Path | None) – The helper’s directory (state_dir()).

Returns:

The result, or None when no update has finished since the last start.

Return type:

LastUpdate | None

plesty.server.services.updates._keep(base: pathlib.Path, result: LastUpdate, finished: datetime.datetime) → None

Keep result, with what was asked for, as the last update.

Parameters:
  • base (pathlib.Path)

  • result (LastUpdate)

  • finished (datetime.datetime)

Return type:

None

plesty.server.services.updates.update_record(base: pathlib.Path | None) → UpdateRecord | None

The last update that finished, or None when there has been none to tell of.

Parameters:

base (pathlib.Path | None) – The helper’s directory (state_dir()).

Return type:

UpdateRecord | None

plesty.server.services.updates.STARTUP_CHECK = 'update-check.json'
plesty.server.services.updates.CHECK_EVERY
plesty.server.services.updates.startup_update(home: pathlib.Path, installed: str, now: datetime.datetime | None = None, fetch: collections.abc.Callable[[str], bytes] | None = None) → str | None

The release newer than installed to tell of when the console starts, or None.

PyPI is asked at most once per CHECK_EVERY; within it, the release the last check heard of is used again, so the line stays on every start until the bench is updated. Only a check that answered is written down: one with no network is tried again on the next start. Never raises — a bench with no network says nothing.

Parameters:
  • home (pathlib.Path) – The bench home, where STARTUP_CHECK is kept.

  • installed (str) – The version running.

  • now (datetime.datetime | None) – The time; injected by tests.

  • fetch (collections.abc.Callable[[str], bytes] | None) – Reads a URL; injected by tests.

Return type:

str | None