-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(cli): introduce python management commands
- Loading branch information
Showing
17 changed files
with
1,083 additions
and
31 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
from __future__ import annotations | ||
|
||
from typing import TYPE_CHECKING | ||
from typing import ClassVar | ||
|
||
from cleo.helpers import argument | ||
from cleo.helpers import option | ||
from poetry.core.constraints.version.version import Version | ||
from poetry.core.version.exceptions import InvalidVersionError | ||
|
||
from poetry.console.commands.command import Command | ||
from poetry.console.commands.python.remove import PythonRemoveCommand | ||
from poetry.console.exceptions import PoetryRuntimeError | ||
from poetry.utils.env.python.installer import PythonDownloadNotFoundError | ||
from poetry.utils.env.python.installer import PythonInstallationError | ||
from poetry.utils.env.python.installer import PythonInstaller | ||
from poetry.utils.env.python.providers import PoetryPythonPathProvider | ||
|
||
|
||
if TYPE_CHECKING: | ||
from cleo.io.inputs.argument import Argument | ||
from cleo.io.inputs.option import Option | ||
|
||
|
||
class PythonInstallCommand(Command): | ||
name = "python install" | ||
|
||
arguments: ClassVar[list[Argument]] = [ | ||
argument("python", "The python version to install.") | ||
] | ||
|
||
options: ClassVar[list[Option]] = [ | ||
option("clean", "c", "Cleanup installation if check fails.", flag=True), | ||
option( | ||
"free-threaded", "f", "Use free-threaded version if available.", flag=True | ||
), | ||
option( | ||
"implementation", | ||
"i", | ||
"Python implementation to use. (cpython, pypy)", | ||
flag=False, | ||
default="cpython", | ||
), | ||
option( | ||
"reinstall", "r", "Reinstall if installation already exists.", flag=True | ||
), | ||
] | ||
|
||
description = "Install the specified Python version from the Python Standalone Builds project." | ||
|
||
def handle(self) -> int: | ||
request = self.argument("python") | ||
impl = self.option("implementation").lower() | ||
reinstall = self.option("reinstall") | ||
free_threaded = self.option("free-threaded") | ||
|
||
try: | ||
version = Version.parse(request) | ||
except (ValueError, InvalidVersionError): | ||
self.io.write_error_line( | ||
f"<error>Invalid Python version requested <b>{request}</></error>" | ||
) | ||
return 1 | ||
|
||
if free_threaded and version < Version.parse("3.13.0"): | ||
self.io.write_error_line("") | ||
self.io.write_error_line( | ||
"Free threading is not supported for Python versions prior to <c1>3.13.0</>.\n\n" | ||
"See https://docs.python.org/3/howto/free-threading-python.html for more information." | ||
) | ||
self.io.write_error_line("") | ||
return 1 | ||
|
||
installer = PythonInstaller(request, impl, free_threaded) | ||
|
||
try: | ||
if installer.exists() and not reinstall: | ||
self.io.write_line( | ||
"Python version already installed at " | ||
f"<b>{PoetryPythonPathProvider.installation_dir(version, impl)}</>.\n" | ||
) | ||
self.io.write_line( | ||
f"Use <c1>--reinstall</> to install anyway, " | ||
f"or use <c1>poetry python remove {version}</> first." | ||
) | ||
return 1 | ||
except PythonDownloadNotFoundError: | ||
self.io.write_error_line( | ||
"No suitable standalone build found for the requested Python version." | ||
) | ||
return 1 | ||
|
||
request_title = f"<c1>{request}</> (<b>{impl}</>)" | ||
|
||
try: | ||
self.io.write(f"Downloading and installing {request_title} ... ") | ||
installer.install() | ||
except PythonInstallationError as e: | ||
self.io.write("<fg=red>Failed</>\n") | ||
self.io.write_error_line("") | ||
self.io.write_error_line(str(e)) | ||
self.io.write_error_line("") | ||
return 1 | ||
|
||
self.io.write("<fg=green>Done</>\n") | ||
self.io.write(f"Testing {request_title} ... ") | ||
|
||
try: | ||
installer.exists() | ||
except PoetryRuntimeError as e: | ||
self.io.write("<fg=red>Failed</>\n") | ||
|
||
if installer.installation_directory.exists() and self.option("clean"): | ||
PythonRemoveCommand.remove_python_installation(request, impl, self.io) | ||
|
||
raise e | ||
|
||
self.io.write("<fg=green>Done</>\n") | ||
|
||
return 0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
from __future__ import annotations | ||
|
||
from typing import TYPE_CHECKING | ||
from typing import ClassVar | ||
|
||
from cleo.helpers import argument | ||
from cleo.helpers import option | ||
from poetry.core.constraints.version import parse_constraint | ||
|
||
from poetry.config.config import Config | ||
from poetry.console.commands.command import Command | ||
from poetry.utils.env.python import Python | ||
|
||
|
||
if TYPE_CHECKING: | ||
from cleo.io.inputs.argument import Argument | ||
from cleo.io.inputs.option import Option | ||
|
||
from poetry.utils.env.python.manager import PythonInfo | ||
|
||
|
||
class PythonListCommand(Command): | ||
name = "python list" | ||
|
||
arguments: ClassVar[list[Argument]] = [ | ||
argument("version", "Python version to search for.", optional=True) | ||
] | ||
|
||
options: ClassVar[list[Option]] = [ | ||
option( | ||
"all", | ||
"a", | ||
"List all versions, including those available for download.", | ||
flag=True, | ||
), | ||
option( | ||
"implementation", "i", "Python implementation to search for.", flag=False | ||
), | ||
option("managed", "m", "List only Poetry managed Python versions.", flag=True), | ||
] | ||
|
||
description = "Shows Python versions available for this environment." | ||
|
||
def handle(self) -> int: | ||
rows: list[PythonInfo] = [] | ||
constraint = None | ||
|
||
if self.argument("version"): | ||
constraint = parse_constraint(f"~{self.argument('version')}") | ||
|
||
for info in Python.find_all_versions(constraint=constraint): | ||
rows.append(info) | ||
|
||
if self.option("all"): | ||
for info in Python.find_downloadable_versions(constraint=constraint): | ||
rows.append(info) | ||
|
||
rows.sort( | ||
key=lambda x: (x.major, x.minor, x.patch, x.implementation), reverse=True | ||
) | ||
|
||
table = self.table(style="compact") | ||
table.set_headers( | ||
[ | ||
"<fg=magenta;options=bold>Version</>", | ||
"<fg=magenta;options=bold>Implementation</>", | ||
"<fg=magenta;options=bold>Manager</>", | ||
"<fg=magenta;options=bold>Path</>", | ||
] | ||
) | ||
|
||
implementations = {"cpython": "CPython", "pypy": "PyPy"} | ||
python_installation_path = Config().python_installation_dir | ||
|
||
row_count = 0 | ||
|
||
for pv in rows: | ||
version = f"{pv.major}.{pv.minor}.{pv.patch}" | ||
implementation = implementations.get( | ||
pv.implementation.lower(), pv.implementation | ||
) | ||
is_poetry_managed = ( | ||
pv.executable is None | ||
or pv.executable.resolve().is_relative_to(python_installation_path) | ||
) | ||
|
||
if self.option("managed") and not is_poetry_managed: | ||
continue | ||
|
||
manager = ( | ||
"<fg=blue>Poetry</>" if is_poetry_managed else "<fg=yellow>System</>" | ||
) | ||
path = ( | ||
f"<fg=green>{pv.executable.as_posix()}</>" | ||
if pv.executable | ||
else "Available for download" | ||
) | ||
|
||
table.add_row( | ||
[ | ||
f"<c1>{version}</>", | ||
f"<b>{implementation}</>", | ||
f"{manager}", | ||
f"{path}", | ||
] | ||
) | ||
row_count += 1 | ||
|
||
if row_count > 0: | ||
table.render() | ||
else: | ||
self.io.write_line("No Python installations found.") | ||
|
||
return 0 |
Oops, something went wrong.