From 6a8a3b50bc3f0782892047f1c3f88aca09481d03 Mon Sep 17 00:00:00 2001 From: Eugene Brodsky Date: Mon, 5 Feb 2024 18:58:55 -0500 Subject: [PATCH] feat(installer): add an interactive version chooser --- installer/lib/installer.py | 50 +++++++++++++++++++------------------- installer/lib/messages.py | 29 +++++++++++++++++++++- 2 files changed, 53 insertions(+), 26 deletions(-) diff --git a/installer/lib/installer.py b/installer/lib/installer.py index 8fa73f6d9d..e46397ad5f 100644 --- a/installer/lib/installer.py +++ b/installer/lib/installer.py @@ -34,6 +34,7 @@ class Installer: print("A virtual environment is already activated. Please 'deactivate' before installation.") sys.exit(-1) self.bootstrap() + self.available_releases = get_github_releases() def mktemp_venv(self) -> TemporaryDirectory: """ @@ -105,7 +106,7 @@ class Installer: return venv_dir def install( - self, root: str = "~/invokeai", version: str = "latest", yes_to_all=False, find_links: Optional[Path] = None + self, version=None, root: str = "~/invokeai", yes_to_all=False, find_links: Optional[Path] = None ) -> None: """ Install the InvokeAI application into the given runtime path @@ -122,7 +123,9 @@ class Installer: import messages - messages.welcome() + messages.welcome(self.available_releases) + + version = messages.choose_version(self.available_releases) auto_dest = Path(os.environ.get("INVOKEAI_ROOT", root)).expanduser().resolve() destination = auto_dest if yes_to_all else messages.dest_path(root) @@ -157,7 +160,7 @@ class InvokeAiInstance: A single runtime directory *may* be shared by multiple virtual environments, though this isn't currently tested or supported. """ - def __init__(self, runtime: Path, venv: Path, version: str) -> None: + def __init__(self, runtime: Path, venv: Path, version: str = "stable") -> None: self.runtime = runtime self.venv = venv self.pip = get_pip_from_venv(venv) @@ -179,21 +182,7 @@ class InvokeAiInstance: def install(self, extra_index_url=None, optional_modules=None, find_links=None): """ - Install this instance, including dependencies and the app itself - - :param extra_index_url: the "--extra-index-url ..." line for pip to look in extra indexes. - :type extra_index_url: str - """ - - import messages - - messages.simple_banner("Installing the InvokeAI Application :art:") - self.install_app(extra_index_url, optional_modules, find_links) - - def install_app(self, extra_index_url=None, optional_modules=None, find_links=None): - """ - Install the application with pip. - Supports installation from PyPi or from a local source directory. + Install the package from PyPi. :param extra_index_url: the "--extra-index-url ..." line for pip to look in extra indexes. :type extra_index_url: str @@ -205,15 +194,26 @@ class InvokeAiInstance: :type find_links: Path """ - ## this only applies to pypi installs; TODO actually use this - if self.version == "pre": + import messages + + # not currently used, but may be useful for "install most recent version" option + if self.version == "prerelease": version = None - pre = "--pre" + pre_flag = "--pre" + elif self.version == "stable": + version = None + pre_flag = None else: version = self.version - pre = None + pre_flag = None - src = f"invokeai=={version}" if version is not None else "invokeai" + src = "invokeai" + if optional_modules: + src += optional_modules + if version: + src += f"=={version}" + + messages.simple_banner("Installing the InvokeAI Application :art:") from plumbum import FG, ProcessExecutionError, local # type: ignore @@ -225,12 +225,12 @@ class InvokeAiInstance: "--require-virtualenv", "--force-reinstall", "--use-pep517", - str(src) + (optional_modules if optional_modules else ""), + str(src), "--find-links" if find_links is not None else None, find_links, "--extra-index-url" if extra_index_url is not None else None, extra_index_url, - pre, + pre_flag, ] try: diff --git a/installer/lib/messages.py b/installer/lib/messages.py index a0b3c72cb2..3a16169b92 100644 --- a/installer/lib/messages.py +++ b/installer/lib/messages.py @@ -9,7 +9,7 @@ from enum import Enum from pathlib import Path from prompt_toolkit import HTML, prompt -from prompt_toolkit.completion import PathCompleter +from prompt_toolkit.completion import FuzzyWordCompleter, PathCompleter from prompt_toolkit.validation import Validator from rich import box, print from rich.console import Console, Group, group @@ -62,6 +62,33 @@ def welcome(): console.line() +def choose_version(available_releases: tuple | None = None) -> str: + """ + Prompt the user to choose an Invoke version to install + """ + + # short circuit if we couldn't get a version list + # still try to install the latest stable version + if available_releases is None: + return "stable" + + console.print(":grey_question: [orange3]Please choose an Invoke version to install.") + + choices = available_releases[0] + available_releases[1] + + response = prompt( + message=f" to install the recommended release ({choices[0]}). or type to pick a version: ", + complete_while_typing=True, + completer=FuzzyWordCompleter(choices), + ) + + console.print(f" Version {choices[0]} will be installed.") + console.line() + + return "stable" if response == "" else response + + return response + def confirm_install(dest: Path) -> bool: if dest.exists(): print(f":exclamation: Directory {dest} already exists :exclamation:")