From c5be56c32b0da0be96b5d889d42998ed08be56e1 Mon Sep 17 00:00:00 2001 From: "Nikita.Ashihmin" Date: Sun, 6 Jul 2025 01:56:49 +0400 Subject: [PATCH] PY-82426 conda: environment.yaml: Fix add dependencies in edge cases Signed-off-by: Nikita.Ashihmin GitOrigin-RevId: d2cfa776a63ab9bd629e31fd8a87a65fe7506ecd --- .../requirement/PyRequirementVisitor.kt | 2 ++ .../format/EnvironmentYmlModifier.kt | 4 +++ .../environmentYmlEmpty/environment.yml | 21 ++++++++++++++ .../environmentYmlEmpty/requirements.txt | 1 + .../conda/EnvironmentYmlHelperTest.kt | 28 +++++++++++++++++++ 5 files changed, 56 insertions(+) create mode 100644 python/testData/requirement/environmentYmlEmpty/environment.yml create mode 100644 python/testData/requirement/environmentYmlEmpty/requirements.txt diff --git a/python/src/com/jetbrains/python/inspections/requirement/PyRequirementVisitor.kt b/python/src/com/jetbrains/python/inspections/requirement/PyRequirementVisitor.kt index bbf1b7ea62df..14ca07cf4687 100644 --- a/python/src/com/jetbrains/python/inspections/requirement/PyRequirementVisitor.kt +++ b/python/src/com/jetbrains/python/inspections/requirement/PyRequirementVisitor.kt @@ -56,6 +56,8 @@ class PyRequirementVisitor( val sdk = module.pythonSdk ?: return val manager = PythonPackageManager.forSdk(module.project, sdk) val requirementsManager = manager.getDependencyManager() ?: return + if (requirementsManager.getDependenciesFile() == null) + return val installedNotDeclaredChecker = InstalledButNotDeclaredChecker(ignoredPackages, manager) val packageName = installedNotDeclaredChecker.getUndeclaredPackageName(importedPyModule = importedPyModule) ?: return diff --git a/python/src/com/jetbrains/python/packaging/conda/environmentYml/format/EnvironmentYmlModifier.kt b/python/src/com/jetbrains/python/packaging/conda/environmentYml/format/EnvironmentYmlModifier.kt index 5fd00f35a7e1..5e37663e6df4 100644 --- a/python/src/com/jetbrains/python/packaging/conda/environmentYml/format/EnvironmentYmlModifier.kt +++ b/python/src/com/jetbrains/python/packaging/conda/environmentYml/format/EnvironmentYmlModifier.kt @@ -45,6 +45,10 @@ object EnvironmentYmlModifier { val dependenciesText = "dependencies:" val dependenciesIndex = text.indexOf(dependenciesText) + if (dependenciesIndex < 0) { + modifiedContent.append("\ndependencies:\n - $packageName") + } + if (dependenciesIndex >= 0) { // Find the indentation level by looking at existing entries val indentPattern = INDENT_PATTERN diff --git a/python/testData/requirement/environmentYmlEmpty/environment.yml b/python/testData/requirement/environmentYmlEmpty/environment.yml new file mode 100644 index 000000000000..704772e66e29 --- /dev/null +++ b/python/testData/requirement/environmentYmlEmpty/environment.yml @@ -0,0 +1,21 @@ +# environment.yml - Comprehensive test file for dependency parsing +name: test-environment + +channels: + - defaults + - conda-forge + - bioconda + - pytorch + - nvidia + - anaconda + - https://conda.anaconda.org/pyviz + +# Environment variables +variables: + CUDA_HOME: /usr/local/cuda + PYTHONPATH: /custom/python/path + API_KEY: your-api-key-here + DEBUG: "1" + +# Prefix for installation (optional) +prefix: /path/to/conda/envs/test-environment \ No newline at end of file diff --git a/python/testData/requirement/environmentYmlEmpty/requirements.txt b/python/testData/requirement/environmentYmlEmpty/requirements.txt new file mode 100644 index 000000000000..b1533e0557dd --- /dev/null +++ b/python/testData/requirement/environmentYmlEmpty/requirements.txt @@ -0,0 +1 @@ +tqdm==1.2.3 \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/packaging/conda/EnvironmentYmlHelperTest.kt b/python/testSrc/com/jetbrains/python/packaging/conda/EnvironmentYmlHelperTest.kt index 8fe6623690e5..af5c62b22abc 100644 --- a/python/testSrc/com/jetbrains/python/packaging/conda/EnvironmentYmlHelperTest.kt +++ b/python/testSrc/com/jetbrains/python/packaging/conda/EnvironmentYmlHelperTest.kt @@ -4,6 +4,7 @@ package com.jetbrains.python.packaging.conda import com.intellij.openapi.util.io.FileUtil import com.intellij.openapi.vfs.VirtualFileManager import com.jetbrains.python.fixtures.PyTestCase +import com.jetbrains.python.packaging.PyRequirement import com.jetbrains.python.packaging.PyRequirementParser import com.jetbrains.python.packaging.conda.environmentYml.format.CondaEnvironmentYmlParser import com.jetbrains.python.packaging.conda.environmentYml.format.EnvironmentYmlModifier @@ -11,6 +12,33 @@ import org.junit.jupiter.api.Assertions import java.io.File class EnvironmentYmlHelperTest : PyTestCase() { + fun testAddRequirementEmpty() { + val virtualFile = getVirtualFileByName("$testDataPath/requirement/environmentYmlEmpty/environment.yml")!! + + // Create a temporary copy of the file + val tempDir = FileUtil.createTempDirectory(getTestName(false), null) + val tempFile = File(tempDir.path, "environment.yml") + virtualFile.inputStream.use { input -> + tempFile.outputStream().use { output -> + input.copyTo(output) + } + } + val tempVirtualFile = VirtualFileManager.getInstance().refreshAndFindFileByUrl("file://${tempFile.absolutePath}")!! + + // Parse the updated file and check if the package was added + val requirements = CondaEnvironmentYmlParser.fromFile(tempVirtualFile)!! + Assertions.assertEquals(emptyList(), requirements) + + // Add a new package that doesn't exist in the file + val newPackageName = "new-test-package" + EnvironmentYmlModifier.addRequirement(myFixture.project, tempVirtualFile, newPackageName) + val newPackageRequirement = PyRequirementParser.fromLine(newPackageName)!! + + // Parse the file again and check that the package appears only once + val updatedRequirements = CondaEnvironmentYmlParser.fromFile(tempVirtualFile)!! + Assertions.assertEquals(listOf(newPackageRequirement), updatedRequirements) + } + fun testAddRequirement() { val virtualFile = getVirtualFileByName("$testDataPath/requirement/environmentYml/environment.yml")!!