Source code for matmelt.commands.update

"""The ``update`` command: upgrade MatMelt in place (5.2b).

The counterpart of ``install.sh``: the installer records what it installed
into ``<venv>/matmelt.spec`` (a PyPI name, a git URL, or a local
path), and this command re-runs ``pip install --upgrade <that spec>`` with
the interpreter it is running under -- an update always comes from the same
source the install did, never a guessed one.

A copy that was *not* installed by ``install.sh`` (a dev checkout, a plain
``pip install``, a system python) has no spec file, and upgrading it here
would be a guess about how the user manages that environment -- refused with
the right command for each case named instead.

Exit codes: 0 = updated, 1 = refused or pip failed.
"""

from __future__ import annotations

import subprocess
import sys
from pathlib import Path

from matmelt.ui.console import render_error, render_note

#: Written by install.sh next to the venv's bin/ -- the update contract.
SPEC_FILENAME = "matmelt.spec"


[docs] def run_update() -> int: """Upgrade this installation from the spec its installer recorded. Returns ------- int pip's own exit code on an attempted upgrade, or ``1`` when this installation cannot be updated this way -- a system Python, or a venv with no recorded spec, each refused with the command that *would* work. Notes ----- The new version is reported by a **fresh interpreter**: this process is still running the old code, so asking it would print the version that was just replaced. """ if sys.prefix == sys.base_prefix: render_error( "update: this MatMelt runs on a system Python, not an " "install.sh venv -- update it the way it was installed " "(e.g. pip install --upgrade matmelt)." ) return 1 spec_file = Path(sys.prefix) / SPEC_FILENAME if not spec_file.is_file(): render_error( "update: this environment was not set up by install.sh (no " f"{SPEC_FILENAME} in {sys.prefix}) -- update it the way it was " "installed: 'pip install --upgrade matmelt' for a pip " "install, 'git pull' + 'pip install -e .' for a dev checkout." ) return 1 spec = spec_file.read_text(encoding="utf-8").strip() if not spec: render_error(f"update: {spec_file} is empty -- re-run install.sh to repair.") return 1 render_note(f"Updating MatMelt from '{spec}' ...") proc = subprocess.run([sys.executable, "-m", "pip", "install", "--upgrade", spec]) if proc.returncode != 0: render_error(f"update: pip exited {proc.returncode} -- see its output above.") return 1 # The new version, from a fresh interpreter -- this process imported the # old code and cannot report it truthfully itself. probe = subprocess.run( [sys.executable, "-c", "import matmelt; print(matmelt.__version__)"], capture_output=True, text=True, ) version = probe.stdout.strip() if probe.returncode == 0 and probe.stdout.strip() else "unknown" render_note(f"MatMelt updated (version {version}).") return 0