spacr.install_cleanup

Find spaCR installations and run the appropriate update or removal procedure.

The standard replacement procedure runs three steps in order:

  1. find old spaCR files – find_old_installs()

  2. delete old spaCR files – remove_install()

  3. install new spaCR – run_update_sequence() runs the install only after step 2 removed every installer-made copy.

In-app updates use a different procedure for supported macOS installations. An online installation upgrades spaCR in its existing Python environment using its bundled uv executable. It keeps the environment, bootstrap files, application launcher, and installed packages that need no update. It verifies the installed spaCR version before restarting the application.

A supported frozen macOS application uses a verified DMG replacement and retains its complete previous application bundle. These macOS update paths do not run the standard deletion procedure. Other frozen application families require a supported replacement adapter before an update can start.

Removal never runs an old version’s own uninstaller, so a copy whose Uninstall.exe or uninstall-spacr.sh is missing or broken is removed all the same. Preferences and user data are kept: ~/.spacr, ~/.cache/spacr, ~/.local/state/spacr, ~/spacr-demos, ~/spacr-tutorials, the Hugging Face cache, and the spacr/qt settings (~/.config/spacr, the macOS preferences file, and the Software\spacr registry key on Windows).

A pip or conda environment the user made is listed but never deleted; the spaCR package is uninstalled from it only when the caller says it was ticked. An editable source checkout is reported and left alone.

The module uses only the standard library and imports nothing from spaCR, so an installer can run it with its bootstrapped Python before any spaCR exists, and a helper process can run a copy of it after the running spaCR has closed:

python -I install_cleanup.py find [--json]
python -I install_cleanup.py remove [--keep PATH] [--sudo] [--purge [--yes]]
python -I install_cleanup.py purge [--yes] [--dry-run]
python -I install_cleanup.py run-plan PLAN.json

purge is the opt-in clean uninstall: it also deletes spaCR’s caches, backend environments and user data, after listing them and asking. It never runs on its own; remove --purge runs it after the removal succeeded.

Classes

InstallRecord

One spaCR installation found on this computer.

RemovalReport

What remove_install() did with one installation.

Functions

find_old_installs(→ List[InstallRecord])

Find every spaCR installation on this computer.

remove_install([system])

Remove one installation, without running its own uninstaller.

run_update_sequence(install, *[, records, ticked, ...])

Find, then delete, then install -- and never install over an old copy.

start_update_helper(, pid, workdir[, system, spawn])

Run an installation update in a process that outlives spaCR.

Module Contents

class spacr.install_cleanup.InstallRecord[source]

One spaCR installation found on this computer.

Parameters:
  • kind – "installer" for a copy a spaCR installer made, "environment" for a pip or conda environment the user made, or "checkout" for an editable source checkout.

  • layout – which layout it is, for example "windows-online", "linux-online", "macos-app" or "conda".

  • platform – "windows", "macos" or "linux".

  • root – the directory the installation lives in.

  • version – the spaCR version, when it can be read.

  • launchers – command launchers that start this copy.

  • shortcuts – desktop and Start Menu shortcut files.

  • menu_entries – application-menu entries: .desktop files and Start Menu folders.

  • registrations – uninstall registrations: registry: keys, deb: packages, and application bundles.

  • python – the environment’s interpreter, used to uninstall spaCR.

  • running – whether the current process runs from this copy.

  • needs_admin – whether removal needs administrator rights.

  • notes – other facts worth showing, such as a missing uninstaller.

class spacr.install_cleanup.RemovalReport[source]

What remove_install() did with one installation.

Parameters:
  • record – the installation this report is about.

  • removed – every path, registry entry and package that was removed.

  • failed – (item, reason) for everything that could not be removed.

  • skipped – (item, reason) for everything left in place on purpose.

property ok: bool[source]

Whether nothing failed.

spacr.install_cleanup.find_old_installs(*, system=None) → List[InstallRecord][source]

Find every spaCR installation on this computer.

Installer-made copies come first, then environments the user made, then editable source checkouts. Every layout any released installer used is looked for: the Windows online and offline installers, the Linux and macOS online installers, the macOS application bundle, and the Debian package.

Parameters:

system – the computer to look at; None means this one. Tests pass a stand-in over a temporary directory.

Returns:

one InstallRecord per installation.

spacr.install_cleanup.remove_install(record: InstallRecord, *, ticked: bool = False, keep: Sequence[str] = (), system=None) → RemovalReport[source]

Remove one installation, without running its own uninstaller.

An installer-made copy is removed completely: its folder, launchers, shortcuts, menu entries and uninstall registrations. A Debian package is removed through the package manager, and reported as needing administrator rights when that is what stops it. From an environment the user made, only the spaCR package is uninstalled, and only when ticked; the environment itself stays. A source checkout is skipped.

The running copy is not removed here, because on Windows an installation cannot delete itself while it runs. start_update_helper() runs the appropriate update procedure after spaCR has closed.

Parameters:
  • record – an installation from find_old_installs().

  • ticked – whether the user ticked this environment for removal.

  • keep – paths to leave in place, such as the log of the install that is about to start.

  • system – the computer; None means this one.

Returns:

what was removed, what could not be, and why.

spacr.install_cleanup.run_update_sequence(install: Callable[[], object], *, records: Sequence[InstallRecord] | None = None, ticked: Iterable[str] = (), keep: Sequence[str] = (), find=None, remove=None, system=None)[source]

Find, then delete, then install – and never install over an old copy.

Every installer-made copy is removed, the spaCR package is uninstalled from each ticked environment, and only then is install called. When an installer-made copy could not be removed, install is not called at all, and the reports say what stopped it.

Parameters:
  • install – zero-argument callable that installs the new version.

  • records – installations already found; find_old_installs() is called when None.

  • ticked – roots of the environments the user ticked.

  • keep – paths removal must leave in place.

  • find – replaces find_old_installs().

  • remove – replaces remove_install().

  • system – the computer; None means this one.

Returns:

(reports, install_result); install_result is None when the install was not run.

spacr.install_cleanup.start_update_helper(records: Sequence[InstallRecord], version: str, *, ticked: Iterable[str] = (), pid: int | None = None, workdir: str | None = None, system=None, spawn=None) → Dict[source]

Run an installation update in a process that outlives spaCR.

The running spaCR must be an installer-made copy. The helper waits for this process to exit before changing the installation.

For a supported macOS online installation, use the existing bundled uv and private Python to upgrade spaCR in the same environment. Preserve the environment directory, bootstrap files, application launcher, and installed packages that need no update. Restart only after verifying that the installed version is at least version and newer than the previous version, when that previous version is known. A failed upgrade or version check does not trigger removal or a replacement installer.

The standard replacement path downloads the new installer, removes older installer-made copies, installs the new version, and starts it. If any required removal fails, skip installation and write the reason to the log.

Parameters:
  • records – installations from find_old_installs().

  • version – the version to install.

  • ticked – roots of the environments the user ticked.

  • pid – the process to wait for; this process when None.

  • workdir – the helper’s folder; a new temporary folder when None.

  • system – the computer; None means this one.

  • spawn – replaces the detached process launcher.

Returns:

the plan as written to plan.json. plan["command"] is the helper’s command, or None with plan["error"] saying why nothing was started.

A frozen macOS bundle in a user-writable location uses its verified DMG replacement adapter and retains its complete prior bundle. Other frozen application families return an error before removing anything; the helper does not enable an online-installer fallback for these applications.

Nested helpers

_delete._writable_then_retry(func, failed_path, exc_info)

Clear a read-only flag and retry once, recording failures.

Parameters:
  • func – the os function that failed.

  • failed_path – the path it failed on.

  • exc_info – the error, as shutil.rmtree() passes it.

spacr/install_cleanup.py:1154

_macos_recover_user_bundle.identity(path)

Bind recovery to actual directory inodes, not mutable app names alone.

Parameters:

path – journaled bundle directory to identify.

Returns:

JSON-compatible device/inode pair.

spacr/install_cleanup.py:2586

_macos_replace_user_bundle.identity(path)

Record the actual directory identity before or after the exchange.

Parameters:

path – bundle directory whose device and inode are recorded.

Returns:

JSON-compatible device/inode pair.

spacr/install_cleanup.py:2246

_macos_tree.refuse_unreadable(error)

Propagate a filesystem traversal failure instead of omitting its subtree.

Parameters:

error – operating-system exception reported by os.walk.

spacr/install_cleanup.py:2044

_run_plan._finish(code: int) → int

Write the update log and return code.

Parameters:

code – the exit code to return.

spacr/install_cleanup.py:3143

_standalone_helper_command.contains(path, root)

Compare canonical paths using the host filesystem’s case semantics.

Parameters:
  • path – candidate nested path.

  • root – containing directory to check.

Returns:

whether the candidate resolves inside the directory.

spacr/install_cleanup.py:2666