[python] refactor stub suggestions to use PythonPackageManager

(cherry picked from commit d370e9fb005f850246a377ae10e4e87ba924efbd)

GitOrigin-RevId: aea57ce2819119f7f8cb877def6f13a65dc5e754
This commit is contained in:
Morgan Bartholomew
2025-06-17 21:45:45 +10:00
committed by intellij-monorepo-bot
parent 40f0114010
commit 7ce9158c26
5 changed files with 62 additions and 53 deletions

View File

@@ -9,8 +9,7 @@ import com.intellij.webcore.packaging.PackageManagementService;
import org.jetbrains.annotations.NotNull;
/**
* @deprecated replaced by {@link com.jetbrains.python.packaging.common.PackageManagerHolder }.
* To get an instance of PythonPackageManager consider using
* @deprecated To get an instance of PythonPackageManager consider using
* {@link com.jetbrains.python.packaging.management.PythonPackageManager.Companion#forSdk(Project, Sdk)}
*/
@Deprecated(forRemoval = true)

View File

@@ -13,7 +13,7 @@ import com.intellij.notification.NotificationAction
import com.intellij.notification.NotificationGroupManager
import com.intellij.notification.NotificationType
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.ComponentManager
import com.intellij.openapi.components.service
import com.intellij.openapi.module.Module
import com.intellij.openapi.module.ModuleUtilCore
import com.intellij.openapi.project.Project
@@ -30,6 +30,7 @@ import com.jetbrains.python.inspections.PyInspectionVisitor
import com.jetbrains.python.inspections.quickfix.PyInstallRequirementsFix
import com.jetbrains.python.inspections.requirement.RunningPackagingTasksListener
import com.jetbrains.python.packaging.*
import com.jetbrains.python.packaging.management.PythonPackageManager
import com.jetbrains.python.packaging.requirement.PyRequirementRelation
import com.jetbrains.python.psi.PyFile
import com.jetbrains.python.psi.PyReferenceExpression
@@ -104,19 +105,19 @@ private class PyStubPackagesAdvertiser : PyInspection() {
val module = ModuleUtilCore.findModuleForFile(file) ?: return
val sdk = PythonSdkUtil.findPythonSdk(module) ?: return
val packageManager = PyPackageManager.getInstance(sdk)
val installedPackages = packageManager.packages ?: emptyList()
val packageManager = PythonPackageManager.forSdk(module.project, sdk)
val installedPackages = packageManager.listInstalledPackagesSnapshot()
if (installedPackages.isEmpty()) return
val packageManagementService = PyPackageManagers.getInstance().getManagementService(file.project, sdk)
val availablePackages = packageManagementService.allPackagesCached
if (availablePackages.isEmpty()) return
val ignoredStubPackages = (IGNORE + ignoredPackages).mapNotNull { packageManager.parseRequirement(it) }
val cache = ApplicationManager.getApplication().getService<PyStubPackagesAdvertiserCache>().forSdk(sdk)
val ignoredStubPackages = (IGNORE + ignoredPackages).mapNotNull { PyRequirementParser.fromLine(it) }
val cache = ApplicationManager.getApplication().service<PyStubPackagesAdvertiserCache>().forSdk(sdk)
val forcedToLoad = processForcedPackages(file, sources, module, sdk, packageManager, ignoredStubPackages, cache)
val checkedToLoad = processCheckedPackages(file, sources, module, sdk, packageManager, ignoredStubPackages, cache)
val forcedToLoad = processForcedPackages(file, sources, module, sdk, ignoredStubPackages, cache)
val checkedToLoad = processCheckedPackages(file, sources, module, sdk, ignoredStubPackages, cache)
loadStubPackagesForSources(
forcedToLoad + checkedToLoad,
@@ -128,13 +129,14 @@ private class PyStubPackagesAdvertiser : PyInspection() {
)
}
private fun processForcedPackages(file: PyFile,
sources: Set<String>,
module: Module,
sdk: Sdk,
packageManager: PyPackageManager,
ignoredStubPackages: List<PyRequirement>,
cache: Cache<String, StubPackagesForSource>): Set<String> {
private fun processForcedPackages(
file: PyFile,
sources: Set<String>,
module: Module,
sdk: Sdk,
ignoredStubPackages: List<PyRequirement>,
cache: Cache<String, StubPackagesForSource>
): Set<String> {
val (sourcesToLoad, cached) = splitIntoNotCachedAndCached(forcedSourcesToProcess(sources), cache)
val (reqs, args) = toRequirementsAndExtraArgs(cached, ignoredStubPackages)
@@ -144,20 +146,21 @@ private class PyStubPackagesAdvertiser : PyInspection() {
registerProblem(file,
message,
createInstallStubPackagesQuickFix(reqs, args, module, sdk, packageManager),
createIgnorePackagesQuickFix(reqs, packageManager))
createInstallStubPackagesQuickFix(reqs, args, module, sdk),
createIgnorePackagesQuickFix(reqs))
}
return sourcesToLoad
}
private fun processCheckedPackages(file: PyFile,
sources: Set<String>,
module: Module,
sdk: Sdk,
packageManager: PyPackageManager,
ignoredStubPackages: List<PyRequirement>,
cache: Cache<String, StubPackagesForSource>): Set<String> {
private fun processCheckedPackages(
file: PyFile,
sources: Set<String>,
module: Module,
sdk: Sdk,
ignoredStubPackages: List<PyRequirement>,
cache: Cache<String, StubPackagesForSource>
): Set<String> {
val project = file.project
if (project.getUserData(BALLOON_SHOWING) == true) return emptySet()
@@ -165,7 +168,7 @@ private class PyStubPackagesAdvertiser : PyInspection() {
val (unfilteredReqs, args) = toRequirementsAndExtraArgs(cached, ignoredStubPackages)
val status = file.project.getService<PyStubPackagesInstallingStatus>()
val status = file.project.service<PyStubPackagesInstallingStatus>()
val reqs = unfilteredReqs.filterNot { status.markedAsInstalling(it.name) }
@@ -197,11 +200,11 @@ private class PyStubPackagesAdvertiser : PyInspection() {
NotificationAction.createSimpleExpiring(
if (plural) PyBundle.message("code.insight.install.type.hints.action")
else "${PyBundle.message("python.packaging.install")} $reqsToString"
) { createInstallStubPackagesQuickFix(reqs, args, module, sdk, packageManager).applyFix(project, problemDescriptor) }
) { createInstallStubPackagesQuickFix(reqs, args, module, sdk).applyFix(project, problemDescriptor) }
)
.addAction(
NotificationAction.createSimpleExpiring(PyBundle.message("code.insight.ignore.type.hints")) {
createIgnorePackagesQuickFix(reqs, packageManager).applyFix(project, problemDescriptor)
createIgnorePackagesQuickFix(reqs).applyFix(project, problemDescriptor)
}
)
.addAction(
@@ -271,21 +274,22 @@ private class PyStubPackagesAdvertiser : PyInspection() {
return requirements to args.toList()
}
private fun createInstallStubPackagesQuickFix(reqs: List<PyRequirement>,
args: List<String>,
module: Module,
sdk: Sdk,
packageManager: PyPackageManager): LocalQuickFix {
private fun createInstallStubPackagesQuickFix(
reqs: List<PyRequirement>,
args: List<String>,
module: Module,
sdk: Sdk,
): LocalQuickFix {
val project = module.project
val stubPkgNamesToInstall = reqs.mapTo(mutableSetOf()) { it.name }
val installationListener = object : RunningPackagingTasksListener(module) {
override fun started() {
project.getService<PyStubPackagesInstallingStatus>().markAsInstalling(stubPkgNamesToInstall)
project.service<PyStubPackagesInstallingStatus>().markAsInstalling(stubPkgNamesToInstall)
}
override fun finished(exceptions: List<ExecutionException>) {
val status = project.getService<PyStubPackagesInstallingStatus>()
val status = project.service<PyStubPackagesInstallingStatus>()
val stubPkgsToUninstall = PyStubPackagesCompatibilityInspection
.findIncompatibleRuntimeToStubPackages(sdk) { it.name in stubPkgNamesToInstall }
@@ -300,7 +304,7 @@ private class PyStubPackagesAdvertiser : PyInspection() {
override fun finished(exceptions: MutableList<ExecutionException>?) {
status.unmarkAsInstalling(stubPkgNamesToUninstall)
val reqsToIgnore = stubPkgsToUninstall.map { pyRequirement(it.name, PyRequirementRelation.EQ, it.version) }
addStubPackagesToIgnore(reqsToIgnore, stubPkgNamesToUninstall, project, packageManager)
addStubPackagesToIgnore(reqsToIgnore, stubPkgNamesToUninstall, project)
}
}
@@ -321,21 +325,20 @@ private class PyStubPackagesAdvertiser : PyInspection() {
return PyInstallRequirementsFix(name, sdk, reqs, args, installationListener)
}
private fun createIgnorePackagesQuickFix(reqs: List<PyRequirement>, packageManager: PyPackageManager): LocalQuickFix {
private fun createIgnorePackagesQuickFix(reqs: List<PyRequirement>): LocalQuickFix {
return object : LocalQuickFix {
override fun getFamilyName() = PyBundle.message("code.insight.ignore.packages.qfix", reqs.size)
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
this@Visitor.addStubPackagesToIgnore(reqs, reqs.mapTo(mutableSetOf()) { it.name }, project, packageManager)
this@Visitor.addStubPackagesToIgnore(reqs, reqs.mapTo(mutableSetOf()) { it.name }, project)
}
}
}
private fun addStubPackagesToIgnore(stubPackages: List<PyRequirement>,
stubPackagesNames: Set<String>,
project: Project,
packageManager: PyPackageManager) {
ignoredPackages.removeIf { packageManager.parseRequirement(it)?.name in stubPackagesNames }
project: Project) {
ignoredPackages.removeIf { PyRequirementParser.fromLine(it)?.name in stubPackagesNames }
ignoredPackages.addAll(stubPackages.map { it.presentableText })
ProjectInspectionProfileManager.getInstance(project).fireProfileChanged()

View File

@@ -8,7 +8,8 @@ import com.google.common.cache.LoadingCache
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.Service
import com.intellij.openapi.projectRoots.Sdk
import com.jetbrains.python.packaging.PyPackageManager
import com.jetbrains.python.packaging.common.PythonPackageManagementListener
import com.jetbrains.python.packaging.management.PythonPackageManager.Companion.PACKAGE_MANAGEMENT_TOPIC
import java.time.Duration
@Service
@@ -28,7 +29,11 @@ class PyStubPackagesAdvertiserCache {
init {
val connection = ApplicationManager.getApplication().messageBus.connect()
connection.subscribe(PyPackageManager.PACKAGE_MANAGER_TOPIC, PyPackageManager.Listener { cache.invalidate(it) })
connection.subscribe(PACKAGE_MANAGEMENT_TOPIC, object : PythonPackageManagementListener {
override fun packagesChanged(sdk: Sdk) {
cache.invalidate(sdk)
}
})
}
fun forSdk(sdk: Sdk): Cache<String, StubPackagesForSource> {

View File

@@ -6,6 +6,7 @@ import com.intellij.codeInspection.LocalQuickFix
import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.codeInspection.options.OptPane
import com.intellij.openapi.components.service
import com.intellij.openapi.module.ModuleUtilCore
import com.intellij.openapi.project.Project
import com.intellij.openapi.projectRoots.Sdk
@@ -77,7 +78,7 @@ class PyStubPackagesCompatibilityInspection : PyInspection() {
val nameToPkg = mutableMapOf<String, PyPackage>()
installedPackages.forEach { nameToPkg[it.name] = it }
val status = node.project.getService(PyStubPackagesInstallingStatus::class.java)
val status = node.project.service<PyStubPackagesInstallingStatus>()
findIncompatibleRuntimeToStubPackages(
sdk) { stubPkg ->

View File

@@ -2,19 +2,20 @@
package com.jetbrains.python.codeInsight.typing
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.service
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.util.CatchingConsumer
import com.intellij.webcore.packaging.PackageManagementService
import com.intellij.webcore.packaging.RepoPackage
import com.jetbrains.python.packaging.PyPIPackageUtil
import com.jetbrains.python.packaging.PyPackage
import com.jetbrains.python.packaging.common.PythonPackage
import java.util.*
import java.util.function.BiConsumer
fun loadStubPackagesForSources(sourcesToLoad: Set<String>,
sourceToPackage: Map<String, String>,
installedPackages: List<PyPackage>,
installedPackages: List<PythonPackage>,
availablePackages: List<RepoPackage>,
packageManagementService: PackageManagementService,
sdk: Sdk) {
@@ -27,15 +28,15 @@ fun loadStubPackagesForSources(sourcesToLoad: Set<String>,
sourceToStubPackagesAvailableToInstall,
packageManagementService,
BiConsumer { source, stubPackagesForSource ->
ApplicationManager.getApplication().getService(PyStubPackagesAdvertiserCache::class.java).forSdk(sdk).put(source, stubPackagesForSource)
ApplicationManager.getApplication().service<PyStubPackagesAdvertiserCache>().forSdk(sdk).put(source, stubPackagesForSource)
}
)
}
private fun sourceToInstalledRuntimeAndStubPackages(sourcesToLoad: Set<String>,
sourceToPackage: Map<String, String>,
installedPackages: List<PyPackage>): Map<String, List<Pair<PyPackage, PyPackage?>>> {
val result = mutableMapOf<String, List<Pair<PyPackage, PyPackage?>>>()
installedPackages: List<PythonPackage>): Map<String, List<Pair<PythonPackage, PythonPackage?>>> {
val result = mutableMapOf<String, List<Pair<PythonPackage, PythonPackage?>>>()
for (source in sourcesToLoad) {
val pkgName = sourceToPackage[source] ?: continue
@@ -45,7 +46,7 @@ private fun sourceToInstalledRuntimeAndStubPackages(sourcesToLoad: Set<String>,
return result
}
private fun sourceToStubPackagesAvailableToInstall(sourceToInstalledRuntimeAndStubPkgs: Map<String, List<Pair<PyPackage, PyPackage?>>>,
private fun sourceToStubPackagesAvailableToInstall(sourceToInstalledRuntimeAndStubPkgs: Map<String, List<Pair<PythonPackage, PythonPackage?>>>,
availablePackages: List<RepoPackage>): Map<String, Set<RepoPackage>> {
if (sourceToInstalledRuntimeAndStubPkgs.isEmpty()) return emptyMap()
@@ -83,9 +84,9 @@ private fun loadRequirementsAndExtraArgs(sourceToStubPackagesAvailableToInstall:
}
}
private fun installedRuntimeAndStubPackages(pkgName: String, installedPackages: List<PyPackage>): Pair<PyPackage, PyPackage?>? {
var runtime: PyPackage? = null
var stub: PyPackage? = null
private fun installedRuntimeAndStubPackages(pkgName: String, installedPackages: List<PythonPackage>): Pair<PythonPackage, PythonPackage?>? {
var runtime: PythonPackage? = null
var stub: PythonPackage? = null
val stubPkgName = "$pkgName$STUBS_SUFFIX"
val typesPkgName = "$TYPES_PREFIX$pkgName"
val typesSuffixPkgName = "$pkgName$TYPES_SUFFIX"