plesty.server.services.installer
Creates the environments the DeviceManager keeps books on.
PyPI source — one relocatable
uvvenv per(package, version)at<home>/packages/<package>/<version>/. An unpinned spec is installed into a staging directory first and moved under the version it resolved to, so the layout never has to guess what “newest” was.Git source — one checkout per
(package, ref)at<home>/repos/<package>/<ref>/withuv sync’s.venvas the environment. A checkout is what makes the developer loop possible from afar:Installer.update()pulls and re-syncs, and the repository’s owntests/field_test.pyandplesty checkrun right there.
uv (and git for repositories) are the only tools used. Both are driven
through an injectable Runner, so tests never spawn them, and every
line they print goes to an Echo — the job log when run as a job.
Neither has to be on PATH: uv is looked up there, git also where
Windows puts it and, failing that, installed for the bench
(plesty.server.services.gitbin).
Attributes
Exceptions
|
Classes
Install, update and remove device environments; records them in the manager. |
Functions
|
path in the form that compares equal for two spellings of one directory. |
|
Delete the environment of package, saying why when the host will not. |
|
Run cmd, streaming its merged output to echo as it arrives. |
|
Default |
|
PEP 503 normalisation, enough to compare distribution names. |
Module Contents
- plesty.server.services.installer.CHECKOUT_VENV = '.venv'
- plesty.server.services.installer.PYTHON_SPEC = '>=3.12'
- plesty.server.services.installer.STAGING_PREFIX = '.staging-'
- plesty.server.services.installer.PRERELEASE_HINT = 'pre-release'
- plesty.server.services.installer.UV_DIRECTORIES = ('~/.local/bin', '~/.cargo/bin')
- plesty.server.services.installer.UV_DIRECTORIES_POSIX = ('/opt/homebrew/bin', '/usr/local/bin')
- plesty.server.services.installer.UV_DIRECTORIES_WINDOWS = ('%LOCALAPPDATA%\\Microsoft\\WindowsApps', '%USERPROFILE%\\scoop\\shims',...
- plesty.server.services.installer.GIT_ADVICE = 'install it (macOS: xcode-select --install; Debian/Ubuntu: apt install git) — or declare the...
- plesty.server.services.installer.Echo
- plesty.server.services.installer.Runner
- plesty.server.services.installer._same_dir(path: str) str
path in the form that compares equal for two spellings of one directory.
- Parameters:
path (str)
- Return type:
str
- plesty.server.services.installer._remove_env(env: pathlib.Path, package: str) None
Delete the environment of package, saying why when the host will not.
Windows keeps a file open by a running process undeletable, so replacing the environment of a device that is still serving fails there where it succeeds on POSIX. The bare
PermissionErrornames a file deep inside a venv; this names the device instead.- Raises:
InstallError – When the environment is in use.
- Parameters:
env (pathlib.Path)
package (str)
- Return type:
None
- exception plesty.server.services.installer.InstallError
Bases:
RuntimeErroruv/gitfailed, or the environment is not in the shape expected.Initialize self. See help(type(self)) for accurate signature.
- plesty.server.services.installer.default_runner(cmd: list[str], echo: Echo | None = None) subprocess.CompletedProcess[str]
Run cmd, streaming its merged output to echo as it arrives.
stderr is folded into stdout so the order the tool wrote them in survives; the text is returned as well, so a failure still carries its own detail.
- Parameters:
cmd (list[str])
echo (Echo | None)
- Return type:
subprocess.CompletedProcess[str]
- class plesty.server.services.installer.Installer(home: plesty.server.model.home.Home, manager: plesty.server.model.device_manager.DeviceManager | None = None, uv: str | None = None, git: str | None = None, runner: Runner | None = None, echo: Echo | None = None)
Install, update and remove device environments; records them in the manager.
Bind the installer to a home.
- Parameters:
home (plesty.server.model.home.Home) – The bench home.
manager (plesty.server.model.device_manager.DeviceManager | None) – The inventory to record into; a fresh one on home by default.
uv (str | None) – Path of the
uvexecutable; looked up onPATHby default.git (str | None) – Path of the
gitexecutable; found (or installed) by default.runner (Runner | None) – Executes a command line — injected by tests.
echo (Echo | None) – Where each line
uvandgitwrite goes while they run. Defaults to stdout, which is the job log when run as a job.
- home
- manager
- _uv = None
- _git = None
- _run
- property uv: str
The
uvexecutable; raisesInstallErrorwhen the bench has none.- Return type:
str
- _find_uv() str
PATHfirst, then where uv installs itself.- Raises:
InstallError – When there is no uv to be found.
- Return type:
str
- property git: str
The
gitexecutable, installing one for the bench when it has none.- Raises:
InstallError – When the bench has no git and cannot be given one.
- Return type:
str
- _on_path(git: str) str
Put git’s directory on
PATHfor this process and its children.Calling git by absolute path is enough for the installer’s own clone and checkout — and not enough for the install. A module’s dependencies may be
git+httpsURLs, and theuvthis spawns to resolve them looks for git onPATHand nowhere else: on the very bench this feature exists for, one with no git at all, the install would fetch a portable git, clone with it, and then fail inuv syncwith “Git executable not found”. The same holds for a git found under%ProgramFiles%but absent fromPATH.- Parameters:
git (str)
- Return type:
str
- _find_or_install_git() str
Locate git, or fetch a portable one (Windows); explains itself either way.
- Return type:
str
- python(installed: plesty.server.model.device_manager.InstalledVersion) pathlib.Path
The interpreter that runs installed.
- Parameters:
installed (plesty.server.model.device_manager.InstalledVersion)
- Return type:
pathlib.Path
- install(spec: plesty.server.model.device.DeviceSpec, force: bool = False, job_id: str | None = None) plesty.server.model.device_manager.InstalledVersion
Make sure an environment serving spec exists; returns its record.
- Parameters:
spec (plesty.server.model.device.DeviceSpec) – What to install: package plus a pinned version (PyPI) or a repository and ref (git checkout).
force (bool) – Reinstall even when a satisfying environment is on record.
job_id (str | None) – The job this runs as, stored on the record.
- Raises:
InstallError – When
uv/gitfail or the package did not end up installed.- Return type:
- _install_the_tag_instead(spec: plesty.server.model.device.DeviceSpec, job_id: str | None, failure: InstallError) plesty.server.model.device_manager.InstalledVersion
After a failed release install, install the same version from the repository.
A module is on the hub before it is on PyPI: the registry knows the release from its tag, and the package reaches the index only once the release job has run — so “that release is not on the index” is a routine state of a young module, not a broken bench. The repository holds the same version under
v<version>, which is the ref a spec with a version already resolves to.- Raises:
InstallError – When no repository is known for the package, or the repository has no such tag either — carrying both reasons.
- Parameters:
job_id (str | None)
failure (InstallError)
- Return type:
- update(installed: plesty.server.model.device_manager.InstalledVersion, ref: str | None = None, job_id: str | None = None) plesty.server.model.device_manager.InstalledVersion
Bring installed up to date; returns the (possibly new) record.
Git checkout: fetch, check out ref (default: the recorded ref), fast-forward,
uv sync. PyPI: reinstall the same version.- Parameters:
installed (plesty.server.model.device_manager.InstalledVersion) – The environment to refresh.
ref (str | None) – Branch, tag or commit to move a checkout to.
job_id (str | None) – The job this runs as.
- Return type:
- uninstall(installed: plesty.server.model.device_manager.InstalledVersion) bool
Delete the environment of installed and forget it.
- Returns:
Truewhen a directory was removed.- Parameters:
installed (plesty.server.model.device_manager.InstalledVersion)
- Return type:
bool
- _install_release(spec: plesty.server.model.device.DeviceSpec, job_id: str | None) plesty.server.model.device_manager.InstalledVersion
- Parameters:
job_id (str | None)
- Return type:
- _install_checkout(spec: plesty.server.model.device.DeviceSpec, job_id: str | None) plesty.server.model.device_manager.InstalledVersion
- Parameters:
job_id (str | None)
- Return type:
- _uv_call(args: list[str], what: str, quiet: bool = False) subprocess.CompletedProcess[str]
- Parameters:
args (list[str])
what (str)
quiet (bool)
- Return type:
subprocess.CompletedProcess[str]
- _uv_resolve(args: list[str], what: str) subprocess.CompletedProcess[str]
Run a resolving
uvcommand, retrying once with pre-releases allowed.A hub module under development depends on a
.devrelease of plesty-lib, and uv considers only stable versions unless it is told otherwise — it fails with a hint naming the pre-release. Allowing them from the start would pull a pre-release into every install that has a stable answer, so it is the retry, not the rule.- Parameters:
args (list[str])
what (str)
- Return type:
subprocess.CompletedProcess[str]
- _git_call(args: list[str], what: str, quiet: bool = False) subprocess.CompletedProcess[str]
- Parameters:
args (list[str])
what (str)
quiet (bool)
- Return type:
subprocess.CompletedProcess[str]
- _call(cmd: list[str], what: str, quiet: bool = False) subprocess.CompletedProcess[str]
Run cmd; quiet keeps a query’s output out of the log (it is read, not watched).
- Parameters:
cmd (list[str])
what (str)
quiet (bool)
- Return type:
subprocess.CompletedProcess[str]
- _installed_version(python: pathlib.Path, package: str) str | None
- Parameters:
python (pathlib.Path)
package (str)
- Return type:
str | None
- _python_version(python: pathlib.Path) str
- Parameters:
python (pathlib.Path)
- Return type:
str
- plesty.server.services.installer._print(line: str) None
Default
Echo: stdout, flushed so a tailed job log sees it.- Parameters:
line (str)
- Return type:
None
- plesty.server.services.installer._canonical(name: str) str
PEP 503 normalisation, enough to compare distribution names.
- Parameters:
name (str)
- Return type:
str