diff --git a/python/src/com/jetbrains/python/sdk/poetry/PoetryCommandExecutor.kt b/python/src/com/jetbrains/python/sdk/poetry/PoetryCommandExecutor.kt index 3b7f56d8daae..304446edeaac 100644 --- a/python/src/com/jetbrains/python/sdk/poetry/PoetryCommandExecutor.kt +++ b/python/src/com/jetbrains/python/sdk/poetry/PoetryCommandExecutor.kt @@ -16,8 +16,12 @@ import com.intellij.openapi.util.io.FileUtil import com.intellij.platform.ide.progress.withBackgroundProgress import com.intellij.util.SystemProperties import com.jetbrains.python.PyBundle +import com.jetbrains.python.packaging.PyPackage import com.jetbrains.python.packaging.PyExecutionException import com.jetbrains.python.packaging.PyPackageManager +import com.jetbrains.python.packaging.PyRequirement +import com.jetbrains.python.packaging.PyRequirementParser +import com.jetbrains.python.packaging.common.PythonOutdatedPackage import com.jetbrains.python.packaging.common.PythonPackage import com.jetbrains.python.packaging.management.PythonPackageManager import com.jetbrains.python.pathValidation.PlatformAndRoot @@ -198,7 +202,7 @@ fun parsePoetryShow(input: String): List { val result = mutableListOf() input.split("\n").forEach { line -> if (line.isNotBlank()) { - val packageInfo = line.trim().split(" ").map { it.trim() }.filter { it.isNotBlank() && it != "(!)"} + val packageInfo = line.trim().split(" ").map { it.trim() }.filter { it.isNotBlank() && it != "(!)" } result.add(PythonPackage(packageInfo[0], packageInfo[1], false)) } } @@ -206,6 +210,70 @@ fun parsePoetryShow(input: String): List { return result } +@Internal +suspend fun poetryShowOutdated(sdk: Sdk): Result> { + val output = runPoetryWithSdk(sdk, "show", "--outdated").getOrElse { + return Result.failure(it) + } + + return parsePoetryShowOutdated(output).let { Result.success(it) } +} + +@Internal +suspend fun poetryListPackages(sdk: Sdk): Result, List>> { + // Just in case there were any changes to pyproject.toml + if (runPoetryWithSdk(sdk, "lock", "--check").isFailure) { + runPoetryWithSdk(sdk, "lock", "--no-update") + } + + val output = runPoetryWithSdk(sdk, "install", "--dry-run", "--no-root").getOrElse { + return Result.failure(it) + } + + return parsePoetryInstallDryRun(output).let { + Result.success(it) + } +} + +@Internal +fun parsePoetryInstallDryRun(input: String): Pair, List> { + val installedLines = listOf("Already installed", "Skipping", "Updating") + + fun getNameAndVersion(line: String): Triple { + return line.split(" ").let { + val installedVersion = it[5].replace(Regex("[():]"), "") + val requiredVersion = when { + it.size > 7 && it[6] == "->" -> it[7].replace(Regex("[():]"), "") + else -> installedVersion + } + Triple(it[4], installedVersion, requiredVersion) + } + } + + fun getVersion(version: String): String { + return if (Regex("^[0-9]").containsMatchIn(version)) "==$version" else version + } + + val pyPackages = mutableListOf() + val pyRequirements = mutableListOf() + input + .lineSequence() + .filter { listOf(")", "Already installed").any { lastWords -> it.endsWith(lastWords) } } + .forEach { line -> + getNameAndVersion(line).also { + if (installedLines.any { installedLine -> line.contains(installedLine) }) { + pyPackages.add(PyPackage(it.first, it.second, null, emptyList())) + PyRequirementParser.fromLine(it.first + getVersion(it.third))?.let { pyRequirement -> pyRequirements.add(pyRequirement) } + } + else if (line.contains("Installing")) { + PyRequirementParser.fromLine(it.first + getVersion(it.third))?.let { pyRequirement -> pyRequirements.add(pyRequirement) } + } + } + } + + return Pair(pyPackages.distinct().toList(), pyRequirements.distinct().toList()) +} + /** * Configures the Poetry environment for the specified module path with the given arguments. * Runs command: GeneralCommandLine("poetry config [args]").withWorkingDirectory([modulePath]) diff --git a/python/src/com/jetbrains/python/sdk/poetry/PoetryPackageManager.kt b/python/src/com/jetbrains/python/sdk/poetry/PoetryPackageManager.kt index 814c1ca22f96..bf5385602047 100644 --- a/python/src/com/jetbrains/python/sdk/poetry/PoetryPackageManager.kt +++ b/python/src/com/jetbrains/python/sdk/poetry/PoetryPackageManager.kt @@ -27,29 +27,23 @@ class PoetryPackageManager(project: Project, sdk: Sdk) : PythonPackageManager(pr override suspend fun uninstallPackageCommand(pkg: PythonPackage): Result = poetryUninstallPackage(sdk, pkg.name) override suspend fun reloadPackagesCommand(): Result> { - return poetryShowPackages(sdk) - } + val (installed, _) = poetryListPackages(sdk).getOrElse { + return Result.failure(it) + } - override suspend fun reloadPackages(): Result> { - updateOutdatedPackages() - return super.reloadPackages() + outdatedPackages = poetryShowOutdated(sdk).getOrElse { + emptyMap() + } + + val packages = installed.map { + PythonPackage(it.name, it.version, false) + } + + return Result.success(packages) } internal fun getOutdatedPackages(): Map = outdatedPackages - /** - * Updates the list of outdated packages by running the Poetry command - * `poetry show --outdated`, parsing its output, and storing the results. - */ - private suspend fun updateOutdatedPackages() { - val outputOutdatedPackages = runPoetryWithSdk(sdk, "show", "--outdated").getOrElse { - outdatedPackages = emptyMap() - return - } - - outdatedPackages = parsePoetryShowOutdated(outputOutdatedPackages) - } - private fun PythonPackageSpecification.getVersionForPoetry(): String = if (versionSpecs == null) name else "$name@$versionSpecs" } diff --git a/python/src/com/jetbrains/python/sdk/poetry/PyPoetryPackageManager.kt b/python/src/com/jetbrains/python/sdk/poetry/PyPoetryPackageManager.kt index 3af085f41eef..674a87e976f3 100644 --- a/python/src/com/jetbrains/python/sdk/poetry/PyPoetryPackageManager.kt +++ b/python/src/com/jetbrains/python/sdk/poetry/PyPoetryPackageManager.kt @@ -17,8 +17,6 @@ import com.jetbrains.python.sdk.associatedModuleDir class PyPoetryPackageManager(sdk: Sdk) : PyPackageManager(sdk) { - private val installedLines = listOf("Already installed", "Skipping", "Updating") - @Volatile private var packages: List? = null @@ -37,13 +35,13 @@ class PyPoetryPackageManager(sdk: Sdk) : PyPackageManager(sdk) { override fun install(requirements: List?, extraArgs: List) { val args = if (requirements.isNullOrEmpty()) { listOfNotNull(listOf("install"), - extraArgs) + extraArgs) .flatten() } else { listOfNotNull(listOf("add"), - requirements.map { it.name }, - extraArgs) + requirements.map { it.name }, + extraArgs) .flatten() } @@ -87,29 +85,27 @@ class PyPoetryPackageManager(sdk: Sdk) : PyPackageManager(sdk) { override fun getPackages() = packages - fun getOutdatedPackages() = outdatedPackages - override fun refreshAndGetPackages(alwaysRefresh: Boolean): List { if (alwaysRefresh || packages == null) { - packages = null - val outputInstallDryRun = runBlockingCancellable { runPoetryWithSdk(sdk, "install", "--dry-run", "--no-root") }.getOrElse { + val allPackages = runBlockingCancellable { + poetryListPackages(sdk) + }.getOrElse { packages = emptyList() - return packages ?: emptyList() + return emptyList() } - val allPackage = parsePoetryInstallDryRun(outputInstallDryRun) - packages = allPackage.first - requirements = allPackage.second + packages = allPackages.first + requirements = allPackages.second - val outputOutdatedPackages = runBlockingCancellable { runPoetryWithSdk(sdk, "show", "--outdated") }.getOrElse { - outdatedPackages = emptyMap() + runBlockingCancellable { + outdatedPackages = poetryShowOutdated(sdk).getOrElse { + emptyMap() + } } - if (outputOutdatedPackages is String) { - outdatedPackages = parsePoetryShowOutdated(outputOutdatedPackages) - } ApplicationManager.getApplication().messageBus.syncPublisher(PACKAGE_MANAGER_TOPIC).packagesRefreshed(sdk) } + return packages ?: emptyList() } @@ -130,42 +126,4 @@ class PyPoetryPackageManager(sdk: Sdk) : PyPackageManager(sdk) { // TODO: Parse the dependency information from `pipenv graph` return emptySet() } - - private fun getVersion(version: String): String { - return if (Regex("^[0-9]").containsMatchIn(version)) "==$version" else version - } - - /** - * Parses the output of `poetry install --dry-run ` into a list of packages. - */ - private fun parsePoetryInstallDryRun(input: String): Pair, List> { - fun getNameAndVersion(line: String): Triple { - return line.split(" ").let { - val installedVersion = it[5].replace(Regex("[():]"), "") - val requiredVersion = when { - it.size > 7 && it[6] == "->" -> it[7].replace(Regex("[():]"), "") - else -> installedVersion - } - Triple(it[4], installedVersion, requiredVersion) - } - } - - val pyPackages = mutableListOf() - val pyRequirements = mutableListOf() - input - .lineSequence() - .filter { listOf(")", "Already installed").any { lastWords -> it.endsWith(lastWords) } } - .forEach { line -> - getNameAndVersion(line).also { - if (installedLines.any { installedLine -> line.contains(installedLine) }) { - pyPackages.add(PyPackage(it.first, it.second, null, emptyList())) - this.parseRequirement(it.first + getVersion(it.third))?.let { pyRequirement -> pyRequirements.add(pyRequirement) } - } - else if (line.contains("Installing")) { - this.parseRequirement(it.first + getVersion(it.third))?.let { pyRequirement -> pyRequirements.add(pyRequirement) } - } - } - } - return Pair(pyPackages.distinct().toList(), pyRequirements.distinct().toList()) - } } \ No newline at end of file