From b9b8efa2b3abbface73ef54f54d0ad3b000352f1 Mon Sep 17 00:00:00 2001 From: Semyon Proshev Date: Tue, 26 Jun 2018 16:01:10 +0300 Subject: [PATCH] Support strings and tuple of strings in setup.py requirements (PY-29153, PY-30016) --- .../python/packaging/PyPackageUtil.java | 49 ++++++++++++------- .../SetupPyExtrasReading/setup.py | 17 +++++++ .../PyPackageUtil/SetupPyReading/setup.py | 10 ++-- .../python/packaging/PyPackageUtilTest.java | 33 +++++++++++-- 4 files changed, 80 insertions(+), 29 deletions(-) create mode 100644 python/testData/packaging/PyPackageUtil/SetupPyExtrasReading/setup.py diff --git a/python/src/com/jetbrains/python/packaging/PyPackageUtil.java b/python/src/com/jetbrains/python/packaging/PyPackageUtil.java index 72b4785874fc..e2041883749c 100644 --- a/python/src/com/jetbrains/python/packaging/PyPackageUtil.java +++ b/python/src/com/jetbrains/python/packaging/PyPackageUtil.java @@ -42,6 +42,7 @@ import com.jetbrains.python.PyNames; import com.jetbrains.python.codeInsight.controlflow.ScopeOwner; import com.jetbrains.python.packaging.setupPy.SetupTaskIntrospector; import com.jetbrains.python.psi.*; +import com.jetbrains.python.psi.impl.PyPsiUtils; import com.jetbrains.python.psi.resolve.PyResolveContext; import com.jetbrains.python.psi.resolve.QualifiedResolveResult; import com.jetbrains.python.psi.types.TypeEvalContext; @@ -171,21 +172,11 @@ public class PyPackageUtil { @Nullable private static Pair> getExtraRequires(@NotNull PyExpression extra, @Nullable PyExpression requires) { if (extra instanceof PyStringLiteralExpression) { - final String requiresValue; - - if (requires instanceof PyStringLiteralExpression) { - requiresValue = ((PyStringLiteralExpression)requires).getStringValue(); - } - else if (requires instanceof PyListLiteralExpression) { - final List requiresListValue = PyUtil.strListValue(requires); - requiresValue = requiresListValue != null ? StringUtil.join(requiresListValue, "\n") : null; - } - else { - requiresValue = null; - } + final List requiresValue = resolveRequiresValue(requires); if (requiresValue != null) { - return Pair.createNonNull(((PyStringLiteralExpression)extra).getStringValue(), PyRequirementParser.fromText(requiresValue)); + return Pair.createNonNull(((PyStringLiteralExpression)extra).getStringValue(), + PyRequirementParser.fromText(StringUtil.join(requiresValue, "\n"))); } } @@ -199,11 +190,7 @@ public class PyPackageUtil { StreamEx .of(argumentNames) .map(setupCall::getKeywordArgument) - .map(requires -> resolveValue(requires, PyListLiteralExpression.class)) - .nonNull() - .flatMap(requires -> Stream.of(requires.getElements())) - .select(PyStringLiteralExpression.class) - .map(StringLiteralExpression::getStringValue) + .flatCollection(PyPackageUtil::resolveRequiresValue) .joining("\n") ); } @@ -242,6 +229,32 @@ public class PyPackageUtil { return null; } + @Nullable + private static List resolveRequiresValue(@Nullable PyExpression expression) { + PsiElement elementToAnalyze = PyPsiUtils.flattenParens(expression); + + if (elementToAnalyze instanceof PyReferenceExpression) { + final TypeEvalContext context = TypeEvalContext.deepCodeInsight(elementToAnalyze.getProject()); + final PyResolveContext resolveContext = PyResolveContext.noImplicits().withTypeEvalContext(context); + final QualifiedResolveResult result = ((PyReferenceExpression)elementToAnalyze).followAssignmentsChain(resolveContext); + elementToAnalyze = result.getElement(); + } + + if (elementToAnalyze instanceof PyStringLiteralExpression) { + return Collections.singletonList(((PyStringLiteralExpression)elementToAnalyze).getStringValue()); + } + else if (elementToAnalyze instanceof PyListLiteralExpression || elementToAnalyze instanceof PyTupleExpression) { + return StreamEx + .of(((PySequenceExpression)elementToAnalyze).getElements()) + .map(e -> resolveValue(e, PyStringLiteralExpression.class)) + .select(PyStringLiteralExpression.class) + .map(PyStringLiteralExpression::getStringValue) + .toList(); + } + + return null; + } + @NotNull public static List getPackageNames(@NotNull Module module) { // TODO: Cache found module packages, clear cache on module updates diff --git a/python/testData/packaging/PyPackageUtil/SetupPyExtrasReading/setup.py b/python/testData/packaging/PyPackageUtil/SetupPyExtrasReading/setup.py new file mode 100644 index 000000000000..c11590794552 --- /dev/null +++ b/python/testData/packaging/PyPackageUtil/SetupPyExtrasReading/setup.py @@ -0,0 +1,17 @@ +from distutils.core import setup + +tests_require = [ + 'mynose' +] + +setup(name='foo', + version=0.1, + requires=[ + 'Markdown', + ], + install_requires='NewDjango==1.3.1', + tests_require=tests_require, + setup_requires=( + 'numpy', + ), + extras_require={"e1": "r1", "e2": ("r2",), "e3": ["r3", "r4"]}) \ No newline at end of file diff --git a/python/testData/packaging/PyPackageUtil/SetupPyReading/setup.py b/python/testData/packaging/PyPackageUtil/SetupPyReading/setup.py index a3dd5ebdd87d..e014042c41eb 100644 --- a/python/testData/packaging/PyPackageUtil/SetupPyReading/setup.py +++ b/python/testData/packaging/PyPackageUtil/SetupPyReading/setup.py @@ -9,10 +9,8 @@ setup(name='foo', requires=[ 'Markdown', ], - install_requires=[ - 'NewDjango==1.3.1', - ], + install_requires='NewDjango==1.3.1', tests_require=tests_require, - setup_requires=[ - 'numpy' - ]) \ No newline at end of file + setup_requires=( + 'numpy', + )) \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/packaging/PyPackageUtilTest.java b/python/testSrc/com/jetbrains/python/packaging/PyPackageUtilTest.java index 78d6cdcdca45..e5a2e345face 100644 --- a/python/testSrc/com/jetbrains/python/packaging/PyPackageUtilTest.java +++ b/python/testSrc/com/jetbrains/python/packaging/PyPackageUtilTest.java @@ -15,6 +15,7 @@ */ package com.jetbrains.python.packaging; +import com.google.common.collect.ImmutableMap; import com.intellij.openapi.command.WriteCommandAction; import com.intellij.openapi.module.Module; import com.jetbrains.python.fixtures.PyTestCase; @@ -25,7 +26,10 @@ import com.jetbrains.python.psi.PyFile; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.Arrays; +import java.util.Collections; import java.util.List; +import java.util.Map; public class PyPackageUtilTest extends PyTestCase { @@ -37,19 +41,23 @@ public class PyPackageUtilTest extends PyTestCase { } public void testAbsentSetupPyReading() { - doTestSetupPyReading(false, false, false); + doTestSetupPyReading(false, false, false, false); } public void testAbsentSetupCallReading() { - doTestSetupPyReading(true, false, false); + doTestSetupPyReading(true, false, false, false); } public void testAbsentSetupPyRequiresReading() { - doTestSetupPyReading(true, true, false); + doTestSetupPyReading(true, true, false, false); } public void testSetupPyReading() { - doTestSetupPyReading(true, true, true); + doTestSetupPyReading(true, true, true, false); + } + + public void testSetupPyExtrasReading() { + doTestSetupPyReading(true, true, true, true); } // PY-18966 @@ -104,7 +112,7 @@ public class PyPackageUtilTest extends PyTestCase { checkRequirements(PyPackageUtil.getRequirementsFromTxt(module)); } - private void doTestSetupPyReading(boolean hasFile, boolean hasCall, boolean requires) { + private void doTestSetupPyReading(boolean hasFile, boolean hasCall, boolean requires, boolean extrasRequire) { final Module module = myFixture.getModule(); if (hasFile) { @@ -124,6 +132,21 @@ public class PyPackageUtilTest extends PyTestCase { assertNotNull(requirements); assertEmpty(requirements); } + + if (extrasRequire) { + final Map> extrasRequirements = PyPackageUtil.findSetupPyExtrasRequire(module); + + final ImmutableMap> expected = ImmutableMap.of( + "e1", Collections.singletonList(PyRequirementsKt.pyRequirement("r1")), + "e2", Collections.singletonList(PyRequirementsKt.pyRequirement("r2")), + "e3", Arrays.asList(PyRequirementsKt.pyRequirement("r3"), PyRequirementsKt.pyRequirement("r4")) + ); + + assertEquals(expected, extrasRequirements); + } + else { + assertNull(PyPackageUtil.findSetupPyExtrasRequire(module)); + } } else { assertNull(PyPackageUtil.findSetupCall(module));