PY-78749 Packages: refactor install(String) from PyPM

Signed-off-by: Nikita.Ashihmin <nikita.ashihmin@jetbrains.com>

GitOrigin-RevId: 7d537581c6190d7a434d162854ac5db739909c59
This commit is contained in:
Nikita.Ashihmin
2025-07-24 11:34:37 +00:00
committed by intellij-monorepo-bot
parent 05c98fc8d6
commit 50d6856ce5
10 changed files with 28 additions and 153 deletions
@@ -3,9 +3,7 @@ package com.jetbrains.python.packaging;
import com.intellij.execution.ExecutionException;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.util.Key;
import com.intellij.util.messages.Topic;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
@@ -18,7 +16,6 @@ import java.util.List;
*/
@Deprecated(forRemoval = true)
public abstract class PyPackageManager implements Disposable {
public static final Key<Boolean> RUNNING_PACKAGING_TASKS = Key.create("PyPackageRequirementsInspection.RunningPackagingTasks");
public static final String USE_USER_SITE = "--user";
@Topic.AppLevel
@@ -62,8 +59,6 @@ public abstract class PyPackageManager implements Disposable {
public abstract boolean hasManagement() throws ExecutionException;
public abstract void install(@NotNull String requirementString) throws ExecutionException;
public abstract void install(@Nullable List<PyRequirement> requirements, @NotNull List<String> extraArgs) throws ExecutionException;
public abstract void uninstall(@NotNull List<PyPackage> packages) throws ExecutionException;
@@ -76,8 +71,6 @@ public abstract class PyPackageManager implements Disposable {
public abstract @NotNull List<PyPackage> refreshAndGetPackages(boolean alwaysRefresh) throws ExecutionException;
public abstract @Nullable List<PyRequirement> getRequirements(@NotNull Module module);
public interface Listener {
void packagesRefreshed(@NotNull Sdk sdk);
}
@@ -2,35 +2,20 @@
package com.jetbrains.python.newProject;
import com.intellij.execution.ExecutionException;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.util.Pair;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.PyPsiPackageUtil;
import com.jetbrains.python.packaging.PyPackage;
import com.jetbrains.python.packaging.PyPackageManager;
import com.jetbrains.python.packaging.PyPackageUtil;
import com.jetbrains.python.packaging.PyPackagesNotificationPanel;
import com.jetbrains.python.packaging.ui.PyPackageManagementService;
import com.jetbrains.python.sdk.PreferredSdkComparator;
import com.jetbrains.python.sdk.PySdkExtKt;
import com.jetbrains.python.sdk.PythonSdkType;
import com.jetbrains.python.sdk.PythonSdkUtil;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
/**
@@ -43,20 +28,6 @@ import java.util.concurrent.Future;
public final class DeprecatedUtils {
private DeprecatedUtils() {
}
/**
* @param sdkAndException if you have SDK and execution exception provide them here (both must not be null).
*/
private static void reportPackageInstallationFailure(final @NotNull String frameworkName,
final @Nullable Pair<Sdk, ExecutionException> sdkAndException) {
final PyPackageManagementService.PyPackageInstallationErrorDescription errorDescription =
getErrorDescription(sdkAndException, frameworkName);
final Application app = ApplicationManager.getApplication();
app.invokeLater(() -> {
PyPackagesNotificationPanel.showPackageInstallationError(PyBundle.message("python.new.project.install.failed.title", frameworkName),
errorDescription);
});
}
private static @NotNull PyPackageManagementService.PyPackageInstallationErrorDescription getErrorDescription(final @Nullable Pair<Sdk, ExecutionException> sdkAndException,
@NotNull String packageName) {
@@ -80,90 +51,6 @@ public final class DeprecatedUtils {
//TODO: Support for plugin also
/**
* Installs framework and runs callback on success.
* Installation runs in modal dialog and callback is posted to AWT thread.
* <p>
* If "forceInstallFramework" is passed then installs framework in any case.
* If SDK is remote then checks if it has interpreter and installs if missing
*
* @param frameworkName user-readable framework name (i.e. "Django")
* @param requirement name of requirement to install (i.e. "django")
* @param forceInstallFramework pass true if you are sure required framework is missing
* @param callback to be called after installation (or instead of is framework is installed) on AWT thread
* @return future to be used instead of callback.
*/
public static @NotNull Future<Void> installFrameworkIfNeeded(final @NotNull Project project,
final @NotNull String frameworkName,
final @NotNull String requirement,
final @NotNull Sdk sdk,
final boolean forceInstallFramework,
final @Nullable Runnable callback) {
var future = new CompletableFuture<Void>();
// For remote SDK we are not sure if framework exists or not, so we'll check it anyway
if (forceInstallFramework || PythonSdkUtil.isRemote(sdk)) {
ProgressManager.getInstance()
.run(new Task.Modal(project, PyBundle.message("python.install.framework.ensure.installed", frameworkName), false) {
@Override
public void run(final @NotNull ProgressIndicator indicator) {
installPackages(frameworkName, forceInstallFramework, indicator, requirement, sdk);
}
@Override
public void onThrowable(@NotNull Throwable error) {
future.completeExceptionally(error);
}
@Override
public void onSuccess() {
future.complete(null);
// Installed / checked successfully, call callback on AWT
if (callback != null) {
callback.run();
}
}
});
}
else {
future.complete(null);
// No need to install, but still need to call callback on AWT
if (callback != null) {
assert SwingUtilities.isEventDispatchThread();
callback.run();
}
}
return future;
}
private static void installPackages(final @NotNull String frameworkName,
boolean forceInstallFramework,
@NotNull ProgressIndicator indicator,
final @NotNull String requirement,
final @NotNull Sdk sdk) {
final PyPackageManager packageManager = PyPackageManager.getInstance(sdk);
boolean installed = false;
if (!forceInstallFramework) {
// First check if we need to do it
indicator.setText(PyBundle.message("python.install.framework.checking.is.installed", frameworkName));
final List<PyPackage> packages = PyPackageUtil.refreshAndGetPackagesModally(sdk);
installed = PyPsiPackageUtil.findPackage(packages, requirement) != null;
}
if (!installed) {
indicator.setText(PyBundle.message("python.install.framework.installing", frameworkName));
try {
packageManager.install(requirement);
packageManager.refresh();
}
catch (final ExecutionException e) {
reportPackageInstallationFailure(requirement, Pair.create(sdk, e));
}
}
}
// TODO: Migrate to interpreter service
public static @NotNull List<Sdk> getValidPythonSdks(@NotNull List<Sdk> existingSdks) {
return StreamEx
@@ -3,7 +3,6 @@ package com.jetbrains.python.packaging
import com.intellij.execution.ExecutionException
import com.intellij.openapi.diagnostic.thisLogger
import com.intellij.openapi.module.Module
import com.intellij.openapi.progress.runBlockingMaybeCancellable
import com.intellij.openapi.project.ProjectManager
import com.intellij.openapi.project.getOpenedProjects
@@ -68,9 +67,5 @@ internal open class PyPackageManagerBridge(sdk: Sdk) : PyTargetEnvironmentPackag
return packageManagerUI.manager.listInstalledPackagesSnapshot().map { PyPackage(it.name, it.version) }
}
override fun getRequirements(module: Module): List<PyRequirement>? {
return packageManager.getDependencyManager()?.getDependencies()
}
private fun guessProject() = getOpenedProjects().firstOrNull() ?: ProjectManager.getInstance().defaultProject
}
@@ -41,7 +41,7 @@ import static com.jetbrains.python.sdk.PySdkExtKt.showSdkExecutionException;
* @deprecated This class and all its inheritors are deprecated. Everything should work via {@link PyTargetEnvironmentPackageManager}
*/
@Deprecated(forRemoval = true)
public class PyPackageManagerImpl extends PyPackageManagerImplBase {
public abstract class PyPackageManagerImpl extends PyPackageManagerImplBase {
private static final Logger LOG = Logger.getInstance(PyPackageManagerImpl.class);
@Override
@@ -61,11 +61,6 @@ public class PyPackageManagerImpl extends PyPackageManagerImplBase {
super(sdk);
}
@Override
public void install(@NotNull String requirementString) throws ExecutionException {
install(Collections.singletonList(PyRequirementParser.fromLine(requirementString)), Collections.emptyList());
}
@Override
public void install(@Nullable List<PyRequirement> requirements, @NotNull List<String> extraArgs) throws ExecutionException {
install(requirements, extraArgs, null);
@@ -5,7 +5,6 @@ import com.intellij.execution.ExecutionException;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.roots.OrderRootType;
import com.intellij.openapi.util.text.StringUtil;
@@ -31,7 +30,10 @@ import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Pattern;
@@ -166,13 +168,6 @@ public abstract class PyPackageManagerImplBase extends PyPackageManager {
return PythonSdkType.getLanguageLevelForSdk(sdk);
}
@Override
public @Nullable List<PyRequirement> getRequirements(@NotNull Module module) {
return Optional
.ofNullable(PyPackageUtil.getRequirementsFromTxt(module))
.orElseGet(() -> PyPackageUtil.findSetupPyRequires(module));
}
@Override
public @NotNull List<PyPackage> refreshAndGetPackages(boolean alwaysRefresh) throws ExecutionException {
final List<PyPackage> currentPackages = myPackagesCache;
@@ -69,11 +69,6 @@ public abstract class PyTargetEnvironmentPackageManager extends PyPackageManager
super(sdk);
}
@Override
public void install(@NotNull String requirementString) throws ExecutionException {
install(Collections.singletonList(PyRequirementParser.fromLine(requirementString)), Collections.emptyList());
}
@Override
public void install(@Nullable List<PyRequirement> requirements, @NotNull List<String> extraArgs) throws ExecutionException {
install(requirements, extraArgs, null);
@@ -46,7 +46,7 @@ class PythonPackageManagerUI(val manager: PythonPackageManager, val sink: ErrorS
/**
* @return List of all installed packages or null if the operation was failed.
*/
suspend fun installPackagesBackground(
suspend fun installPackagesRequestBackground(
installRequest: PythonPackageInstallRequest,
options: List<String> = emptyList(),
): List<PythonPackage>? {
@@ -68,8 +68,23 @@ suspend fun PythonPackageManagerUI.installPyRequirementsBackground(
val specifications = packages.mapNotNull {
manager.findPackageSpecificationWithVersionSpec(it.name, it.versionSpecs.firstOrNull())
}
return installPackagesBackground(PythonPackageInstallRequest.ByRepositoryPythonPackageSpecifications(specifications),
options = options)
return installPackagesRequestBackground(PythonPackageInstallRequest.ByRepositoryPythonPackageSpecifications(specifications),
options = options)
}
@ApiStatus.Internal
suspend fun PythonPackageManagerUI.installPackagesBackground(
packages: List<String>,
options: List<String> = emptyList(),
): List<PythonPackage>? {
//Wait here to load spec
manager.waitForInit()
val specifications = packages.mapNotNull {
manager.findPackageSpecificationWithVersionSpec(it, null)
}
return installPackagesRequestBackground(PythonPackageInstallRequest.ByRepositoryPythonPackageSpecifications(specifications),
options = options)
}
/**
@@ -51,7 +51,7 @@ class PyPackagingToolWindowService(val project: Project, val serviceScope: Corou
internal var currentSdk: Sdk? = null
private lateinit var managerUI: PythonPackageManagerUI
private val manager: PythonPackageManager?
private val manager: PythonPackageManager
get() = managerUI.manager
@@ -128,7 +128,7 @@ class PyPackagingToolWindowService(val project: Project, val serviceScope: Corou
suspend fun installPackage(installRequest: PythonPackageInstallRequest, options: List<String> = emptyList()) {
PythonPackagesToolwindowStatisticsCollector.installPackageEvent.log(project)
managerUI.installPackagesBackground(installRequest, options)?.let {
managerUI.installPackagesRequestBackground(installRequest, options)?.let {
handleActionCompleted(message("python.packaging.notification.installed", installRequest.title))
}
}
@@ -136,7 +136,7 @@ class PyPackagingToolWindowService(val project: Project, val serviceScope: Corou
suspend fun installPackage(pkg: PythonPackage, options: List<String> = emptyList()) {
val installRequest = manager?.findPackageSpecification(pkg.name, pkg.version)?.toInstallRequest() ?: return
PythonPackagesToolwindowStatisticsCollector.installPackageEvent.log(project)
managerUI.installPackagesBackground(installRequest, options)?.let {
managerUI.installPackagesRequestBackground(installRequest, options)?.let {
handleActionCompleted(message("python.packaging.notification.installed", installRequest.title))
}
}
@@ -11,7 +11,6 @@ import com.intellij.openapi.project.Project
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread
import com.jetbrains.python.extensions.getSdk
import com.jetbrains.python.packaging.PyPIPackageCache
import com.jetbrains.python.packaging.PyPackageManager
import com.jetbrains.python.packaging.management.PythonPackageManager
import com.jetbrains.python.packaging.normalizePackageName
import com.jetbrains.python.sdk.PythonSdkAdditionalData
@@ -49,7 +48,8 @@ internal class PyPackageVersionUsagesCollector : ProjectUsagesCollector() {
val sdk = module.getSdk() ?: continue
if (!PythonSdkUtil.isPythonSdk(sdk)) continue
val usageData = getPythonSpecificInfo(sdk)
PyPackageManager.getInstance(sdk).getRequirements(module).orEmpty()
val requirements = PythonPackageManager.forSdk(project, sdk).getDependencyManager()?.getDependencies().orEmpty()
requirements
.filter { pypiPackages.containsPackage(it.name) }
.forEach { req ->
ProgressManager.checkCanceled()