From 032935307a947f3f93ca306ab00d72ff83f829e8 Mon Sep 17 00:00:00 2001 From: David Lysenko Date: Mon, 6 Jul 2026 15:06:21 +0000 Subject: [PATCH] [pycharm] PY-89747 Add DependenciesInspectionProvider impl for legacy Poetry fixup! [pycharm] PY-89747 Add DependenciesInspectionProvider impl for legacy Poetry [pycharm] PY-89747 Add DependenciesInspectionProvider impl for legacy Poetry Co-authored-by: Morgan Bartholomew Merge-request: IJ-MR-211850 Merged-by: David Lysenko (cherry picked from commit 147e19a3dea8c6a322f9a4a0dbf06b5972710819) IJ-MR-211850 GitOrigin-RevId: 8f5aa63e3381746ee70b216a5198524ad4bebdc6 --- python/BUILD.bazel | 2 + python/intellij.python.community.tests.iml | 1 + .../intellij.python.community.impl.xml | 6 +- .../messages/PyBundle.properties | 2 - .../dependencies/DependenciesInspection.kt | 37 ++++++--- .../DependenciesInspectionProvider.kt | 2 +- python/src/com/jetbrains/python/psi/PsiExt.kt | 11 ++- .../python/sdk/pipenv/PipEnvParser.kt | 12 +-- ...acyPoetryDependenciesInspectionProvider.kt | 51 ++++++++++++ .../poetry/PoetryPackageVersionsInspection.kt | 80 ------------------- .../pyproject.toml | 18 +++++ ...CondaDependenciesInspectionProviderTest.kt | 19 ----- ...oetryDependenciesInspectionProviderTest.kt | 79 ++++++++++++++++++ .../requirements/PythonDependencyTestCase.kt | 5 +- 14 files changed, 197 insertions(+), 128 deletions(-) create mode 100644 python/src/com/jetbrains/python/sdk/poetry/LegacyPoetryDependenciesInspectionProvider.kt delete mode 100644 python/src/com/jetbrains/python/sdk/poetry/PoetryPackageVersionsInspection.kt create mode 100644 python/testData/poetry/legacy/inspections/LegacyPoetryDependenciesInspectionProvider/pyproject.toml create mode 100644 python/testSrc/com/intellij/python/junit5Tests/env/poetry/legacy/LegacyPoetryDependenciesInspectionProviderTest.kt diff --git a/python/BUILD.bazel b/python/BUILD.bazel index 77e640df95a2..77050ae7f6da 100644 --- a/python/BUILD.bazel +++ b/python/BUILD.bazel @@ -1407,6 +1407,8 @@ jvm_library( "//libraries/junit4:junit4_test_lib", "//plugins/toml/core", "//plugins/toml/core:core_test_lib", + "//python/python-poetry/backend", + "//python/python-poetry/backend:backend_test_lib", ], ) ### auto-generated section `build intellij.python.community.tests` end diff --git a/python/intellij.python.community.tests.iml b/python/intellij.python.community.tests.iml index e296fb9c07fc..bddb9f532b4d 100644 --- a/python/intellij.python.community.tests.iml +++ b/python/intellij.python.community.tests.iml @@ -140,6 +140,7 @@ + \ No newline at end of file diff --git a/python/pluginResources/intellij.python.community.impl.xml b/python/pluginResources/intellij.python.community.impl.xml index 72bc462e272f..06b7f3830475 100644 --- a/python/pluginResources/intellij.python.community.impl.xml +++ b/python/pluginResources/intellij.python.community.impl.xml @@ -55,10 +55,6 @@ implementation="com.jetbrains.python.requirements.widget.PyDependenciesFileStatusBarWidgetFactory" order="after pythonInterpreterWidget, before git, before hg, before Notifications"/> - + diff --git a/python/pluginResources/messages/PyBundle.properties b/python/pluginResources/messages/PyBundle.properties index 8c3a753867b3..d95072dcb38b 100644 --- a/python/pluginResources/messages/PyBundle.properties +++ b/python/pluginResources/messages/PyBundle.properties @@ -1284,8 +1284,6 @@ INSP.settings.bdd.behave.specific=Incorrect BDD Behave-specific definitions INSP.settings.bdd.step.definition.arguments=Incorrect arguments in step definition functions python.compatibility.inspection.advertiser.notifications.group.title=Python compatibility inspection recommended python.compatibility.inspection.advertiser.notifications.title=Python versions compatibility -INSP.poetry.package.versions.display.name=Outdated Poetry package versions -INSP.uv.package.versions.display.name=Outdated uv package versions # Both placeholders are Python versions like 2.7, 3.7, etc. python.compatibility.inspection.advertiser.version.stale.python3.version.warning.message=\ diff --git a/python/src/com/jetbrains/python/inspections/dependencies/DependenciesInspection.kt b/python/src/com/jetbrains/python/inspections/dependencies/DependenciesInspection.kt index 1ab5814385a9..50444c6b4d05 100644 --- a/python/src/com/jetbrains/python/inspections/dependencies/DependenciesInspection.kt +++ b/python/src/com/jetbrains/python/inspections/dependencies/DependenciesInspection.kt @@ -72,15 +72,30 @@ class DependenciesInspection : LocalInspectionTool() { return } - val (provider, dependencies) = DependenciesInspectionProviderData.providers.firstNotNullOfOrNull { provider -> - provider.getDependencies(psiFile)?.let { provider to it } - } ?: return + val eligibleProviders = + DependenciesInspectionProviderData + .providers + .mapNotNull { provider -> + provider + .getDependencies(psiFile) + ?.let { provider to it } + } - if (psiFile.injectionParent() == null) { - verifyNonEmptyFile(psiFile, provider, packageManager) + if (eligibleProviders.isEmpty()) { + return } - packageManager.verifyPackageManager(dependencies) + val finalDependenciesMap = mutableMapOf() + + for ((provider, dependencies) in eligibleProviders) { + if (psiFile.injectionParent() == null) { + verifyNonEmptyFile(psiFile, provider, packageManager) + } + + finalDependenciesMap += dependencies + } + + packageManager.verifyPackageManager(finalDependenciesMap) } private fun verifyNonEmptyFile(psiFile: PsiFile, provider: DependenciesInspectionProvider<*>, packageManager: PythonPackageManager) { @@ -88,12 +103,16 @@ class DependenciesInspection : LocalInspectionTool() { return } - val dependenciesExporter = packageManager.dependenciesExporter - val fixes = dependenciesExporter?.let { arrayOf(ExportDependenciesQuickFix(it)) } ?: emptyArray() + val fixes = + packageManager + .dependenciesExporter + ?.let { arrayOf(ExportDependenciesQuickFix(it)) } + ?: emptyArray() + val inspectionMessage = provider.emptyFileInspectionMessage ?: return holder.registerProblem( psiFile, - provider.emptyFileInspectionMessage, + inspectionMessage, ProblemHighlightType.GENERIC_ERROR_OR_WARNING, *fixes, ) diff --git a/python/src/com/jetbrains/python/inspections/dependencies/DependenciesInspectionProvider.kt b/python/src/com/jetbrains/python/inspections/dependencies/DependenciesInspectionProvider.kt index ff44b93f71f1..55a0b09db29d 100644 --- a/python/src/com/jetbrains/python/inspections/dependencies/DependenciesInspectionProvider.kt +++ b/python/src/com/jetbrains/python/inspections/dependencies/DependenciesInspectionProvider.kt @@ -13,7 +13,7 @@ typealias DependenciesMap = Map @ApiStatus.Internal abstract class DependenciesInspectionProvider(internal val `class`: Class) { protected abstract fun provideDependencies(file: T): DependenciesMap? - abstract val emptyFileInspectionMessage: @InspectionMessage String + abstract val emptyFileInspectionMessage: @InspectionMessage String? fun getDependencies(file: PsiFile): DependenciesMap? = @Suppress("UNCHECKED_CAST") diff --git a/python/src/com/jetbrains/python/psi/PsiExt.kt b/python/src/com/jetbrains/python/psi/PsiExt.kt index c987e914c952..c48f9e7d1c90 100644 --- a/python/src/com/jetbrains/python/psi/PsiExt.kt +++ b/python/src/com/jetbrains/python/psi/PsiExt.kt @@ -4,6 +4,15 @@ package com.jetbrains.python.psi import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile import com.intellij.psi.impl.source.resolve.FileContextUtil +import org.toml.lang.psi.TomlLiteral +import org.toml.lang.psi.ext.TomlLiteralKind +import org.toml.lang.psi.ext.kind internal fun PsiFile.injectionParent(): PsiElement? = - getUserData(FileContextUtil.INJECTED_IN_ELEMENT)?.element \ No newline at end of file + getUserData(FileContextUtil.INJECTED_IN_ELEMENT)?.element + +internal fun TomlLiteral.getStringOrNull(): String? = + when (val kind = kind) { + is TomlLiteralKind.String -> kind.value + else -> null + } diff --git a/python/src/com/jetbrains/python/sdk/pipenv/PipEnvParser.kt b/python/src/com/jetbrains/python/sdk/pipenv/PipEnvParser.kt index 97d5b39df338..ed0cb8bb3f40 100644 --- a/python/src/com/jetbrains/python/sdk/pipenv/PipEnvParser.kt +++ b/python/src/com/jetbrains/python/sdk/pipenv/PipEnvParser.kt @@ -10,20 +10,16 @@ import com.intellij.util.concurrency.annotations.RequiresBackgroundThread import com.jetbrains.python.inspections.dependencies.DependenciesMap import com.jetbrains.python.packaging.PyRequirement import com.jetbrains.python.packaging.PyRequirementParser -import org.jetbrains.annotations.ApiStatus +import com.jetbrains.python.psi.getStringOrNull import org.toml.lang.psi.TomlFile import org.toml.lang.psi.TomlInlineTable import org.toml.lang.psi.TomlLiteral import org.toml.lang.psi.TomlTable -import org.toml.lang.psi.ext.TomlLiteralKind import org.toml.lang.psi.ext.getValueByKey -import org.toml.lang.psi.ext.kind - private val dependencyHeaders = setOf("packages", "dev-packages") private const val versionKey = "version" -@ApiStatus.Internal internal object PipEnvParser { private val gson = Gson() @@ -80,9 +76,3 @@ internal object PipEnvParser { } } } - -private fun TomlLiteral.getStringOrNull(): String? = - when (val kind = kind) { - is TomlLiteralKind.String -> kind.value - else -> null - } diff --git a/python/src/com/jetbrains/python/sdk/poetry/LegacyPoetryDependenciesInspectionProvider.kt b/python/src/com/jetbrains/python/sdk/poetry/LegacyPoetryDependenciesInspectionProvider.kt new file mode 100644 index 000000000000..d1576cf1a866 --- /dev/null +++ b/python/src/com/jetbrains/python/sdk/poetry/LegacyPoetryDependenciesInspectionProvider.kt @@ -0,0 +1,51 @@ +// Copyright 2000-2026 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.codeInspection.util.InspectionMessage +import com.intellij.psi.PsiFile +import com.intellij.python.pyproject.PY_PROJECT_TOML +import com.jetbrains.python.inspections.dependencies.DependenciesInspectionProvider +import com.jetbrains.python.inspections.dependencies.DependenciesMap +import com.jetbrains.python.packaging.PyRequirementParser +import com.jetbrains.python.psi.getStringOrNull +import com.jetbrains.python.requirements.getPythonSdk +import org.toml.lang.psi.TomlFile +import org.toml.lang.psi.TomlKeyValue +import org.toml.lang.psi.TomlLiteral +import org.toml.lang.psi.TomlTable + +private val poetryGroupRegex = Regex("""^tool\.poetry\.group\.[^.]*\.dependencies$""") +private val legacyPoetryDependencyHeaders = setOf("tool.poetry.dependencies", "tool.poetry.dev-dependencies") + +internal class LegacyPoetryDependenciesInspectionProvider : DependenciesInspectionProvider(TomlFile::class.java) { + override fun provideDependencies(file: TomlFile): DependenciesMap? { + if (file.name != PY_PROJECT_TOML || !isPoetryProject(file)) { + return null + } + + return file + .children + .filterIsInstance() + .filter { + it.header.key?.text?.let { text -> + text in legacyPoetryDependencyHeaders || poetryGroupRegex matches text + } == true + } + .flatMap { it.children.filterIsInstance() } + .mapNotNull { keyValue -> + val versionString = + (keyValue.value as? TomlLiteral)?.getStringOrNull() + ?: return@mapNotNull null + + (PyRequirementParser.fromLine("${keyValue.key.text}${versionString}") + ?: PyRequirementParser.fromLine(keyValue.key.text)) + ?.let { pyRequirement -> pyRequirement to keyValue } + } + .toMap() + } + + override val emptyFileInspectionMessage: @InspectionMessage String? = null + + private fun isPoetryProject(psiFile: PsiFile) = + getPythonSdk(psiFile)?.isPoetry == true +} diff --git a/python/src/com/jetbrains/python/sdk/poetry/PoetryPackageVersionsInspection.kt b/python/src/com/jetbrains/python/sdk/poetry/PoetryPackageVersionsInspection.kt deleted file mode 100644 index 1b875d293232..000000000000 --- a/python/src/com/jetbrains/python/sdk/poetry/PoetryPackageVersionsInspection.kt +++ /dev/null @@ -1,80 +0,0 @@ -// 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.codeInspection.LocalInspectionTool -import com.intellij.codeInspection.LocalInspectionToolSession -import com.intellij.codeInspection.ProblemHighlightType -import com.intellij.codeInspection.ProblemsHolder -import com.intellij.openapi.module.Module -import com.intellij.openapi.module.ModuleManager -import com.intellij.openapi.module.ModuleUtilCore -import com.intellij.openapi.progress.runBlockingMaybeCancellable -import com.intellij.openapi.vfs.VirtualFile -import com.intellij.psi.PsiElement -import com.intellij.psi.PsiElementVisitor -import com.intellij.psi.PsiFile -import com.intellij.python.pyproject.PY_PROJECT_TOML -import com.intellij.util.concurrency.annotations.RequiresBackgroundThread -import com.jetbrains.python.PyBundle -import com.jetbrains.python.packaging.PyPackageName -import com.jetbrains.python.packaging.management.PythonPackageManager -import com.jetbrains.python.sdk.findAmongRoots -import com.jetbrains.python.sdk.legacy.PythonSdkUtil -import org.toml.lang.psi.TomlKeyValue -import org.toml.lang.psi.TomlTable - -/** - * This source code is created by @koxudaxi Koudai Aono - */ - -internal class PoetryPackageVersionsInspection : LocalInspectionTool() { - override fun buildVisitor( - holder: ProblemsHolder, - isOnTheFly: Boolean, - session: LocalInspectionToolSession, - ): PsiElementVisitor { - return PoetryFileVisitor(holder) - } - - class PoetryFileVisitor( - val holder: ProblemsHolder, - ) : PsiElementVisitor() { - @RequiresBackgroundThread - private fun guessModule(element: PsiElement): Module? { - return ModuleUtilCore.findModuleForPsiElement(element) - ?: ModuleManager.getInstance(element.project).modules.let { if (it.size != 1) null else it[0] } - } - - @RequiresBackgroundThread - private fun Module.pyProjectTomlBlocking(): VirtualFile? = runBlockingMaybeCancellable { - findAmongRoots(this@pyProjectTomlBlocking, PY_PROJECT_TOML) - } - - val poetryGroupRegex = Regex("""^tool\.poetry\.group\.[^.]*\.dependencies$""") - - @RequiresBackgroundThread - override fun visitFile(psiFile: PsiFile) { - val module = guessModule(psiFile) ?: return - val sdk = PythonSdkUtil.findPythonSdk(module) ?: return - if (!sdk.isPoetry) return - if (psiFile.virtualFile != module.pyProjectTomlBlocking()) return - psiFile.children - .filter { element -> - (element as? TomlTable)?.header?.key?.text?.let { key -> - key in listOf("tool.poetry.dependencies", "tool.poetry.dev-dependencies") || - poetryGroupRegex matches key - } ?: false - }.flatMap { - it.children.mapNotNull { line -> line as? TomlKeyValue } - }.forEach { keyValue -> - val packageName = PyPackageName.normalizePackageName(keyValue.key.text) - val outdatedVersion = PythonPackageManager.forSdk(module.project, sdk).listOutdatedPackagesSnapshot()[packageName] - if (outdatedVersion != null) { - val message = PyBundle.message("python.sdk.inspection.message.version.outdated.latest", - packageName, outdatedVersion.version, outdatedVersion.latestVersion) - holder.registerProblem(keyValue, message, ProblemHighlightType.WARNING) - } - } - } - } -} \ No newline at end of file diff --git a/python/testData/poetry/legacy/inspections/LegacyPoetryDependenciesInspectionProvider/pyproject.toml b/python/testData/poetry/legacy/inspections/LegacyPoetryDependenciesInspectionProvider/pyproject.toml new file mode 100644 index 000000000000..0427f79fb73e --- /dev/null +++ b/python/testData/poetry/legacy/inspections/LegacyPoetryDependenciesInspectionProvider/pyproject.toml @@ -0,0 +1,18 @@ +[tool.poetry] +name = "pythonproject15" +version = "0.1.0" +description = "" +readme = "README.md" + +[tool.poetry.dependencies] +django = "=1.3.1" +numpy = "*" +pandas = "=1.0.0" + +[tool.poetry.group.dev.dependencies] +flask = "=1.0" +requests = "=1.22.0" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/python/testSrc/com/intellij/python/junit5Tests/env/conda/CondaDependenciesInspectionProviderTest.kt b/python/testSrc/com/intellij/python/junit5Tests/env/conda/CondaDependenciesInspectionProviderTest.kt index 40e93aff7e45..49b10b169f49 100644 --- a/python/testSrc/com/intellij/python/junit5Tests/env/conda/CondaDependenciesInspectionProviderTest.kt +++ b/python/testSrc/com/intellij/python/junit5Tests/env/conda/CondaDependenciesInspectionProviderTest.kt @@ -60,23 +60,4 @@ class CondaDependenciesInspectionProviderTest : PythonDependencyTestCase() { } override fun getBasePath(): String = "/community/python/testData/conda/environmentYml/inspections/" - - /** - * Marks the SDK as tracking `environment.yml` as its declared-dependencies root (associating it with - * the module dir that holds the file) so the package manager's dependency-file cache tracks it — the - * gate in [com.jetbrains.python.inspections.dependencies.DependenciesInspection] reads that cache. The - * real conda manager exposes `environment.yml` as its root unconditionally; the test manager keys this - * off SDK user data. Init is then forced so the cache is seeded before highlighting runs. - */ - private fun markEnvironmentYmlAsDependencyRoot() { - val sdk = myFixture.project.pythonSdk!! - sdk.putUserData(TestPythonPackageManager.REQUIREMENTS_PROVIDER_KEY, RequirementsProviderType.ENVIRONMENT_YML) - val moduleDir = myFixture.findFileInTempDir("environment.yml").parent - ApplicationManager.getApplication().runWriteAction { - val modificator = sdk.sdkModificator - (modificator.sdkAdditionalData as PythonSdkAdditionalData).associatedModulePath = moduleDir.path - modificator.commitChanges() - } - runBlocking { PythonPackageManager.forSdk(myFixture.project, sdk).waitForInit() } - } } diff --git a/python/testSrc/com/intellij/python/junit5Tests/env/poetry/legacy/LegacyPoetryDependenciesInspectionProviderTest.kt b/python/testSrc/com/intellij/python/junit5Tests/env/poetry/legacy/LegacyPoetryDependenciesInspectionProviderTest.kt new file mode 100644 index 000000000000..ad15cf9edddd --- /dev/null +++ b/python/testSrc/com/intellij/python/junit5Tests/env/poetry/legacy/LegacyPoetryDependenciesInspectionProviderTest.kt @@ -0,0 +1,79 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.python.junit5Tests.env.poetry.legacy + +import com.intellij.codeInspection.ex.InspectionProfileImpl +import com.intellij.testFramework.TestDataPath +import com.jetbrains.python.PyBundle +import com.jetbrains.python.inspections.dependencies.DependenciesInspection +import com.jetbrains.python.packaging.common.PythonOutdatedPackage +import com.jetbrains.python.packaging.common.PythonPackage +import com.jetbrains.python.packaging.management.RequirementsProviderType +import com.jetbrains.python.packaging.management.TestPackageManagerProvider +import com.jetbrains.python.requirements.PythonDependencyTestCase +import com.jetbrains.python.sdk.PythonSdkAdditionalData +import com.jetbrains.python.sdk.poetry.PyPoetrySdkAdditionalData +import com.jetbrains.python.sdk.pythonSdk + +@TestDataPath($$"$CONTENT_ROOT/../testData/poetry/legacy/inspections") +internal class LegacyPoetryDependenciesInspectionProviderTest : PythonDependencyTestCase() { + fun testLegacyPoetryDependenciesInspectionProvider() { + val provider = + TestPackageManagerProvider() + .withPackageInstalled( + PythonPackage("python", "3.9", false), + PythonPackage("django", "1.3.1", false), + PythonPackage("flask", "1.0", false), + PythonPackage("requests", "1.22.0", false) + ) + .withOutdatedPackages( + PythonOutdatedPackage("django", "1.3.1", "5.0.0"), + PythonOutdatedPackage("flask", "1.0", "3.0.0"), + ) + initTestPackageManager(provider) + + myFixture.copyDirectoryToProject(getTestName(false), "") + setDependencyRoot(RequirementsProviderType.PYPROJECT_TOML) + myFixture.configureFromTempProjectFile("pyproject.toml") + + myFixture.enableInspections(DependenciesInspection::class.java) + myFixture.checkHighlighting(true, false, true, false) + + for (pkg in listOf("django", "flask")) { + val offset = myFixture.editor.document.text.indexOf("$pkg =") + myFixture.editor.caretModel.moveToOffset(offset) + + val availableIntentions = myFixture.availableIntentions.map { it.text } + val updateFixText = PyBundle.message("QFIX.NAME.update.requirement", pkg) + + assertContainsElements(availableIntentions, updateFixText) + assertContainsElements(availableIntentions, PyBundle.message("QFIX.NAME.update.all.requirements")) + } + + for (pkg in listOf("numpy")) { + val offset = myFixture.editor.document.text.indexOf("$pkg =") + myFixture.editor.caretModel.moveToOffset(offset) + + val availableIntentions = myFixture.availableIntentions.map { it.text } + val installFixText = PyBundle.message("QFIX.NAME.install.requirement", pkg) + + assertContainsElements(availableIntentions, installFixText) + assertContainsElements(availableIntentions, PyBundle.message("QFIX.NAME.install.all.requirements")) + } + } + + override fun setUp() { + super.setUp() + InspectionProfileImpl.INIT_INSPECTIONS = true + myFixture.project.pythonSdk = projectDescriptor.sdk + } + + override fun tearDown() { + InspectionProfileImpl.INIT_INSPECTIONS = false + super.tearDown() + } + + override val sdkAdditionalData: PythonSdkAdditionalData + get() = PyPoetrySdkAdditionalData(null) + + override fun getBasePath(): String = "/community/python/testData/poetry/legacy/inspections/" +} diff --git a/python/testSrc/com/jetbrains/python/requirements/PythonDependencyTestCase.kt b/python/testSrc/com/jetbrains/python/requirements/PythonDependencyTestCase.kt index c89d41c36fd7..9586aba4c789 100644 --- a/python/testSrc/com/jetbrains/python/requirements/PythonDependencyTestCase.kt +++ b/python/testSrc/com/jetbrains/python/requirements/PythonDependencyTestCase.kt @@ -6,6 +6,7 @@ import com.intellij.openapi.projectRoots.Sdk import com.intellij.testFramework.ExtensionTestUtil import com.intellij.testFramework.LightProjectDescriptor import com.intellij.testFramework.fixtures.BasePlatformTestCase +import com.jetbrains.env.python.testing.PythonNoseTestingTest import com.jetbrains.python.PyNames import com.jetbrains.python.PythonMockSdk import com.jetbrains.python.PythonTestUtil @@ -74,7 +75,7 @@ abstract class PythonDependencyTestCase : BasePlatformTestCase() { "${PythonTestUtil.getTestDataPath()}/MockSdk", PythonSdkType.getInstance(), languageLevel, *additionalRoots) sdk.sdkModificator.let { - it.sdkAdditionalData = PythonSdkAdditionalData() + it.sdkAdditionalData = sdkAdditionalData ?: PythonSdkAdditionalData() ApplicationManager.getApplication().runWriteAction { it.commitChanges() } @@ -84,6 +85,8 @@ abstract class PythonDependencyTestCase : BasePlatformTestCase() { } } + open val sdkAdditionalData: PythonSdkAdditionalData? = null + override fun getBasePath(): String { return "/community/python/testData/requirements/" }