[python] refactor stub suggestions to use PythonPackageManager

GitOrigin-RevId: d370e9fb005f850246a377ae10e4e87ba924efbd
This commit is contained in:
Morgan Bartholomew
2025-07-14 10:11:59 +00:00
committed by intellij-monorepo-bot
parent 376cb69c39
commit b3cd91977f
5 changed files with 54 additions and 44 deletions
@@ -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)
@@ -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,8 +105,8 @@ 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)
@@ -113,10 +114,10 @@ private class PyStubPackagesAdvertiser : PyInspection() {
if (availablePackages.isEmpty()) return
val ignoredStubPackages = (IGNORE + ignoredPackages).mapNotNull { PyRequirementParser.fromLine(it) }
val cache = ApplicationManager.getApplication().getService<PyStubPackagesAdvertiserCache>().forSdk(sdk)
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),
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,7 +200,7 @@ 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")) {
@@ -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 }
@@ -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> {
@@ -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 ->
@@ -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"