PY-26492 Install, uninstall, show list of packages via pipenv

This commit is contained in:
Andrey Vlasovskikh
2018-06-15 21:24:41 +03:00
parent d389b8539d
commit 16980455ae
3 changed files with 139 additions and 2 deletions
@@ -21,6 +21,7 @@ import com.intellij.openapi.vfs.VirtualFile;
import com.jetbrains.python.packaging.ui.PyCondaManagementService;
import com.jetbrains.python.packaging.ui.PyPackageManagementService;
import com.jetbrains.python.sdk.PythonSdkType;
import com.jetbrains.python.sdk.flavors.PipenvKt;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
@@ -30,17 +31,20 @@ import java.util.Map;
* @author yole
*/
public class PyPackageManagersImpl extends PyPackageManagers {
private final Map<String, PyPackageManagerImpl> myInstances = new HashMap<>();
private final Map<String, PyPackageManager> myInstances = new HashMap<>();
@NotNull
public synchronized PyPackageManager forSdk(@NotNull final Sdk sdk) {
final String key = PythonSdkType.getSdkKey(sdk);
PyPackageManagerImpl manager = myInstances.get(key);
PyPackageManager manager = myInstances.get(key);
if (manager == null) {
final VirtualFile homeDirectory = sdk.getHomeDirectory();
if (PythonSdkType.isRemote(sdk)) {
manager = new PyRemotePackageManagerImpl(sdk);
}
else if (PipenvKt.isPipEnv(sdk)) {
manager = new PyPipEnvPackageManager(sdk);
}
else if (PyCondaPackageManagerImpl.isConda(sdk) &&
homeDirectory != null &&
PyCondaPackageService.getCondaExecutable(homeDirectory) != null) {
@@ -0,0 +1,123 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.jetbrains.python.packaging
import com.google.gson.Gson
import com.google.gson.JsonSyntaxException
import com.google.gson.annotations.SerializedName
import com.intellij.execution.ExecutionException
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.module.Module
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.roots.OrderRootType
import com.intellij.openapi.vfs.VfsUtil
import com.jetbrains.python.sdk.PythonSdkType
import com.jetbrains.python.sdk.flavors.runPipEnv
/**
* @author vlan
*/
class PyPipEnvPackageManager(val sdk: Sdk) : PyPackageManager() {
@Volatile
private var packages: List<PyPackage>? = null
override fun installManagement() {}
override fun hasManagement() = true
override fun install(requirementString: String) {
install(parseRequirements(requirementString), emptyList())
}
override fun install(requirements: List<PyRequirement>, extraArgs: List<String>) {
val args = listOf("install") +
requirements.flatMap { it.installOptions } +
extraArgs
try {
runPipEnv(sdk, *args.toTypedArray())
}
finally {
refreshAndGetPackages(true)
}
}
override fun uninstall(packages: List<PyPackage>) {
val args = listOf("uninstall") +
packages.map { it.name }
try {
runPipEnv(sdk, *args.toTypedArray())
}
finally {
refreshAndGetPackages(true)
}
}
override fun refresh() {
with(ApplicationManager.getApplication()) {
invokeLater {
runWriteAction {
val files = sdk.rootProvider.getFiles(OrderRootType.CLASSES)
VfsUtil.markDirtyAndRefresh(true, true, true, *files)
}
PythonSdkType.getInstance().setupSdkPaths(sdk)
}
}
}
override fun createVirtualEnv(destinationDir: String, useGlobalSite: Boolean): String {
throw ExecutionException("Creating virtual environments based on Pipenv environments is not supported")
}
override fun getPackages() = packages
override fun refreshAndGetPackages(alwaysRefresh: Boolean): List<PyPackage> {
if (alwaysRefresh || packages == null) {
val output = try {
runPipEnv(sdk, "graph", "--json")
}
catch (e: ExecutionException) {
packages = emptyList()
throw e
}
packages = parsePipEnvGraph(output)
}
return packages ?: emptyList()
}
override fun getRequirements(module: Module): List<PyRequirement>? = null
override fun parseRequirements(text: String): List<PyRequirement> =
PyPackageUtil.fix(PyRequirement.fromText(text))
override fun getDependents(pkg: PyPackage): Set<PyPackage> {
// TODO: Parse the dependency information from `pipenv graph`
return emptySet()
}
companion object {
private data class GraphPackage(@SerializedName("key") var key: String,
@SerializedName("package_name") var packageName: String,
@SerializedName("installed_version") var installedVersion: String,
@SerializedName("required_version") var requiredVersion: String?)
private data class GraphEntry(@SerializedName("package") var pkg: GraphPackage,
@SerializedName("dependencies") var dependencies: List<GraphPackage>)
/**
* Parses the output of `pipenv graph --json` into a list of packages.
*/
private fun parsePipEnvGraph(input: String): List<PyPackage> {
val entries = try {
Gson().fromJson(input, Array<GraphEntry>::class.java)
}
catch (e: JsonSyntaxException) {
// TODO: Log errors
return emptyList()
}
return entries
.asSequence()
.filterNotNull()
.map { PyPackage(it.pkg.packageName, it.pkg.installedVersion, null, emptyList()) }
.toList()
}
}
}
@@ -121,6 +121,16 @@ fun setupPipEnv(projectPath: @SystemDependent String, python: String?, installPa
return runPipEnv(projectPath, "--venv").trim()
}
/**
* Runs the configured pipenv for the specified Pipenv SDK with the associated project path.
*/
fun runPipEnv(sdk: Sdk, vararg args: String): String {
val projectPath = sdk.associatedModulePath ?:
throw PyExecutionException("Cannot find the project associated with this Pipenv environment",
"Pipenv", emptyList(), ProcessOutput())
return runPipEnv(projectPath, *args)
}
/**
* Runs the configured pipenv for the specified project path.
*/