diff --git a/python/src/com/jetbrains/python/sdk/pipenv/pipenv.kt b/python/src/com/jetbrains/python/sdk/pipenv/pipenv.kt index 51ac82b1bb33..b49224af5534 100644 --- a/python/src/com/jetbrains/python/sdk/pipenv/pipenv.kt +++ b/python/src/com/jetbrains/python/sdk/pipenv/pipenv.kt @@ -52,6 +52,7 @@ import com.jetbrains.python.sdk.* import com.jetbrains.python.sdk.flavors.PythonSdkFlavor import icons.PythonIcons import org.jetbrains.annotations.SystemDependent +import org.jetbrains.annotations.TestOnly import java.io.File import javax.swing.Icon @@ -244,17 +245,7 @@ val Sdk.pipFileLockSources: List */ val Sdk.pipFileLockRequirements: List? get() { - fun toRequirements(packages: Map): List = - packages - .asSequence() - .filterNot { (_, pkg) -> pkg.editable ?: false } - .flatMap { (name, pkg) -> packageManager.parseRequirements("$name${pkg.version ?: ""}").asSequence() } - .toList() - - val pipFileLock = parsePipFileLock() ?: return null - val packages = pipFileLock.packages?.let { toRequirements(it) } ?: emptyList() - val devPackages = pipFileLock.devPackages?.let { toRequirements(it) } ?: emptyList() - return packages + devPackages + return pipFileLock?.let { getPipFileLockRequirements(it, packageManager) } } /** @@ -423,10 +414,31 @@ private val LOCK_NOTIFICATION_GROUP = NotificationGroup("$PIP_FILE Watcher", Not private val Sdk.packageManager: PyPackageManager get() = PyPackageManagers.getInstance().forSdk(this) + +@TestOnly +fun getPipFileLockRequirements(virtualFile: VirtualFile, packageManager: PyPackageManager): List? { + fun toRequirements(packages: Map): List = + packages + .asSequence() + .filterNot { (_, pkg) -> pkg.editable ?: false } + // TODO: Support requirements markers (PEP 496), currently any packages with markers are ignored due to PY-30803 + .filter { (_, pkg) -> pkg.markers == null } + .flatMap { (name, pkg) -> packageManager.parseRequirements("$name${pkg.version ?: ""}").asSequence() } + .toList() + val pipFileLock = parsePipFileLock(virtualFile) ?: return null + val packages = pipFileLock.packages?.let { toRequirements(it) } ?: emptyList() + val devPackages = pipFileLock.devPackages?.let { toRequirements(it) } ?: emptyList() + return packages + devPackages +} + private fun Sdk.parsePipFileLock(): PipFileLock? { // TODO: Log errors if Pipfile.lock is not found val file = pipFileLock ?: return null - val text = ReadAction.compute { FileDocumentManager.getInstance().getDocument(file)?.text } + return parsePipFileLock(file) +} + +private fun parsePipFileLock(virtualFile: VirtualFile): PipFileLock? { + val text = ReadAction.compute { FileDocumentManager.getInstance().getDocument(virtualFile)?.text } return try { Gson().fromJson(text, PipFileLock::class.java) } @@ -452,5 +464,7 @@ private data class PipFileLockMeta(@SerializedName("sources") var sources: List< private data class PipFileLockSource(@SerializedName("url") var url: String?) private data class PipFileLockPackage(@SerializedName("version") var version: String?, - @SerializedName("editable") var editable: Boolean?) + @SerializedName("editable") var editable: Boolean?, + @SerializedName("hashes") var hashes: List?, + @SerializedName("markers") var markers: String?) diff --git a/python/testData/inspections/PyPackageRequirementsInspection/PipEnvEnvironmentMarkers/Pipfile b/python/testData/inspections/PyPackageRequirementsInspection/PipEnvEnvironmentMarkers/Pipfile new file mode 100644 index 000000000000..6ec60c512b98 --- /dev/null +++ b/python/testData/inspections/PyPackageRequirementsInspection/PipEnvEnvironmentMarkers/Pipfile @@ -0,0 +1,12 @@ +[[source]] +name = "pypi" +verify_ssl = true +url = "https://pypi.org/simple" + +[packages] + +[dev-packages] +pytest = "*" + +[requires] +python_version = "3.5" diff --git a/python/testData/inspections/PyPackageRequirementsInspection/PipEnvEnvironmentMarkers/Pipfile.lock b/python/testData/inspections/PyPackageRequirementsInspection/PipEnvEnvironmentMarkers/Pipfile.lock new file mode 100644 index 000000000000..6b3bbaf5b434 --- /dev/null +++ b/python/testData/inspections/PyPackageRequirementsInspection/PipEnvEnvironmentMarkers/Pipfile.lock @@ -0,0 +1,80 @@ +{ + "_meta": { + "hash": { + "sha256": "40d973ba4b11089fb91a60d1184a22424028289e5e337e54e0c2ecb33db4d763" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.5" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": {}, + "develop": { + "atomicwrites": { + "hashes": [ + "sha256:240831ea22da9ab882b551b31d4225591e5e447a68c5e188db5b89ca1d487585", + "sha256:a24da68318b08ac9c9c45029f4a10371ab5b20e4226738e150e6e7c571630ae6" + ], + "version": "==1.1.5" + }, + "attrs": { + "hashes": [ + "sha256:4b90b09eeeb9b88c35bc642cbac057e45a5fd85367b985bd2809c62b7b939265", + "sha256:e0d0eb91441a3b53dab4d9b743eafc1ac44476296a2053b6ca3af0b139faf87b" + ], + "version": "==18.1.0" + }, + "more-itertools": { + "hashes": [ + "sha256:c187a73da93e7a8acc0001572aebc7e3c69daf7bf6881a2cea10650bd4420092", + "sha256:c476b5d3a34e12d40130bc2f935028b5f636df8f372dc2c1c01dc19681b2039e", + "sha256:fcbfeaea0be121980e15bc97b3817b5202ca73d0eae185b4550cbfce2a3ebb3d" + ], + "version": "==4.3.0" + }, + "pathlib2": { + "hashes": [ + "sha256:8eb170f8d0d61825e09a95b38be068299ddeda82f35e96c3301a8a5e7604cb83", + "sha256:d1aa2a11ba7b8f7b21ab852b1fb5afb277e1bb99d5dfc663380b5015c0d80c5a" + ], + "markers": "python_version < '3.6'", + "version": "==2.3.2" + }, + "pluggy": { + "hashes": [ + "sha256:6e3836e39f4d36ae72840833db137f7b7d35105079aee6ec4a62d9f80d594dd1", + "sha256:95eb8364a4708392bae89035f45341871286a333f749c3141c20573d2b3876e1" + ], + "version": "==0.7.1" + }, + "py": { + "hashes": [ + "sha256:3fd59af7435864e1a243790d322d763925431213b6b8529c6ca71081ace3bbf7", + "sha256:e31fb2767eb657cbde86c454f02e99cb846d3cd9d61b318525140214fdc0e98e" + ], + "version": "==1.5.4" + }, + "pytest": { + "hashes": [ + "sha256:8214ab8446104a1d0c17fbd218ec6aac743236c6ffbe23abc038e40213c60b88", + "sha256:e2b2c6e1560b8f9dc8dd600b0923183fbd68ba3d9bdecde04467be6dd296a384" + ], + "index": "pypi", + "version": "==3.7.0" + }, + "six": { + "hashes": [ + "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9", + "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb" + ], + "version": "==1.11.0" + } + } +} diff --git a/python/testSrc/com/jetbrains/python/inspections/PyPackageRequirementsInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyPackageRequirementsInspectionTest.java index 8f11fab95613..4e37ede6a815 100644 --- a/python/testSrc/com/jetbrains/python/inspections/PyPackageRequirementsInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/PyPackageRequirementsInspectionTest.java @@ -2,12 +2,18 @@ package com.jetbrains.python.inspections; import com.intellij.openapi.projectRoots.Sdk; +import com.intellij.openapi.vfs.VirtualFile; import com.jetbrains.python.fixtures.PyInspectionTestCase; import com.jetbrains.python.packaging.PyPackageManager; +import com.jetbrains.python.packaging.PyRequirement; import com.jetbrains.python.psi.LanguageLevel; import com.jetbrains.python.sdk.PythonSdkType; +import com.jetbrains.python.sdk.pipenv.PipenvKt; +import one.util.streamex.StreamEx; import org.jetbrains.annotations.NotNull; +import java.util.List; + /** * @author vlan */ @@ -82,4 +88,17 @@ public class PyPackageRequirementsInspectionTest extends PyInspectionTestCase { myFixture.configureFromTempProjectFile("a.py"); configureInspection(); } + + // PY-30803 + public void testPipEnvEnvironmentMarkers() { + myFixture.copyDirectoryToProject(getTestDirectoryPath(), ""); + final VirtualFile pipFileLock = myFixture.findFileInTempDir("Pipfile.lock"); + assertNotNull(pipFileLock); + final PyPackageManager packageManager = PyPackageManager.getInstance(getProjectDescriptor().getSdk()); + final List requirements = PipenvKt.getPipFileLockRequirements(pipFileLock, packageManager); + final List names = StreamEx.of(requirements).map(PyRequirement::getName).toList(); + assertNotEmpty(names); + assertContainsElements(names, "atomicwrites", "attrs", "more-itertools", "pluggy", "py", "pytest", "six"); + assertDoesntContain(names, "pathlib2"); + } }