Support appending requirement to string or tuple in setup.py (PY-29153, PY-30016)

This commit is contained in:
Semyon Proshev
2018-06-26 18:19:06 +03:00
parent f132334197
commit a66b787eea
6 changed files with 100 additions and 25 deletions

View File

@@ -126,15 +126,14 @@ public class PyPackageUtil {
}
@Nullable
private static PyListLiteralExpression findSetupPyInstallRequires(@Nullable PyCallExpression setupCall) {
private static PsiElement findSetupPyInstallRequires(@Nullable PyCallExpression setupCall) {
if (setupCall == null) return null;
return StreamEx
.of(REQUIRES, INSTALL_REQUIRES)
.map(setupCall::getKeywordArgument)
.map(PyPackageUtil::resolveValue)
.select(PyListLiteralExpression.class)
.findFirst()
.findFirst(Objects::nonNull)
.orElse(null);
}
@@ -435,25 +434,18 @@ public class PyPackageUtil {
}
final PyFile setupPy = findSetupPy(module);
if (setupPy == null) {
return;
}
if (setupPy == null) return;
final PyCallExpression setupCall = findSetupCall(setupPy);
final PyListLiteralExpression installRequires = findSetupPyInstallRequires(setupCall);
final PyElementGenerator generator = PyElementGenerator.getInstance(module.getProject());
if (setupCall == null) return;
if (installRequires != null && installRequires.isWritable()) {
final String text = String.format("'%s'", requirementName);
final PyExpression generated = generator.createExpressionFromText(languageLevel, text);
installRequires.add(generated);
return;
final PsiElement installRequires = findSetupPyInstallRequires(setupCall);
if (installRequires != null) {
addRequirementToInstallRequires(installRequires, requirementName, languageLevel);
}
if (setupCall != null) {
else {
final PyArgumentList argumentList = setupCall.getArgumentList();
final PyKeywordArgument requiresArg = generateRequiresKwarg(setupPy, requirementName, languageLevel, generator);
final PyKeywordArgument requiresArg = generateRequiresKwarg(setupPy, requirementName, languageLevel);
if (argumentList != null && requiresArg != null) {
argumentList.addArgument(requiresArg);
@@ -461,14 +453,47 @@ public class PyPackageUtil {
}
}
private static void addRequirementToInstallRequires(@NotNull PsiElement installRequires,
@NotNull String requirementName,
@NotNull LanguageLevel languageLevel) {
final PyElementGenerator generator = PyElementGenerator.getInstance(installRequires.getProject());
final PyExpression newRequirement = generator.createExpressionFromText(languageLevel, "'" + requirementName + "'");
if (installRequires instanceof PyListLiteralExpression) {
installRequires.add(newRequirement);
}
else if (installRequires instanceof PyTupleExpression) {
final String newInstallRequiresText = StreamEx
.of(((PyTupleExpression)installRequires).getElements())
.append(newRequirement)
.map(PyExpression::getText)
.joining(",", "(", ")");
final PyExpression expression = generator.createExpressionFromText(languageLevel, newInstallRequiresText);
Optional
.ofNullable(PyUtil.as(expression, PyParenthesizedExpression.class))
.map(PyParenthesizedExpression::getContainedExpression)
.map(e -> PyUtil.as(e, PyTupleExpression.class))
.ifPresent(e -> installRequires.replace(e));
}
else if (installRequires instanceof PyStringLiteralExpression) {
final PyListLiteralExpression newInstallRequires = generator.createListLiteral();
newInstallRequires.add(installRequires);
newInstallRequires.add(newRequirement);
installRequires.replace(newInstallRequires);
}
}
@Nullable
private static PyKeywordArgument generateRequiresKwarg(@NotNull PyFile setupPy,
@NotNull String requirementName,
@NotNull LanguageLevel languageLevel,
@NotNull PyElementGenerator generator) {
@NotNull LanguageLevel languageLevel) {
final String keyword = SetupTaskIntrospector.usesSetuptools(setupPy) ? INSTALL_REQUIRES : REQUIRES;
final String text = String.format("foo(%s=['%s'])", keyword, requirementName);
final PyExpression generated = generator.createExpressionFromText(languageLevel, text);
final PyExpression generated = PyElementGenerator.getInstance(setupPy.getProject()).createExpressionFromText(languageLevel, text);
if (generated instanceof PyCallExpression) {
final PyCallExpression callExpression = (PyCallExpression)generated;

View File

@@ -0,0 +1,13 @@
from setuptools import setup
tests_require = [
'mynose'
]
setup(name='foo',
version=0.1,
tests_require=tests_require,
setup_requires=[
'numpy'
],
install_requires='NewDjango==1.3.1')

View File

@@ -0,0 +1,13 @@
from setuptools import setup
tests_require = [
'mynose'
]
setup(name='foo',
version=0.1,
tests_require=tests_require,
setup_requires=[
'numpy'
],
install_requires=('NewDjango==1.3.1',))

View File

@@ -89,12 +89,20 @@ public class PyPackageUtilTest extends PyTestCase {
doTestUselessRequirementsTxtOrSetupPyUpdating(false);
}
public void testDistutilsSetupPyUpdating() {
doTestSetupPyUpdating("requires");
public void testDistutilsSetupPyRequiresIntroduction() {
doTestSetupPyRequiresIntroduction("requires");
}
public void testSetuptoolsSetupPyUpdating() {
doTestSetupPyUpdating("install_requires");
public void testSetuptoolsSetupPyRequiresIntroduction() {
doTestSetupPyRequiresIntroduction("install_requires");
}
public void testSetuptoolsSetupPyTupleRequiresAppending() {
doTestSetupPyRequiresAppending("('NewDjango==1.3.1',)", "('NewDjango==1.3.1', 'Markdown')");
}
public void testSetuptoolsSetupPyStringRequiresAppending() {
doTestSetupPyRequiresAppending("'NewDjango==1.3.1'", "['NewDjango==1.3.1', 'Markdown']");
}
public void testAbsentRequirementsTxtUpdating() {
@@ -204,7 +212,7 @@ public class PyPackageUtilTest extends PyTestCase {
assertEquals(expected.subList(fromIndex, expected.size()), actual);
}
private void doTestSetupPyUpdating(@NotNull String keyword) {
private void doTestSetupPyRequiresIntroduction(@NotNull String keyword) {
final Module module = myFixture.getModule();
checkSetupArgumentText(module, keyword, null);
@@ -226,6 +234,22 @@ public class PyPackageUtilTest extends PyTestCase {
assertEquals(expected, actual);
}
private void doTestSetupPyRequiresAppending(@NotNull String argumentBefore, @NotNull String argumentAfter) {
final Module module = myFixture.getModule();
checkSetupArgumentText(module, "install_requires", argumentBefore);
checkRequirements(PyPackageUtil.findSetupPyRequires(module), 1);
final Runnable appendToRequires = () -> PyPackageUtil.addRequirementToTxtOrSetupPy(module, "Markdown", LanguageLevel.PYTHON34);
WriteCommandAction.runWriteCommandAction(myFixture.getProject(), appendToRequires);
checkSetupArgumentText(module, "install_requires", argumentAfter);
final List<PyRequirement> actual = PyPackageUtil.findSetupPyRequires(module);
final List<PyRequirement> expected = PyRequirementParser.fromText("NewDjango==1.3.1\nMarkdown\nnumpy\nmynose");
assertEquals(expected, actual);
}
private static void checkSetupArgumentText(@NotNull Module module, @NotNull String keyword, @Nullable String text) {
final PyCallExpression setupCall = PyPackageUtil.findSetupCall(module);
assertNotNull(setupCall);