diff --git a/python/openapi/src/com/jetbrains/python/packaging/requirement/PyRequirementVersionSpec.java b/python/openapi/src/com/jetbrains/python/packaging/requirement/PyRequirementVersionSpec.java index 1d51b6bf3cbd..69b983dba2ad 100644 --- a/python/openapi/src/com/jetbrains/python/packaging/requirement/PyRequirementVersionSpec.java +++ b/python/openapi/src/com/jetbrains/python/packaging/requirement/PyRequirementVersionSpec.java @@ -99,7 +99,10 @@ public class PyRequirementVersionSpec { case NE: return VERSION_COMPARATOR.compare(version, myVersion) != 0; case COMPATIBLE: - return false; // TODO: implement matching version against compatible relation + Objects.requireNonNull(myParsedVersion); + + return new PyRequirementVersionSpec(PyRequirementRelation.GTE, myParsedVersion).matches(version) && + new PyRequirementVersionSpec(PyRequirementRelation.EQ, toEqPartOfCompatibleRelation(myParsedVersion)).matches(version); case STR_EQ: return version.equals(myVersion); default: @@ -124,4 +127,14 @@ public class PyRequirementVersionSpec { return Pair.createNonNull(publicVersion, localVersion); } + + @NotNull + private static PyRequirementVersion toEqPartOfCompatibleRelation(@NotNull PyRequirementVersion version) { + final String release = version.getRelease(); + final int lastPoint = release.lastIndexOf("."); + + if (lastPoint == -1) return version; + + return new PyRequirementVersion(version.getEpoch(), release.substring(0, lastPoint) + "*", null, null, null, null); + } } diff --git a/python/testSrc/com/jetbrains/python/packaging/PyRequirementTest.java b/python/testSrc/com/jetbrains/python/packaging/PyRequirementTest.java index 024e31c477c7..f2935af155ec 100644 --- a/python/testSrc/com/jetbrains/python/packaging/PyRequirementTest.java +++ b/python/testSrc/com/jetbrains/python/packaging/PyRequirementTest.java @@ -2320,6 +2320,46 @@ public class PyRequirementTest extends PyTestCase { assertEquals(negativeLocalVersion, negativeRequirement.match(Collections.singletonList(negativeLocalVersion))); } + // https://www.python.org/dev/peps/pep-0440/#compatible-release + // PY-20522 + public void testMatchingCompatible() { + final PyRequirement requirement = PyRequirement.fromLine("foo~=2.2"); + final PyPackage release = new PyPackage("foo", "2.3", null, Collections.emptyList()); + final PyPackage pre = new PyPackage("foo", "2.3a1", null, Collections.emptyList()); + final PyPackage post = new PyPackage("foo", "2.3.post1", null, Collections.emptyList()); + final PyPackage dev = new PyPackage("foo", "2.3.dev1", null, Collections.emptyList()); + final PyPackage localVersion = new PyPackage("foo", "2.3+local.version", null, Collections.emptyList()); + + assertEquals(release, requirement.match(Collections.singletonList(release))); + assertEquals(pre, requirement.match(Collections.singletonList(pre))); + assertEquals(post, requirement.match(Collections.singletonList(post))); + assertEquals(dev, requirement.match(Collections.singletonList(dev))); + assertEquals(localVersion, requirement.match(Collections.singletonList(localVersion))); + + final PyRequirement moreModernRequirement = PyRequirement.fromLine("foo~=2.4"); + assertNull(moreModernRequirement.match(Arrays.asList(release, pre, post, dev, localVersion))); + } + + // https://www.python.org/dev/peps/pep-0440/#compatible-release + // PY-20522 + public void testMatchingCompatibleWithTrailingZero() { + final PyRequirement requirement = PyRequirement.fromLine("foo~=2.20.0"); + final PyPackage release = new PyPackage("foo", "2.20.3", null, Collections.emptyList()); + final PyPackage pre = new PyPackage("foo", "2.20.3a1", null, Collections.emptyList()); + final PyPackage post = new PyPackage("foo", "2.20.3.post1", null, Collections.emptyList()); + final PyPackage dev = new PyPackage("foo", "2.20.3.dev1", null, Collections.emptyList()); + final PyPackage localVersion = new PyPackage("foo", "2.20.3+local.version", null, Collections.emptyList()); + + assertEquals(release, requirement.match(Collections.singletonList(release))); + assertEquals(pre, requirement.match(Collections.singletonList(pre))); + assertEquals(post, requirement.match(Collections.singletonList(post))); + assertEquals(dev, requirement.match(Collections.singletonList(dev))); + assertEquals(localVersion, requirement.match(Collections.singletonList(localVersion))); + + final PyRequirement moreModernRequirement = PyRequirement.fromLine("foo~=2.21.0"); + assertNull(moreModernRequirement.match(Arrays.asList(release, pre, post, dev, localVersion))); + } + // OPTIONS public void testOptions() { assertEmpty(