link/PY-85711/skip-non-python-modules-for-non-pycharm-ides

[python]: PY-85709: Support Python facet for non-PyCharm IDEs.

The following code doesn't work for anything but PyCharm.
```kotlin
ModuleRootManager.getInstance(module).sdk
```

We now call `com.jetbrains.python.module.PyModuleService.findPythonSdk` which supports both Python SDK and Facet.

[python]: PY-85711 : Do not touch non-python modules in `removeFakeModuleEntity` and do not set `inherited` for newly created modules.

1. We should never delete any module which isn't python.

2. No need to look for sdk in workspace as we already do that for all modules by reverting their SDK settings. Moreover, `InheritedSdkDependency` was wrong: it set project-level Java JDK as Python SDK

[python]: PY-85711 : Make `PyActiveSdkModuleConfigurable` filter non-python modules.

See `com.jetbrains.python.configuration.PyActiveSdkModuleConfigurable.isSuitableForModule` comment

[python]: PY-85711 : Move `isPythonModule` to `PyModuleService`.

We are going to reuse it all over the code

[python]: PY-85711 : Refactor `PyModuleService`.

Drop unneeded class


Merge-request: IJ-MR-183088
Merged-by: Ilya Kazakevich <ilya.kazakevich@jetbrains.com>

GitOrigin-RevId: 19f65ba65ea7ea8d8ff8698a64e6a3cbe6803cb0
This commit is contained in:
Ilya Kazakevich
2025-11-24 22:50:38 +00:00
committed by intellij-monorepo-bot
parent 2119408f95
commit 7a580fd48b
15 changed files with 77 additions and 66 deletions
@@ -41,7 +41,7 @@ internal object InterpreterServiceImpl : InterpreterService {
}
override suspend fun getForModule(module: Module): Interpreter? {
val pythonSdk = ModuleRootManager.getInstance(module).sdk?.takeIf { isPythonSdk(it) } ?: return null
val pythonSdk = PythonSdkUtil.findPythonSdk(module)?.takeIf { isPythonSdk(it) } ?: return null
val data = pythonSdk.getOrCreateAdditionalData()
return findInterpreter(data, pythonSdk)
@@ -4,14 +4,18 @@ package com.jetbrains.python.module;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.Consumer;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public abstract class PyModuleService {
public abstract @Nullable Sdk findPythonSdk(@NotNull Module module);
public @Nullable Sdk findPythonSdk(@NotNull Module module) {
return ModuleRootManager.getInstance(module).getSdk();
}
public void forAllFacets(@NotNull Module module, @NotNull Consumer<Object> facetConsumer) {
}
@@ -24,4 +28,6 @@ public abstract class PyModuleService {
public boolean isFileIgnored(@NotNull VirtualFile file) {
return false;
}
@ApiStatus.Internal
public abstract boolean isPythonModule(@NotNull Module module);
}
@@ -155,7 +155,7 @@ class PyPluginSdkFragment<T : AbstractPythonRunConfiguration<*>> : SettingsEdito
private fun getSelectedSdk(): Sdk? {
if (currentMode == SDK_OF_MODULE) {
((modulesCombo?.selectedItem) as? Module)?.let {
return ModuleRootManager.getInstance(it).sdk
return PythonSdkUtil.findPythonSdk(it)
}
}
else if (currentMode == SDK_FROM_LIST) {
@@ -10,6 +10,7 @@ import com.intellij.openapi.projectRoots.Sdk
import com.intellij.python.community.plugin.impl.facet.PythonFacetUtil
import com.jetbrains.python.configuration.PyActiveSdkConfigurable
import com.jetbrains.python.configuration.PyActiveSdkModuleConfigurable
import com.jetbrains.python.module.PyModuleService
import com.jetbrains.python.sdk.removeTransferredRoots
import com.jetbrains.python.sdk.transferRoots
import org.jetbrains.annotations.ApiStatus
@@ -22,7 +23,7 @@ internal class PyPluginSdkModuleConfigurable(project: Project?) : PyActiveSdkMod
}
override fun getSdk(): Sdk? {
return getSdkFromFacet(module)
return PyModuleService.getInstance().findPythonSdk(module)
}
}
}
@@ -42,13 +43,6 @@ fun setSdkToFacet(item: Sdk?, module: Module) {
}
}
@ApiStatus.Internal
fun getSdkFromFacet(module: Module): Sdk? {
val facetManager = FacetManager.getInstance(module)
val facet = facetManager.getFacetByType(MinorPythonFacet.ID)
return facet?.configuration?.sdk
}
private fun setFacetSdk(facet: MinorPythonFacet,
item: Sdk?,
module: Module) {
@@ -17,7 +17,7 @@ package com.jetbrains.python.extensions
import com.intellij.openapi.module.Module
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.roots.ModuleRootManager
import com.jetbrains.python.sdk.legacy.PythonSdkUtil
fun Module.getSdk(): Sdk? = ModuleRootManager.getInstance(this).sdk
fun Module.getSdk(): Sdk? = PythonSdkUtil.findPythonSdk(this)
@@ -6,7 +6,6 @@ import com.intellij.openapi.externalSystem.autoimport.ExternalSystemRefreshStatu
import com.intellij.openapi.project.Project
import com.intellij.openapi.project.modules
import com.intellij.openapi.projectRoots.ProjectJdkTable
import com.intellij.openapi.roots.ModuleRootManager
import com.intellij.openapi.roots.ModuleRootModificationUtil
import com.intellij.openapi.util.Key
import com.intellij.openapi.util.NlsSafe
@@ -27,6 +26,10 @@ import com.intellij.python.pyproject.model.spi.PyProjectTomlProject
import com.intellij.python.pyproject.model.spi.Tool
import com.intellij.python.pyproject.model.spi.WorkspaceName
import com.intellij.util.messages.Topic
import com.intellij.workspaceModel.ide.impl.legacyBridge.module.findModule
import com.jetbrains.python.PyNames
import com.jetbrains.python.module.PyModuleService
import com.jetbrains.python.sdk.legacy.PythonSdkUtil
import com.jetbrains.python.venvReader.Directory
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.sync.Mutex
@@ -70,7 +73,7 @@ internal suspend fun linkProject(project: Project, projectModelRoot: Path) {
val entries = generatePyProjectTomlEntries(files, excludeDirs)
if (entries.isNotEmpty()) {
val sdks = project.modules.associate { Pair(it.name, ModuleRootManager.getInstance(it).sdk?.name) }
val sdks = project.modules.associate { Pair(it.name, PythonSdkUtil.findPythonSdk(it)?.name) }
project.workspaceModel.currentSnapshot.entities(ModuleEntity::class.java)
unlinkProjectImpl(project, externalProjectPath)
@@ -90,7 +93,7 @@ internal suspend fun linkProject(project: Project, projectModelRoot: Path) {
// Restore SDK assoc
for (module in project.modules) {
if (ModuleRootManager.getInstance(module).sdk == null) {
if (PythonSdkUtil.findPythonSdk(module) == null) {
val sdkName = sdks[module.name] ?: continue
ProjectJdkTable.getInstance().findJdk(sdkName)?.let { sdk ->
ModuleRootModificationUtil.setModuleSdk(module, sdk)
@@ -177,17 +180,8 @@ private suspend fun createEntityStorage(
val fileUrlManager = project.workspaceModel.getVirtualFileUrlManager()
val storage = MutableEntityStorage.create()
for (pyProject in graph) {
val existingModuleEntity = project.workspaceModel.currentSnapshot
.entitiesBySource { it is PyProjectTomlEntitySource }
.filterIsInstance<ModuleEntity>()
.find { it.name == pyProject.name.name }
val existingSdkEntity = existingModuleEntity
?.dependencies
?.find { it is SdkDependency } as? SdkDependency
val sdkDependency = existingSdkEntity ?: InheritedSdkDependency
val entitySource = PyProjectTomlEntitySource(pyProject.tomlFile.toVirtualFileUrl(virtualFileUrlManager))
val moduleEntity = storage addEntity ModuleEntity(pyProject.name.name, emptyList(), entitySource) {
dependencies += sdkDependency
dependencies += ModuleSourceDependency
for (moduleName in pyProject.dependencies) {
dependencies += ModuleDependency(ModuleId(moduleName.name), true, DependencyScope.COMPILE, false)
@@ -210,6 +204,7 @@ private suspend fun createEntityStorage(
}
}
type = PYTHON_MODULE_ID_DATA_CLASS
pyProjectTomlEntity = PyProjectTomlWorkspaceEntity(participatedTools = participatedTools, pyProject.tomlFile.parent.toVirtualFileUrl(fileUrlManager), entitySource)
exModuleOptions = ExternalSystemModuleOptionsEntity(entitySource) {
externalSystem = PYTHON_SOURCE_ROOT_TYPE.name
@@ -220,6 +215,7 @@ private suspend fun createEntityStorage(
return@withContext storage
}
private class PyProjectTomlEntitySource(tomlFile: VirtualFileUrl) : EntitySource {
override val virtualFileUrl: VirtualFileUrl = tomlFile
}
@@ -247,9 +243,14 @@ private data class PyProjectTomlBasedEntryImpl(
* @see com.intellij.openapi.project.impl.getOrInitializeModule
*/
private fun removeFakeModuleEntity(storage: MutableEntityStorage, modulesToRemove: Set<String>) {
val moduleService = PyModuleService.getInstance()
val contentRoots = storage
.entitiesBySource { it !is PyProjectTomlEntitySource }
.filterIsInstance<ContentRootEntity>()
.filter {
val module = it.module.findModule(storage) ?: return@filter false
moduleService.isPythonModule(module)
}
.toList()
for (entity in contentRoots) {
if (entity.module.name in modulesToRemove) {
@@ -285,4 +286,6 @@ private suspend fun findSrc(root: Directory): Set<Directory> =
withContext(Dispatchers.IO) {
val src = root.resolve("src")
if (src.exists()) setOf(src) else emptySet()
}
}
private val PYTHON_MODULE_ID_DATA_CLASS: ModuleTypeId = ModuleTypeId(PyNames.PYTHON_MODULE_ID)
@@ -25,6 +25,7 @@ import com.jetbrains.python.sdk.configuration.CreateSdkInfo
import com.jetbrains.python.sdk.configuration.CreateSdkInfoWithTool
import com.jetbrains.python.sdk.configuration.PyProjectSdkConfigurationExtension
import com.jetbrains.python.sdk.getOrCreateAdditionalData
import com.jetbrains.python.sdk.legacy.PythonSdkUtil
import com.jetbrains.python.sdk.setAssociationToPath
import com.jetbrains.python.venvReader.Directory
import kotlinx.collections.immutable.toPersistentList
@@ -108,7 +109,7 @@ internal class ModulesSdkConfigurator private constructor(
val tools = PyProjectSdkConfigurationExtension.createMap()
val limit = Semaphore(permits = Registry.intValue("intellij.python.sdkConfigurator.backend.sdk.parallel"))
val now = System.currentTimeMillis()
val resultDef = project.modules.filter { ModuleRootManager.getInstance(it).sdk == null }.map { module ->
val resultDef = project.modules.filter { PythonSdkUtil.findPythonSdk(it) == null }.map { module ->
limit.withPermit {
async {
val moduleInfo = getModuleInfo(module, tools) ?: return@async null
@@ -190,7 +191,7 @@ internal class ModulesSdkConfigurator private constructor(
} // Link workspace members with their workspace
val reportedBrokenModules = mutableSetOf<Module>()
for ((module, parentModule) in modulesWithSameSdk) {
val parentSdk = ModuleRootManager.getInstance(parentModule).sdk
val parentSdk = PythonSdkUtil.findPythonSdk(module)
if (parentSdk != null) {
ModuleRootModificationUtil.setModuleSdk(module, parentSdk) // This SDK is shared, no need to associate it
// TODO: Support association with multiple modules
@@ -97,13 +97,7 @@ public final class PythonSdkUtil {
if (module == null || module.isDisposed()) {
return null;
}
Sdk sdk = ModuleRootManager.getInstance(module).getSdk();
if (sdk != null && isPythonSdk(sdk)) {
return sdk;
}
sdk = PyModuleService.getInstance().findPythonSdk(module);
var sdk = PyModuleService.getInstance().findPythonSdk(module);
if (sdk != null && isPythonSdk(sdk)) {
return sdk;
}
@@ -282,7 +282,7 @@ public class PyActiveSdkConfigurable implements UnnamedConfigurable {
sdk = ProjectRootManager.getInstance(myProject).getProjectSdk();
}
else {
sdk = ModuleRootManager.getInstance(myModule).getSdk();
sdk = com.jetbrains.python.sdk.PythonSdkUtil.findPythonSdk(myModule);
}
if (sdk != null && PythonSdkUtil.isPythonSdk(sdk)) {
@@ -6,20 +6,29 @@ import com.intellij.openapi.module.Module;
import com.intellij.openapi.options.UnnamedConfigurable;
import com.intellij.openapi.project.Project;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.module.PyModuleService;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
// Inherit in the module you are going to use it
@ApiStatus.Internal
public abstract class PyActiveSdkModuleConfigurable extends ModuleAwareProjectConfigurable {
public abstract class PyActiveSdkModuleConfigurable extends ModuleAwareProjectConfigurable<UnnamedConfigurable> {
private final Project myProject;
protected PyActiveSdkModuleConfigurable(Project project) {
super(project, PyBundle.message("configurable.PyActiveSdkModuleConfigurable.python.interpreter.display.name"), "reference.settings.project.interpreter");
super(project, PyBundle.message("configurable.PyActiveSdkModuleConfigurable.python.interpreter.display.name"),
"reference.settings.project.interpreter");
myProject = project;
}
@Override
protected final boolean isSuitableForModule(@NotNull Module module) {
// One can't configure Python SDK for a random module as random module doesn't have a `baseDir`
// which is a requirement for various SDK types.
return PyModuleService.getInstance().isPythonModule(module);
}
@Override
protected @NotNull UnnamedConfigurable createModuleConfigurable(Module module) {
return new PyActiveSdkConfigurable(module);
@@ -1,5 +0,0 @@
// Copyright 2000-2019 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.module;
public abstract class PyModuleServiceEx extends PyModuleService {
}
@@ -6,14 +6,18 @@ import com.intellij.facet.FacetConfiguration;
import com.intellij.facet.FacetManager;
import com.intellij.openapi.fileTypes.FileTypeManager;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleType;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.Consumer;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.PythonModuleTypeBase;
import com.jetbrains.python.facet.PythonFacetSettings;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
final class PyModuleServiceImpl extends PyModuleServiceEx {
final class PyModuleServiceImpl extends PyModuleService {
@Override
public boolean isFileIgnored(@NotNull VirtualFile file) {
@@ -22,13 +26,17 @@ final class PyModuleServiceImpl extends PyModuleServiceEx {
@Override
public @Nullable Sdk findPythonSdk(@NotNull Module module) {
var sdk = super.findPythonSdk(module);
if (sdk != null) {
return sdk;
}
for (Facet<?> facet : FacetManager.getInstance(module).getAllFacets()) {
final FacetConfiguration configuration = facet.getConfiguration();
if (configuration instanceof PythonFacetSettings) {
return ((PythonFacetSettings)configuration).getSdk();
}
}
return null;
return ModuleRootManager.getInstance(module).getSdk();
}
@Override
@@ -37,4 +45,19 @@ final class PyModuleServiceImpl extends PyModuleServiceEx {
facetConsumer.consume(f);
}
}
@Override
public boolean isPythonModule(@NotNull Module module) {
ModuleType<?> type = ModuleType.get(module);
if (type instanceof PythonModuleTypeBase || type.getId().equals(PyNames.PYTHON_MODULE_ID)) {
return true;
}
final Facet<?>[] allFacets = FacetManager.getInstance(module).getAllFacets();
for (Facet<?> facet : allFacets) {
if (facet.getConfiguration() instanceof PythonFacetSettings) {
return true;
}
}
return false;
}
}
@@ -3,13 +3,13 @@ package com.jetbrains.python.run.configuration
import com.intellij.openapi.module.Module
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.roots.ModuleRootManager
import com.intellij.openapi.ui.ComboBox
import com.intellij.openapi.util.Computable
import com.jetbrains.python.PyBundle
import com.jetbrains.python.run.AbstractPythonRunConfigurationParams
import com.jetbrains.python.sdk.PySdkListCellRenderer
import com.jetbrains.python.sdk.legacy.PythonSdkUtil
import com.jetbrains.python.sdk.pythonSdk
import java.util.function.Consumer
class PySdkComboBox(private val addDefault: Boolean,
@@ -48,7 +48,7 @@ class PySdkComboBox(private val addDefault: Boolean,
}
private fun updateDefaultInterpreter(module: Module?) {
val sdk = if (module == null) null else ModuleRootManager.getInstance(module).sdk
val sdk = module?.pythonSdk
setRenderer(
if (sdk == null) PySdkListCellRenderer()
else PySdkListCellRenderer(PyBundle.message("python.sdk.rendering.project.default.0", sdk.name), sdk)
@@ -3,11 +3,8 @@ package com.jetbrains.python.testing;
import com.intellij.execution.Location;
import com.intellij.execution.actions.ConfigurationContext;
import com.intellij.facet.Facet;
import com.intellij.facet.FacetManager;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.module.ModuleType;
import com.intellij.openapi.module.ModuleUtilCore;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ProjectRootManager;
@@ -17,8 +14,7 @@ import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.ThreeState;
import com.jetbrains.python.PythonModuleTypeBase;
import com.jetbrains.python.facet.PythonFacetSettings;
import com.jetbrains.python.module.PyModuleService;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.types.TypeEvalContext;
import com.jetbrains.python.run.RunnableScriptFilter;
@@ -108,6 +104,7 @@ public abstract class PythonTestLegacyConfigurationProducer<T extends AbstractPy
if (RunnableScriptFilter.isIfNameMain(location)) return false;
final Module module = location.getModule();
if (module == null) return false;
if (!isPythonModule(module)) return false;
if (element instanceof PsiDirectory) {
@@ -231,20 +228,8 @@ public abstract class PythonTestLegacyConfigurationProducer<T extends AbstractPy
return true;
}
protected static boolean isPythonModule(Module module) {
if (module == null) {
return false;
}
if (ModuleType.get(module) instanceof PythonModuleTypeBase) {
return true;
}
final Facet[] allFacets = FacetManager.getInstance(module).getAllFacets();
for (Facet facet : allFacets) {
if (facet.getConfiguration() instanceof PythonFacetSettings) {
return true;
}
}
return false;
protected static boolean isPythonModule(@NotNull Module module) {
return PyModuleService.getInstance().isPythonModule(module);
}
protected List<PyStatement> getTestCaseClassesFromFile(final @NotNull PyFile pyFile) {
@@ -53,6 +53,7 @@ public final class PythonDocTestConfigurationProducer extends PythonTestLegacyCo
@Override
protected boolean isAvailable(final @NotNull Location location) {
final Module module = location.getModule();
if (module == null) return false;
if (!isPythonModule(module)) return false;
final PsiElement element = location.getPsiElement();
if (element instanceof PsiFile) {