mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-03-22 06:50:54 +07:00
PY-59838 Refactor Poetry package management
Associate poetry files ("poetry.lock", "pyproject.toml") with the Python Packages toolwindow.
Separate UI error handling from package management logic.
Add tests to check the installation and removal of packages using poetry and "pyproject.toml" modification.
Merge-request: IJ-MR-146002
Merged-by: Egor Eliseev <Egor.Eliseev@jetbrains.com>
(cherry picked from commit 2ab0816f10c970f738d6d931dc123481030cad38)
Merge-request: IJ-MR-148435
Merged-by: Egor Eliseev <Egor.Eliseev@jetbrains.com>
GitOrigin-RevId: be957c5343b73264c78134f156ad0e4034b912f9
This commit is contained in:
committed by
intellij-monorepo-bot
parent
865f1aa38e
commit
dbee69ed0b
@@ -14,8 +14,8 @@ import kotlinx.coroutines.withContext
|
||||
|
||||
class PyV3EmptyProjectSettings(var generateWelcomeScript: Boolean = false) : PyV3ProjectTypeSpecificSettings {
|
||||
|
||||
override suspend fun generateProject(module: Module, baseDir: VirtualFile, sdk: Sdk) {
|
||||
if (!generateWelcomeScript) return
|
||||
override suspend fun generateProject(module: Module, baseDir: VirtualFile, sdk: Sdk): Result<Boolean> {
|
||||
if (!generateWelcomeScript) return Result.success(false)
|
||||
val file = writeAction {
|
||||
PyWelcome.prepareFile(module.project, baseDir)
|
||||
}
|
||||
@@ -24,6 +24,8 @@ class PyV3EmptyProjectSettings(var generateWelcomeScript: Boolean = false) : PyV
|
||||
file.navigate(true)
|
||||
}
|
||||
}
|
||||
|
||||
return Result.success(true)
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
|
||||
@@ -851,6 +851,7 @@ The Python plug-in provides smart editing for Python scripts. The feature set of
|
||||
|
||||
<pySdkProvider implementation="com.jetbrains.python.sdk.poetry.PoetrySdkProvider"/>
|
||||
<packageManagerProvider implementation="com.jetbrains.python.sdk.poetry.PyPoetryPackageManagerProvider"/>
|
||||
<pythonPackageManagerProvider implementation="com.jetbrains.python.sdk.poetry.PoetryPackageManagerProvider"/>
|
||||
|
||||
|
||||
<pythonPackageManagerProvider implementation="com.jetbrains.python.packaging.pip.PipPackageManagerProvider" order="last"/>
|
||||
|
||||
@@ -56,7 +56,7 @@ abstract class PyV3ProjectBaseGenerator<TYPE_SPECIFIC_SETTINGS : PyV3ProjectType
|
||||
// Either base settings (which create venv) might generate some or type specific settings (like Django) may.
|
||||
// So we expand it right after SDK generation, but if there are no files yet, we do it again after project generation
|
||||
ensureProjectViewExpanded(project)
|
||||
typeSpecificSettings.generateProject(module, baseDir, sdk)
|
||||
typeSpecificSettings.generateProject(module, baseDir, sdk).onFailure { errorSink.emit(it.localizedMessage) }
|
||||
ensureProjectViewExpanded(project)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,5 +20,5 @@ fun interface PyV3ProjectTypeSpecificSettings {
|
||||
* Generate project-specific things in [baseDir].
|
||||
* You might need to [installPackages] on [sdk]
|
||||
*/
|
||||
suspend fun generateProject(module: Module, baseDir: VirtualFile, sdk: Sdk)
|
||||
suspend fun generateProject(module: Module, baseDir: VirtualFile, sdk: Sdk): Result<Boolean>
|
||||
}
|
||||
@@ -5,20 +5,19 @@ import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.jetbrains.python.packaging.common.PythonSimplePackageSpecification
|
||||
import com.jetbrains.python.packaging.management.PythonPackageManager
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.supervisorScope
|
||||
|
||||
/**
|
||||
* Install [packages] to [sdk]
|
||||
*/
|
||||
suspend fun installPackages(project: Project, sdk: Sdk, vararg packages: String) {
|
||||
suspend fun installPackages(project: Project, sdk: Sdk, vararg packages: String): Result<Boolean> {
|
||||
val packageManager = PythonPackageManager.forSdk(project, sdk)
|
||||
supervisorScope { // Not install other packages if one failed
|
||||
return supervisorScope { // Not install other packages if one failed
|
||||
for (packageName in packages) {
|
||||
launch {
|
||||
packageManager.installPackage(PythonSimplePackageSpecification(packageName, null, null), emptyList()).getOrThrow()
|
||||
}
|
||||
val packageSpecification = PythonSimplePackageSpecification(packageName, null, null)
|
||||
packageManager.installPackage(packageSpecification, emptyList<String>()).onFailure { return@supervisorScope Result.failure(it) }
|
||||
}
|
||||
return@supervisorScope Result.success(true)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import com.intellij.util.ui.JBUI
|
||||
import com.jetbrains.python.PyBundle
|
||||
import com.jetbrains.python.inspections.PyPackageRequirementsInspection.InstallPackageQuickFix
|
||||
import com.jetbrains.python.packaging.common.PythonPackage
|
||||
import com.jetbrains.python.packaging.common.runPackagingOperationOrShowErrorDialog
|
||||
import com.jetbrains.python.packaging.management.PythonPackageManager
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
@@ -143,7 +143,11 @@ class PythonPackageManagementServiceBridge(project: Project,sdk: Sdk) : PyPackag
|
||||
manager
|
||||
.installedPackages
|
||||
.filter { it.name.lowercase() in namesToDelete }
|
||||
.forEach { manager.uninstallPackage(it) }
|
||||
.forEach {
|
||||
runPackagingOperationOrShowErrorDialog(sdk, PyBundle.message("python.packaging.operation.failed.title")) {
|
||||
manager.uninstallPackage(it)
|
||||
}
|
||||
}
|
||||
|
||||
listener.operationFinished(namesToDelete.first(), null)
|
||||
}
|
||||
|
||||
@@ -9,9 +9,26 @@ import com.jetbrains.python.packaging.requirement.PyRequirementRelation
|
||||
import org.jetbrains.annotations.Nls
|
||||
|
||||
open class PythonPackage(val name: String, val version: String, val isEditableMode: Boolean) {
|
||||
companion object {
|
||||
private const val HASH_MULTIPLIER = 31
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "PythonPackage(name='$name', version='$version')"
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is PythonPackage) return false
|
||||
return name == other.name && version == other.version && isEditableMode == other.isEditableMode
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = name.hashCode()
|
||||
result = HASH_MULTIPLIER * result + version.hashCode()
|
||||
result = HASH_MULTIPLIER * result + isEditableMode.hashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
interface PythonPackageDetails {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.jetbrains.python.packaging.conda
|
||||
|
||||
import com.intellij.execution.ExecutionException
|
||||
import com.intellij.execution.process.CapturingProcessHandler
|
||||
import com.intellij.execution.process.ProcessOutput
|
||||
import com.intellij.execution.target.TargetProgressIndicator
|
||||
import com.intellij.execution.target.TargetedCommandLineBuilder
|
||||
import com.intellij.execution.target.local.LocalTargetEnvironmentRequest
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.diagnostic.thisLogger
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
@@ -17,7 +17,6 @@ import com.jetbrains.python.PyBundle.message
|
||||
import com.jetbrains.python.packaging.PyExecutionException
|
||||
import com.jetbrains.python.packaging.common.PythonPackage
|
||||
import com.jetbrains.python.packaging.common.PythonPackageSpecification
|
||||
import com.jetbrains.python.packaging.common.runPackagingOperationOrShowErrorDialog
|
||||
import com.jetbrains.python.packaging.pip.PipBasedPackageManager
|
||||
import com.jetbrains.python.sdk.flavors.conda.PyCondaFlavorData
|
||||
import com.jetbrains.python.sdk.getOrCreateAdditionalData
|
||||
@@ -29,64 +28,56 @@ import org.jetbrains.annotations.Nls
|
||||
|
||||
@ApiStatus.Experimental
|
||||
class CondaPackageManager(project: Project, sdk: Sdk) : PipBasedPackageManager(project, sdk) {
|
||||
|
||||
@Volatile
|
||||
override var installedPackages: List<CondaPackage> = emptyList()
|
||||
private set
|
||||
|
||||
override val repositoryManager: CondaRepositoryManger = CondaRepositoryManger(project, sdk)
|
||||
|
||||
override suspend fun installPackage(specification: PythonPackageSpecification, options: List<String>): Result<List<PythonPackage>> {
|
||||
return if (specification is CondaPackageSpecification) {
|
||||
runPackagingOperationOrShowErrorDialog(sdk, message("python.new.project.install.failed.title", specification.name), specification.name) {
|
||||
runConda("install", specification.buildInstallationString() + "-y" + options, message("conda.packaging.install.progress", specification.name))
|
||||
refreshPaths()
|
||||
reloadPackages()
|
||||
override suspend fun installPackageCommand(specification: PythonPackageSpecification, options: List<String>): Result<String> =
|
||||
if (specification is CondaPackageSpecification) {
|
||||
try {
|
||||
Result.success(runConda("install", specification.buildInstallationString() + "-y" + options, message("conda.packaging.install.progress", specification.name)))
|
||||
}
|
||||
catch (ex: ExecutionException) {
|
||||
Result.failure(ex)
|
||||
}
|
||||
}
|
||||
else return super.installPackage(specification, emptyList<String>())
|
||||
}
|
||||
else {
|
||||
super.installPackageCommand(specification, options)
|
||||
}
|
||||
|
||||
override suspend fun uninstallPackage(pkg: PythonPackage): Result<List<PythonPackage>> {
|
||||
return if (pkg is CondaPackage && !pkg.installedWithPip) {
|
||||
runPackagingOperationOrShowErrorDialog(sdk, message("python.packaging.operation.failed.title")) {
|
||||
runConda("uninstall", listOf(pkg.name, "-y"), message("conda.packaging.uninstall.progress", pkg.name))
|
||||
refreshPaths()
|
||||
reloadPackages()
|
||||
override suspend fun updatePackageCommand(specification: PythonPackageSpecification): Result<String> =
|
||||
if (specification is CondaPackageSpecification) {
|
||||
try {
|
||||
Result.success(runConda("update", listOf(specification.name, "-y"), message("conda.packaging.update.progress", specification.name)))
|
||||
}
|
||||
catch (ex: ExecutionException) {
|
||||
Result.failure(ex)
|
||||
}
|
||||
}
|
||||
else super.uninstallPackage(pkg)
|
||||
}
|
||||
else {
|
||||
super.updatePackageCommand(specification)
|
||||
}
|
||||
|
||||
override suspend fun updatePackage(specification: PythonPackageSpecification): Result<List<PythonPackage>> {
|
||||
return if (specification is CondaPackageSpecification) {
|
||||
runPackagingOperationOrShowErrorDialog(sdk, message("python.packaging.notification.update.failed", specification.name), specification.name) {
|
||||
runConda("update", listOf(specification.name, "-y"), message("conda.packaging.update.progress", specification.name))
|
||||
refreshPaths()
|
||||
reloadPackages()
|
||||
|
||||
override suspend fun uninstallPackageCommand(pkg: PythonPackage): Result<String> =
|
||||
if (pkg is CondaPackage && !pkg.installedWithPip) {
|
||||
try {
|
||||
Result.success(runConda("uninstall", listOf(pkg.name, "-y"), message("conda.packaging.uninstall.progress", pkg.name)))
|
||||
}
|
||||
catch (ex: ExecutionException) {
|
||||
Result.failure(ex)
|
||||
}
|
||||
}
|
||||
else super.updatePackage(specification)
|
||||
}
|
||||
|
||||
override suspend fun reloadPackages(): Result<List<PythonPackage>> {
|
||||
return withContext(Dispatchers.IO) {
|
||||
val result = runPackagingOperationOrShowErrorDialog(sdk, message("python.packaging.operation.failed.title")) {
|
||||
val output = runConda("list", emptyList(), message("conda.packaging.list.progress"))
|
||||
Result.success(parseCondaPackageList(output))
|
||||
}
|
||||
if (result.isFailure) return@withContext result
|
||||
|
||||
installedPackages = result.getOrThrow()
|
||||
|
||||
ApplicationManager.getApplication()
|
||||
.messageBus
|
||||
.syncPublisher(PACKAGE_MANAGEMENT_TOPIC)
|
||||
.packagesChanged(sdk)
|
||||
|
||||
result
|
||||
else {
|
||||
super.uninstallPackageCommand(pkg)
|
||||
}
|
||||
|
||||
override suspend fun reloadPackagesCommand(): Result<List<PythonPackage>> =
|
||||
try {
|
||||
val output =runConda("list", emptyList(), message("conda.packaging.list.progress"))
|
||||
Result.success(parseCondaPackageList(output))
|
||||
}
|
||||
catch (ex: ExecutionException) {
|
||||
Result.failure(ex)
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseCondaPackageList(text: String): List<CondaPackage> {
|
||||
return text.lineSequence()
|
||||
|
||||
@@ -17,17 +17,36 @@ import org.jetbrains.annotations.ApiStatus
|
||||
|
||||
@ApiStatus.Experimental
|
||||
abstract class PythonPackageManager(val project: Project, val sdk: Sdk) {
|
||||
abstract val installedPackages: List<PythonPackage>
|
||||
abstract var installedPackages: List<PythonPackage>
|
||||
|
||||
abstract val repositoryManager: PythonRepositoryManager
|
||||
|
||||
abstract suspend fun installPackage(specification: PythonPackageSpecification, options: List<String>): Result<List<PythonPackage>>
|
||||
suspend fun installPackage(specification: PythonPackageSpecification, options: List<String>): Result<List<PythonPackage>> {
|
||||
installPackageCommand(specification, options).onFailure { return Result.failure(it) }
|
||||
refreshPaths()
|
||||
return reloadPackages()
|
||||
}
|
||||
|
||||
abstract suspend fun updatePackage(specification: PythonPackageSpecification): Result<List<PythonPackage>>
|
||||
abstract suspend fun uninstallPackage(pkg: PythonPackage): Result<List<PythonPackage>>
|
||||
suspend fun updatePackage(specification: PythonPackageSpecification): Result<List<PythonPackage>> {
|
||||
updatePackageCommand(specification).onFailure { return Result.failure(it) }
|
||||
refreshPaths()
|
||||
return reloadPackages()
|
||||
}
|
||||
|
||||
suspend fun uninstallPackage(pkg: PythonPackage): Result<List<PythonPackage>> {
|
||||
uninstallPackageCommand(pkg).onFailure { return Result.failure(it) }
|
||||
refreshPaths()
|
||||
return reloadPackages()
|
||||
}
|
||||
|
||||
abstract suspend fun reloadPackages(): Result<List<PythonPackage>>
|
||||
|
||||
|
||||
protected abstract suspend fun installPackageCommand(specification: PythonPackageSpecification, options: List<String>): Result<String>
|
||||
protected abstract suspend fun updatePackageCommand(specification: PythonPackageSpecification): Result<String>
|
||||
protected abstract suspend fun uninstallPackageCommand(pkg: PythonPackage): Result<String>
|
||||
protected abstract suspend fun reloadPackagesCommand(): Result<List<PythonPackage>>
|
||||
|
||||
internal suspend fun refreshPaths() {
|
||||
writeAction {
|
||||
VfsUtil.markDirtyAndRefresh(true, true, true, *sdk.rootProvider.getFiles(OrderRootType.CLASSES))
|
||||
@@ -35,7 +54,6 @@ abstract class PythonPackageManager(val project: Project, val sdk: Sdk) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
companion object {
|
||||
fun forSdk(project: Project, sdk: Sdk): PythonPackageManager {
|
||||
return project.service<PackageManagerHolder>().forSdk(project, sdk)
|
||||
|
||||
@@ -18,11 +18,13 @@ import com.intellij.openapi.project.guessProjectDir
|
||||
import com.intellij.openapi.util.registry.Registry
|
||||
import com.intellij.platform.ide.progress.withBackgroundProgress
|
||||
import com.intellij.util.net.HttpConfigurable
|
||||
import com.jetbrains.python.PyBundle
|
||||
import com.jetbrains.python.PySdkBundle
|
||||
import com.jetbrains.python.PythonHelper
|
||||
import com.jetbrains.python.packaging.PyExecutionException
|
||||
import com.jetbrains.python.packaging.common.PythonPackageSpecification
|
||||
import com.jetbrains.python.packaging.common.normalizePackageName
|
||||
import com.jetbrains.python.packaging.common.runPackagingOperationOrShowErrorDialog
|
||||
import com.jetbrains.python.packaging.repository.PyPackageRepository
|
||||
import com.jetbrains.python.run.PythonInterpreterTargetEnvironmentFactory
|
||||
import com.jetbrains.python.run.buildTargetedCommandLine
|
||||
@@ -36,7 +38,9 @@ import kotlin.math.min
|
||||
|
||||
fun PythonPackageManager.launchReload() {
|
||||
(ApplicationManager.getApplication() as ComponentManagerEx).getCoroutineScope().launch {
|
||||
reloadPackages()
|
||||
runPackagingOperationOrShowErrorDialog(sdk, PyBundle.message("python.packaging.operation.failed.title")) {
|
||||
reloadPackages()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,39 +1,73 @@
|
||||
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.jetbrains.python.packaging.pip
|
||||
|
||||
import com.intellij.execution.ExecutionException
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.jetbrains.python.PyBundle
|
||||
import com.jetbrains.python.packaging.PyPackageManager
|
||||
import com.jetbrains.python.packaging.common.PythonPackage
|
||||
import com.jetbrains.python.packaging.common.PythonPackageSpecification
|
||||
import com.jetbrains.python.packaging.common.runPackagingOperationOrShowErrorDialog
|
||||
import com.jetbrains.python.packaging.management.PythonPackageManager
|
||||
import com.jetbrains.python.packaging.management.runPackagingTool
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
|
||||
@ApiStatus.Experimental
|
||||
abstract class PipBasedPackageManager(project: Project, sdk: Sdk) : PythonPackageManager(project, sdk) {
|
||||
override suspend fun installPackage(specification: PythonPackageSpecification, options: List<String>): Result<List<PythonPackage>> {
|
||||
return runPackagingOperationOrShowErrorDialog(sdk, PyBundle.message("python.new.project.install.failed.title", specification.name), specification.name) {
|
||||
runPackagingTool("install", specification.buildInstallationString() + options, PyBundle.message("python.packaging.install.progress", specification.name))
|
||||
refreshPaths()
|
||||
reloadPackages()
|
||||
}
|
||||
}
|
||||
@Volatile
|
||||
override var installedPackages: List<PythonPackage> = emptyList()
|
||||
|
||||
override suspend fun updatePackage(specification: PythonPackageSpecification): Result<List<PythonPackage>> {
|
||||
return runPackagingOperationOrShowErrorDialog(sdk, PyBundle.message("python.packaging.notification.update.failed", specification.name), specification.name) {
|
||||
runPackagingTool("install", listOf("--upgrade") + specification.buildInstallationString(), PyBundle.message("python.packaging.update.progress", specification.name))
|
||||
refreshPaths()
|
||||
reloadPackages()
|
||||
override suspend fun installPackageCommand(specification: PythonPackageSpecification, options: List<String>): Result<String> =
|
||||
try {
|
||||
Result.success(runPackagingTool("install", specification.buildInstallationString() + options, PyBundle.message("python.packaging.install.progress", specification.name)))
|
||||
}
|
||||
catch (ex: ExecutionException) {
|
||||
Result.failure(ex)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun uninstallPackage(pkg: PythonPackage): Result<List<PythonPackage>> {
|
||||
return runPackagingOperationOrShowErrorDialog(sdk, PyBundle.message("python.packaging.operation.failed.title")) {
|
||||
runPackagingTool("uninstall", listOf(pkg.name), PyBundle.message("python.packaging.uninstall.progress", pkg.name))
|
||||
refreshPaths()
|
||||
reloadPackages()
|
||||
override suspend fun updatePackageCommand(specification: PythonPackageSpecification): Result<String> =
|
||||
try {
|
||||
Result.success(runPackagingTool("install", listOf("--upgrade") + specification.buildInstallationString(), PyBundle.message("python.packaging.update.progress", specification.name)))
|
||||
}
|
||||
catch (ex: ExecutionException) {
|
||||
Result.failure(ex)
|
||||
}
|
||||
|
||||
override suspend fun uninstallPackageCommand(pkg: PythonPackage): Result<String> =
|
||||
try {
|
||||
Result.success(runPackagingTool("uninstall", listOf(pkg.name), PyBundle.message("python.packaging.uninstall.progress", pkg.name)))
|
||||
}
|
||||
catch (ex: ExecutionException) {
|
||||
Result.failure(ex)
|
||||
}
|
||||
|
||||
override suspend fun reloadPackagesCommand(): Result<List<PythonPackage>> =
|
||||
try {
|
||||
val output = runPackagingTool("list", emptyList(), PyBundle.message("python.packaging.list.progress"))
|
||||
val packages = output.lineSequence()
|
||||
.filter { it.isNotBlank() }
|
||||
.map {
|
||||
val line = it.split("\t")
|
||||
PythonPackage(line[0], line[1], isEditableMode = false)
|
||||
}
|
||||
.sortedWith(compareBy(PythonPackage::name))
|
||||
.toList()
|
||||
Result.success(packages)
|
||||
}
|
||||
catch (ex: ExecutionException) {
|
||||
Result.failure(ex)
|
||||
}
|
||||
|
||||
override suspend fun reloadPackages(): Result<List<PythonPackage>> {
|
||||
val packages = reloadPackagesCommand().onFailure { return Result.failure(it) }.getOrThrow()
|
||||
installedPackages = packages
|
||||
|
||||
ApplicationManager.getApplication().messageBus.apply {
|
||||
syncPublisher(PACKAGE_MANAGEMENT_TOPIC).packagesChanged(sdk)
|
||||
syncPublisher(PyPackageManager.PACKAGE_MANAGER_TOPIC).packagesRefreshed(sdk)
|
||||
}
|
||||
|
||||
return Result.success(packages)
|
||||
}
|
||||
}
|
||||
@@ -1,51 +1,11 @@
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.jetbrains.python.packaging.pip
|
||||
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.platform.backend.observation.trackActivity
|
||||
import com.jetbrains.python.PyBundle
|
||||
import com.jetbrains.python.packaging.PyPackageManager
|
||||
import com.jetbrains.python.packaging.common.PythonPackage
|
||||
import com.jetbrains.python.packaging.common.runPackagingOperationOrShowErrorDialog
|
||||
import com.jetbrains.python.packaging.management.runPackagingTool
|
||||
import com.jetbrains.python.sdk.headless.PythonActivityKey
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
|
||||
@ApiStatus.Experimental
|
||||
class PipPythonPackageManager(project: Project, sdk: Sdk) : PipBasedPackageManager(project, sdk) {
|
||||
@Volatile
|
||||
override var installedPackages: List<PythonPackage> = emptyList()
|
||||
private set
|
||||
|
||||
override val repositoryManager: PipRepositoryManager = PipRepositoryManager(project, sdk)
|
||||
|
||||
override suspend fun reloadPackages(): Result<List<PythonPackage>> = project.trackActivity(PythonActivityKey) {
|
||||
val result = runPackagingOperationOrShowErrorDialog(sdk, PyBundle.message("python.packaging.operation.failed.title")) {
|
||||
val output = runPackagingTool("list", emptyList(), PyBundle.message("python.packaging.list.progress"))
|
||||
|
||||
val packages = output.lineSequence()
|
||||
.filter { it.isNotBlank() }
|
||||
.map {
|
||||
val line = it.split("\t")
|
||||
PythonPackage(line[0], line[1], isEditableMode = false)
|
||||
}
|
||||
.sortedWith(compareBy(PythonPackage::name))
|
||||
.toList()
|
||||
Result.success(packages)
|
||||
}
|
||||
|
||||
if (result.isFailure) return@trackActivity result
|
||||
|
||||
installedPackages = result.getOrThrow()
|
||||
|
||||
ApplicationManager.getApplication().messageBus.apply {
|
||||
syncPublisher(PACKAGE_MANAGEMENT_TOPIC).packagesChanged(sdk)
|
||||
syncPublisher(PyPackageManager.PACKAGE_MANAGER_TOPIC).packagesRefreshed(sdk)
|
||||
}
|
||||
|
||||
return@trackActivity result
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,12 +20,14 @@ import com.intellij.openapi.roots.ModuleRootListener
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import com.intellij.platform.ide.progress.withBackgroundProgress
|
||||
import com.intellij.platform.util.progress.reportRawProgress
|
||||
import com.jetbrains.python.PyBundle
|
||||
import com.jetbrains.python.PyBundle.message
|
||||
import com.jetbrains.python.packaging.*
|
||||
import com.jetbrains.python.packaging.common.PythonPackageDetails
|
||||
import com.jetbrains.python.packaging.common.PythonPackageManagementListener
|
||||
import com.jetbrains.python.packaging.common.PythonPackageSpecification
|
||||
import com.jetbrains.python.packaging.common.normalizePackageName
|
||||
import com.jetbrains.python.packaging.common.runPackagingOperationOrShowErrorDialog
|
||||
import com.jetbrains.python.packaging.conda.CondaPackage
|
||||
import com.jetbrains.python.packaging.management.PythonPackageManager
|
||||
import com.jetbrains.python.packaging.management.packagesByRepository
|
||||
@@ -106,18 +108,25 @@ class PyPackagingToolWindowService(val project: Project, val serviceScope: Corou
|
||||
|
||||
suspend fun installPackage(specification: PythonPackageSpecification, options: List<String> = emptyList()) {
|
||||
PythonPackagesToolwindowStatisticsCollector.installPackageEvent.log(project)
|
||||
val result = manager.installPackage(specification, options = options)
|
||||
|
||||
val result = runPackagingOperationOrShowErrorDialog(manager.sdk, message("python.new.project.install.failed.title", specification.name), specification.name) {
|
||||
manager.installPackage(specification, options)
|
||||
}
|
||||
if (result.isSuccess) showPackagingNotification(message("python.packaging.notification.installed", specification.name))
|
||||
}
|
||||
|
||||
suspend fun deletePackage(selectedPackage: InstalledPackage) {
|
||||
PythonPackagesToolwindowStatisticsCollector.uninstallPackageEvent.log(project)
|
||||
val result = manager.uninstallPackage(selectedPackage.instance)
|
||||
val result = runPackagingOperationOrShowErrorDialog(manager.sdk, message("python.packaging.operation.failed.title")) {
|
||||
manager.uninstallPackage(selectedPackage.instance)
|
||||
}
|
||||
if (result.isSuccess) showPackagingNotification(message("python.packaging.notification.deleted", selectedPackage.name))
|
||||
}
|
||||
|
||||
suspend fun updatePackage(specification: PythonPackageSpecification) {
|
||||
val result = manager.updatePackage(specification)
|
||||
val result = runPackagingOperationOrShowErrorDialog(manager.sdk, message("python.packaging.notification.update.failed", specification.name), specification.name) {
|
||||
manager.updatePackage(specification)
|
||||
}
|
||||
if (result.isSuccess) showPackagingNotification(message("python.packaging.notification.updated", specification.name, specification.versionSpecs))
|
||||
}
|
||||
|
||||
@@ -138,7 +147,9 @@ class PyPackagingToolWindowService(val project: Project, val serviceScope: Corou
|
||||
}
|
||||
manager = PythonPackageManager.forSdk(project, currentSdk!!)
|
||||
manager.repositoryManager.initCaches()
|
||||
manager.reloadPackages()
|
||||
runPackagingOperationOrShowErrorDialog(sdk, message("python.packaging.operation.failed.title")) {
|
||||
manager.reloadPackages()
|
||||
}
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
toolWindowPanel?.contentVisible = currentSdk != null
|
||||
@@ -285,7 +296,9 @@ class PyPackagingToolWindowService(val project: Project, val serviceScope: Corou
|
||||
serviceScope.launch(Dispatchers.IO) {
|
||||
withBackgroundProgress(project, message("python.packaging.loading.packages.progress.text"), cancellable = false) {
|
||||
reportRawProgress {
|
||||
manager.reloadPackages()
|
||||
runPackagingOperationOrShowErrorDialog(manager.sdk, message("python.packaging.operation.failed.title")) {
|
||||
manager.reloadPackages()
|
||||
}
|
||||
refreshInstalledPackages()
|
||||
manager.repositoryManager.refreshCashes()
|
||||
}
|
||||
|
||||
@@ -155,7 +155,10 @@ class InstallRequirementQuickFix(requirement: Requirement) : LocalQuickFix {
|
||||
val name = requirement.displayName
|
||||
|
||||
project.service<PyPackagingToolWindowService>().serviceScope.launch(Dispatchers.IO) {
|
||||
manager.installPackage(manager.repositoryManager.createSpecification(name, versionSpec) ?: return@launch, emptyList<String>())
|
||||
val specification = manager.repositoryManager.createSpecification(name, versionSpec) ?: return@launch
|
||||
runPackagingOperationOrShowErrorDialog(sdk, PyBundle.message("python.new.project.install.failed.title", specification.name), specification.name) {
|
||||
manager.installPackage(specification, emptyList<String>())
|
||||
}
|
||||
DaemonCodeAnalyzer.getInstance(project).restart(file)
|
||||
}
|
||||
}
|
||||
@@ -200,7 +203,9 @@ class InstallProjectAsEditableQuickfix : LocalQuickFix {
|
||||
runPackagingOperationOrShowErrorDialog(sdk, PyBundle.message("python.pyproject.install.self.error"), null) {
|
||||
manager.runPackagingTool("install", listOf("-e", "."), PyBundle.message("python.pyproject.install.self.as.editable.progress"))
|
||||
manager.refreshPaths()
|
||||
manager.reloadPackages()
|
||||
runPackagingOperationOrShowErrorDialog(sdk, PyBundle.message("python.packaging.operation.failed.title")) {
|
||||
manager.reloadPackages()
|
||||
}
|
||||
}
|
||||
DaemonCodeAnalyzer.getInstance(project).restart(file)
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ internal object Logger {
|
||||
* @return A [Result] object containing the output of the command execution.
|
||||
*/
|
||||
@RequiresBackgroundThread
|
||||
internal fun runCommandLine(commandLine: GeneralCommandLine): Result<ProcessOutput> {
|
||||
internal fun runCommandLine(commandLine: GeneralCommandLine): Result<String> {
|
||||
Logger.LOG.info("Running command: ${commandLine.commandLineString}")
|
||||
val commandOutput = with(CapturingProcessHandler(commandLine)) {
|
||||
runProcess()
|
||||
@@ -39,7 +39,7 @@ internal fun runCommandLine(commandLine: GeneralCommandLine): Result<ProcessOutp
|
||||
)
|
||||
}
|
||||
|
||||
fun runCommand(executable: Path, projectPath: Path?, @NlsContexts.DialogMessage errorMessage: String, vararg args: String): String {
|
||||
fun runCommand(executable: Path, projectPath: Path?, @NlsContexts.DialogMessage errorMessage: String, vararg args: String): Result<String> {
|
||||
val command = listOf(executable.absolutePathString()) + args
|
||||
val commandLine = GeneralCommandLine(command).withWorkingDirectory(projectPath)
|
||||
val handler = CapturingProcessHandler(commandLine)
|
||||
@@ -55,7 +55,7 @@ fun runCommand(executable: Path, projectPath: Path?, @NlsContexts.DialogMessage
|
||||
}
|
||||
}
|
||||
|
||||
return processOutput(result, executable.pathString, args.asList(), errorMessage).getOrThrow().stdout.trim()
|
||||
return processOutput(result, executable.pathString, args.asList(), errorMessage)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -72,14 +72,14 @@ internal fun processOutput(
|
||||
commandString: String,
|
||||
args: List<String>,
|
||||
@NlsContexts.DialogMessage errorMessage: String = "",
|
||||
): Result<ProcessOutput> {
|
||||
): Result<String> {
|
||||
return with(output) {
|
||||
when {
|
||||
isCancelled ->
|
||||
Result.failure(RunCanceledByUserException())
|
||||
exitCode != 0 ->
|
||||
Result.failure(PyExecutionException(errorMessage, commandString, args, stdout, stderr, exitCode, emptyList()))
|
||||
else -> Result.success(output)
|
||||
else -> Result.success(output.stdout.trim())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -49,7 +49,7 @@ internal class PackageInstallationFilesService {
|
||||
* @return A [Result] object that represents the [ProcessOutput] of the installation command.
|
||||
*/
|
||||
@RequiresBackgroundThread
|
||||
internal suspend fun installPackageWithPython(url: URL, pythonExecutable: String): Result<ProcessOutput> {
|
||||
internal suspend fun installPackageWithPython(url: URL, pythonExecutable: String): Result<String> {
|
||||
val installationFile = downloadFile(url).getOrThrow()
|
||||
val command = GeneralCommandLine(pythonExecutable, installationFile.absolutePathString())
|
||||
return runCommandLine(command)
|
||||
|
||||
@@ -168,7 +168,7 @@ fun runPipEnv(projectPath: @SystemDependent String, vararg args: String): String
|
||||
PyBundle.message("python.sdk.pipenv.execution.exception.no.pipenv.message"),
|
||||
"pipenv", emptyList(), ProcessOutput())
|
||||
@Suppress("DialogTitleCapitalization")
|
||||
return runCommand(executable, Path.of(projectPath), PyBundle.message("python.sdk.pipenv.execution.exception.error.running.pipenv.message"), *args)
|
||||
return runCommand(executable, Path.of(projectPath), PyBundle.message("python.sdk.pipenv.execution.exception.error.running.pipenv.message"), *args).getOrThrow()
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,7 +6,6 @@ import com.intellij.execution.RunCanceledByUserException
|
||||
import com.intellij.execution.configurations.GeneralCommandLine
|
||||
import com.intellij.execution.configurations.PathEnvironmentVariableUtil
|
||||
import com.intellij.execution.process.CapturingProcessHandler
|
||||
import com.intellij.execution.process.ProcessNotCreatedException
|
||||
import com.intellij.execution.process.ProcessOutput
|
||||
import com.intellij.ide.util.PropertiesComponent
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
@@ -21,12 +20,13 @@ import com.intellij.platform.ide.progress.withBackgroundProgress
|
||||
import com.intellij.util.SystemProperties
|
||||
import com.jetbrains.python.PyBundle
|
||||
import com.jetbrains.python.packaging.PyExecutionException
|
||||
import com.jetbrains.python.packaging.PyPackageManager
|
||||
import com.jetbrains.python.packaging.management.PythonPackageManager
|
||||
import com.jetbrains.python.pathValidation.PlatformAndRoot
|
||||
import com.jetbrains.python.pathValidation.ValidationRequest
|
||||
import com.jetbrains.python.pathValidation.validateExecutableFile
|
||||
import com.jetbrains.python.sdk.*
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jetbrains.annotations.ApiStatus.Internal
|
||||
import org.jetbrains.annotations.NonNls
|
||||
import org.jetbrains.annotations.SystemDependent
|
||||
import org.jetbrains.annotations.SystemIndependent
|
||||
@@ -81,7 +81,7 @@ fun validatePoetryExecutable(poetryExecutable: Path?): ValidationInfo? =
|
||||
/**
|
||||
* Runs the configured poetry for the specified Poetry SDK with the associated project path.
|
||||
*/
|
||||
internal fun runPoetry(sdk: Sdk, vararg args: String): String {
|
||||
internal fun runPoetry(sdk: Sdk, vararg args: String): Result<String> {
|
||||
val projectPath = sdk.associatedModulePath?.let { Path.of(it) }
|
||||
?: throw PyExecutionException(PyBundle.message("python.sdk.poetry.execution.exception.no.project.message"),
|
||||
"Poetry", emptyList(), ProcessOutput())
|
||||
@@ -92,10 +92,10 @@ internal fun runPoetry(sdk: Sdk, vararg args: String): String {
|
||||
/**
|
||||
* Runs the configured poetry for the specified project path.
|
||||
*/
|
||||
fun runPoetry(projectPath: Path?, vararg args: String): String {
|
||||
fun runPoetry(projectPath: Path?, vararg args: String): Result<String> {
|
||||
val executable = getPoetryExecutable()
|
||||
?: throw PyExecutionException(PyBundle.message("python.sdk.poetry.execution.exception.no.poetry.message"), "poetry",
|
||||
emptyList(), ProcessOutput())
|
||||
?: return Result.failure(PyExecutionException(PyBundle.message("python.sdk.poetry.execution.exception.no.poetry.message"), "poetry",
|
||||
emptyList(), ProcessOutput()))
|
||||
|
||||
return runCommand(executable, projectPath, PyBundle.message("sdk.create.custom.venv.run.error.message", "poetry"), *args)
|
||||
}
|
||||
@@ -121,10 +121,11 @@ fun setupPoetry(projectPath: Path, python: String?, installPackages: Boolean, in
|
||||
python != null -> runPoetry(projectPath, "env", "use", python)
|
||||
else -> runPoetry(projectPath, "run", "python", "-V")
|
||||
}
|
||||
return runPoetry(projectPath, "env", "info", "-p")
|
||||
|
||||
return runPoetry(projectPath, "env", "info", "-p").getOrThrow()
|
||||
}
|
||||
|
||||
private fun runCommand(projectPath: Path, command: String, vararg args: String): String {
|
||||
private fun runCommand(projectPath: Path, command: String, vararg args: String): Result<String> {
|
||||
val commandLine = GeneralCommandLine(listOf(command) + args).withWorkingDirectory(projectPath)
|
||||
val handler = CapturingProcessHandler(commandLine)
|
||||
|
||||
@@ -134,12 +135,12 @@ private fun runCommand(projectPath: Path, command: String, vararg args: String):
|
||||
return with(result) {
|
||||
when {
|
||||
isCancelled ->
|
||||
throw RunCanceledByUserException()
|
||||
Result.failure(RunCanceledByUserException())
|
||||
exitCode != 0 ->
|
||||
throw PyExecutionException(PyBundle.message("sdk.create.custom.venv.run.error.message", "poetry"), command,
|
||||
args.asList(),
|
||||
stdout, stderr, exitCode, emptyList())
|
||||
else -> stdout
|
||||
Result.failure(PyExecutionException(PyBundle.message("sdk.create.custom.venv.run.error.message", "poetry"), command,
|
||||
args.asList(),
|
||||
stdout, stderr, exitCode, emptyList()))
|
||||
else -> Result.success(stdout)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -149,15 +150,15 @@ internal fun runPoetryInBackground(module: Module, args: List<String>, @NlsSafe
|
||||
withBackgroundProgress(module.project, "$description...", true) {
|
||||
val sdk = module.pythonSdk ?: return@withBackgroundProgress
|
||||
try {
|
||||
runPoetry(sdk, *args.toTypedArray())
|
||||
}
|
||||
catch (e: ExecutionException) {
|
||||
showSdkExecutionException(sdk, e, PyBundle.message("sdk.create.custom.venv.run.error.message", "poetry"))
|
||||
val result = runPoetry(sdk, *args.toTypedArray()).exceptionOrNull()
|
||||
if (result is ExecutionException) {
|
||||
showSdkExecutionException(sdk, result, PyBundle.message("sdk.create.custom.venv.run.error.message", "poetry"))
|
||||
}
|
||||
}
|
||||
finally {
|
||||
PythonSdkUtil.getSitePackagesDirectory(sdk)?.refresh(true, true)
|
||||
sdk.associatedModuleDir?.refresh(true, false)
|
||||
PyPackageManager.getInstance(sdk).refreshAndGetPackages(true)
|
||||
PythonPackageManager.forSdk(module.project, sdk).reloadPackages()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -168,7 +169,7 @@ internal fun detectPoetryEnvs(module: Module?, existingSdkPaths: Set<String>, pr
|
||||
return try {
|
||||
getPoetryEnvs(path).filter { existingSdkPaths.contains(getPythonExecutable(it)) }.map { PyDetectedSdk(getPythonExecutable(it)) }
|
||||
}
|
||||
catch (e: Throwable) {
|
||||
catch (_: Throwable) {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
@@ -191,21 +192,44 @@ inline fun <reified T> syncRunPoetry(
|
||||
): T {
|
||||
return try {
|
||||
ApplicationManager.getApplication().executeOnPooledThread<T> {
|
||||
try {
|
||||
val result = runPoetry(projectPath, *args)
|
||||
callback(result)
|
||||
}
|
||||
catch (e: PyExecutionException) {
|
||||
defaultResult
|
||||
}
|
||||
catch (e: ProcessNotCreatedException) {
|
||||
defaultResult
|
||||
}
|
||||
val result = runPoetry(projectPath, *args).getOrNull()
|
||||
if (result == null) defaultResult else callback(result)
|
||||
}.get(30, TimeUnit.SECONDS)
|
||||
}
|
||||
catch (e: TimeoutException) {
|
||||
catch (_: TimeoutException) {
|
||||
defaultResult
|
||||
}
|
||||
}
|
||||
|
||||
fun getPythonExecutable(homePath: String): String = VirtualEnvReader.Instance.findPythonInPythonRoot(Path.of(homePath))?.toString() ?: FileUtil.join(homePath, "bin", "python")
|
||||
fun getPythonExecutable(homePath: String): String = VirtualEnvReader.Instance.findPythonInPythonRoot(Path.of(homePath))?.toString()
|
||||
?: FileUtil.join(homePath, "bin", "python")
|
||||
|
||||
|
||||
/**
|
||||
* Installs a Python package using Poetry.
|
||||
* Runs `poetry add [pkg] [extraArgs]`
|
||||
*
|
||||
* @param [pkg] The name of the package to be installed.
|
||||
* @param [extraArgs] Additional arguments to pass to the Poetry add command.
|
||||
*/
|
||||
@Internal
|
||||
fun poetryInstallPackage(sdk: Sdk, pkg: String, extraArgs: List<String>): Result<String> {
|
||||
val args = listOf("add", pkg) + extraArgs
|
||||
return runPoetry(sdk, *args.toTypedArray())
|
||||
}
|
||||
|
||||
/**
|
||||
* Uninstalls a Python package using Poetry.
|
||||
* Runs `poetry remove [pkg]`
|
||||
*
|
||||
* @param [pkg] The name of the package to be uninstalled.
|
||||
*/
|
||||
@Internal
|
||||
fun poetryUninstallPackage(sdk: Sdk, pkg: String): Result<String> = runPoetry(sdk, "remove", pkg)
|
||||
|
||||
@Internal
|
||||
fun poetryReloadPackages(sdk: Sdk): Result<String> {
|
||||
runPoetry(sdk, "update").onFailure { return Result.failure(it) }
|
||||
runPoetry(sdk, "install", "--no-root").onFailure { return Result.failure(it) }
|
||||
return runPoetry(sdk, "show")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
// Copyright 2000-2024 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.sdk.poetry
|
||||
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.jetbrains.python.packaging.common.PythonPackage
|
||||
import com.jetbrains.python.packaging.common.PythonPackageSpecification
|
||||
import com.jetbrains.python.packaging.pip.PipBasedPackageManager
|
||||
import com.jetbrains.python.packaging.pip.PipRepositoryManager
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
import java.util.regex.Pattern
|
||||
|
||||
class PoetryPackageManager(project: Project, sdk: Sdk) : PipBasedPackageManager(project, sdk) {
|
||||
@Volatile
|
||||
private var outdatedPackages: Map<String, PoetryOutdatedVersion> = emptyMap()
|
||||
|
||||
override val repositoryManager: PipRepositoryManager = PipRepositoryManager(project, sdk)
|
||||
|
||||
override suspend fun installPackageCommand(specification: PythonPackageSpecification, options: List<String>): Result<String> =
|
||||
poetryInstallPackage(sdk, specification.getVersionForPoetry(), options)
|
||||
|
||||
override suspend fun updatePackageCommand(specification: PythonPackageSpecification): Result<String> =
|
||||
poetryInstallPackage(sdk, specification.getVersionForPoetry(), emptyList())
|
||||
|
||||
override suspend fun uninstallPackageCommand(pkg: PythonPackage): Result<String> = poetryUninstallPackage(sdk, pkg.name)
|
||||
|
||||
override suspend fun reloadPackagesCommand(): Result<List<PythonPackage>> {
|
||||
val output = poetryReloadPackages(sdk).getOrElse { return Result.failure(it) }
|
||||
return Result.success(parsePoetryShow(output))
|
||||
}
|
||||
|
||||
override suspend fun reloadPackages(): Result<List<PythonPackage>> {
|
||||
updateOutdatedPackages()
|
||||
return super.reloadPackages()
|
||||
}
|
||||
|
||||
internal fun getOutdatedPackages(): Map<String, PoetryOutdatedVersion> = outdatedPackages
|
||||
|
||||
|
||||
/**
|
||||
* Updates the list of outdated packages by running the Poetry command
|
||||
* `poetry show --outdated`, parsing its output, and storing the results.
|
||||
*/
|
||||
private fun updateOutdatedPackages() {
|
||||
val outputOutdatedPackages = runPoetry(sdk, "show", "--outdated").getOrElse {
|
||||
outdatedPackages = emptyMap()
|
||||
return
|
||||
}
|
||||
|
||||
outdatedPackages = parsePoetryShowOutdated(outputOutdatedPackages)
|
||||
}
|
||||
|
||||
private fun PythonPackageSpecification.getVersionForPoetry(): String = if (versionSpecs == null) name else "$name@$versionSpecs"
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the output of `poetry show` into a list of packages.
|
||||
*/
|
||||
private fun parsePoetryShow(input: String): List<PythonPackage> {
|
||||
val result = mutableListOf<PythonPackage>()
|
||||
input.split("\n").forEach { line ->
|
||||
if (line.isNotBlank()) {
|
||||
val packageInfo = line.trim().split(" ").map { it.trim() }.filter { it.isNotBlank() }
|
||||
result.add(PythonPackage(packageInfo[0], packageInfo[1], false))
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the output of `poetry show --outdated` into a list of packages.
|
||||
*/
|
||||
private fun parsePoetryShowOutdated(input: String): Map<String, PoetryOutdatedVersion> =
|
||||
input
|
||||
.lines()
|
||||
.map { it.trim() }
|
||||
.filter { it.isNotBlank() }
|
||||
.mapNotNull { line ->
|
||||
line.split(Pattern.compile(" +"))
|
||||
.takeIf { it.size > 3 }?.let { it[0] to PoetryOutdatedVersion(it[1], it[2]) }
|
||||
}.toMap()
|
||||
|
||||
@TestOnly
|
||||
fun parsePoetryShowTest(input: String): List<PythonPackage> = parsePoetryShow(input)
|
||||
|
||||
@TestOnly
|
||||
fun parsePoetryShowOutdatedTest(input: String): Map<String, PoetryOutdatedVersion> = parsePoetryShowOutdated(input)
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.jetbrains.python.sdk.poetry
|
||||
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.jetbrains.python.packaging.management.PythonPackageManager
|
||||
import com.jetbrains.python.packaging.management.PythonPackageManagerProvider
|
||||
|
||||
/**
|
||||
* This source code is created by @koxudaxi Koudai Aono <koxudaxi@gmail.com>
|
||||
*/
|
||||
|
||||
class PoetryPackageManagerProvider : PythonPackageManagerProvider {
|
||||
override fun createPackageManagerForSdk(project: Project, sdk: Sdk): PythonPackageManager? = if (sdk.isPoetry) PoetryPackageManager(project, sdk) else null
|
||||
}
|
||||
@@ -12,7 +12,7 @@ import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiElementVisitor
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.jetbrains.python.PyBundle
|
||||
import com.jetbrains.python.packaging.PyPackageManager
|
||||
import com.jetbrains.python.packaging.management.PythonPackageManager
|
||||
import com.jetbrains.python.sdk.PythonSdkUtil
|
||||
import org.toml.lang.psi.TomlKeyValue
|
||||
import org.toml.lang.psi.TomlTable
|
||||
@@ -47,8 +47,8 @@ internal class PoetryPackageVersionsInspection : LocalInspectionTool() {
|
||||
it.children.mapNotNull { line -> line as? TomlKeyValue }
|
||||
}.forEach { keyValue ->
|
||||
val packageName = keyValue.key.text
|
||||
val outdatedVersion = (PyPackageManager.getInstance(
|
||||
sdk) as? PyPoetryPackageManager)?.let { it.getOutdatedPackages()[packageName] }
|
||||
val outdatedVersion = (PythonPackageManager.forSdk(
|
||||
module.project, sdk) as? PoetryPackageManager)?.let { it.getOutdatedPackages()[packageName] }
|
||||
if (outdatedVersion is PoetryOutdatedVersion) {
|
||||
val message = PyBundle.message("python.sdk.inspection.message.version.outdated.latest",
|
||||
packageName, outdatedVersion.currentVersion, outdatedVersion.latestVersion)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2000-2024 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.
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.jetbrains.python.sdk.poetry
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
@@ -13,6 +13,7 @@ import com.jetbrains.python.PyBundle
|
||||
import com.jetbrains.python.packaging.*
|
||||
import com.jetbrains.python.sdk.PythonSdkType
|
||||
import com.jetbrains.python.sdk.associatedModuleDir
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.util.regex.Pattern
|
||||
|
||||
|
||||
@@ -57,7 +58,7 @@ class PyPoetryPackageManager(sdk: Sdk) : PyPackageManager(sdk) {
|
||||
}
|
||||
|
||||
try {
|
||||
runPoetry(sdk, *args.toTypedArray())
|
||||
runPoetry(sdk, *args.toTypedArray())
|
||||
}
|
||||
finally {
|
||||
sdk.associatedModuleDir?.refresh(true, false)
|
||||
@@ -69,8 +70,8 @@ class PyPoetryPackageManager(sdk: Sdk) : PyPackageManager(sdk) {
|
||||
val args = listOf("remove") +
|
||||
packages.map { it.name }
|
||||
try {
|
||||
runPoetry(sdk, *args.toTypedArray())
|
||||
}
|
||||
runPoetry(sdk, *args.toTypedArray())
|
||||
}
|
||||
finally {
|
||||
sdk.associatedModuleDir?.refresh(true, false)
|
||||
refreshAndGetPackages(true)
|
||||
@@ -102,18 +103,18 @@ class PyPoetryPackageManager(sdk: Sdk) : PyPackageManager(sdk) {
|
||||
if (alwaysRefresh || packages == null) {
|
||||
packages = null
|
||||
val outputInstallDryRun = try {
|
||||
runPoetry(sdk, "install", "--dry-run", "--no-root")
|
||||
runPoetry(sdk, "install", "--dry-run", "--no-root")
|
||||
}
|
||||
catch (e: ExecutionException) {
|
||||
packages = emptyList()
|
||||
return packages ?: emptyList()
|
||||
}
|
||||
val allPackage = parsePoetryInstallDryRun(outputInstallDryRun)
|
||||
val allPackage = parsePoetryInstallDryRun(outputInstallDryRun.getOrThrow())
|
||||
packages = allPackage.first
|
||||
requirements = allPackage.second
|
||||
|
||||
val outputOutdatedPackages = try {
|
||||
runPoetry(sdk, "show", "--outdated")
|
||||
runPoetry(sdk, "show", "--outdated")
|
||||
}
|
||||
catch (e: ExecutionException) {
|
||||
outdatedPackages = emptyMap()
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.jetbrains.python.sdk.poetry
|
||||
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
|
||||
@@ -13,7 +13,7 @@ class TestPythonPackageManager(project: Project, sdk: Sdk) : PythonPackageManage
|
||||
private var packageNames: List<String> = emptyList()
|
||||
private var packageDetails: PythonPackageDetails? = null
|
||||
|
||||
override val installedPackages: List<PythonPackage>
|
||||
override var installedPackages: List<PythonPackage> = emptyList()
|
||||
get() = TODO("Not yet implemented")
|
||||
override val repositoryManager: PythonRepositoryManager
|
||||
get() = TestPythonRepositoryManager(project, sdk).withPackageNames(packageNames).withPackageDetails(packageDetails)
|
||||
@@ -28,19 +28,23 @@ class TestPythonPackageManager(project: Project, sdk: Sdk) : PythonPackageManage
|
||||
return this
|
||||
}
|
||||
|
||||
override suspend fun installPackage(specification: PythonPackageSpecification, options: List<String>): Result<List<PythonPackage>> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override suspend fun updatePackage(specification: PythonPackageSpecification): Result<List<PythonPackage>> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override suspend fun uninstallPackage(pkg: PythonPackage): Result<List<PythonPackage>> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override suspend fun reloadPackages(): Result<List<PythonPackage>> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override suspend fun installPackageCommand(specification: PythonPackageSpecification, options: List<String>): Result<String> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override suspend fun updatePackageCommand(specification: PythonPackageSpecification): Result<String> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override suspend fun uninstallPackageCommand(pkg: PythonPackage): Result<String> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override suspend fun reloadPackagesCommand(): Result<List<PythonPackage>> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user