Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pep440: support upper bound post/local release comparisons #157

Merged
merged 3 commits into from
Apr 2, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions poetry/core/semver/version_range.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,20 @@ def allows(self, other: "Version") -> bool:
return False

if self.full_max is not None:
if other > self.full_max:
_this, _other = self.full_max, other

if not _this.is_local() and _other.is_local():
# allow weak equality to allow `3.0.0+local.1` for `<=3.0.0`
_other = _other.without_local()

if not _this.is_postrelease() and _other.is_postrelease():
# allow weak equality to allow `3.0.0-1` for `<=3.0.0`
_other = _other.without_postrelease()

if _other > _this:
return False

if not self._include_max and other == self.full_max:
if not self._include_max and _other == _this:
return False

return True
Expand Down
24 changes: 24 additions & 0 deletions poetry/core/version/pep440/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ def __post_init__(self):
if self.local is not None and not isinstance(self.local, tuple):
object.__setattr__(self, "local", (self.local,))

if isinstance(self.release, tuple):
object.__setattr__(self, "release", Release(*self.release))

# we do this here to handle both None and tomlkit string values
object.__setattr__(
self, "text", self.to_string() if not self.text else str(self.text)
Expand Down Expand Up @@ -137,6 +140,9 @@ def is_postrelease(self) -> bool:
def is_devrelease(self) -> bool:
return self.dev is not None

def is_local(self) -> bool:
return self.local is not None

def is_no_suffix_release(self) -> bool:
return not (self.pre or self.post or self.dev)

Expand Down Expand Up @@ -200,3 +206,21 @@ def first_prerelease(self) -> "PEP440Version":
return self.__class__(
epoch=self.epoch, release=self.release, pre=ReleaseTag(RELEASE_PHASE_ALPHA)
)

def replace(self, **kwargs):
return self.__class__(
**{
**{
k: getattr(self, k)
for k in self.__dataclass_fields__.keys()
if k not in ("_compare_key", "text")
}, # setup defaults with current values, excluding compare keys and text
**kwargs, # keys to replace
}
)

def without_local(self) -> "PEP440Version":
return self.replace(local=None)

def without_postrelease(self) -> "PEP440Version":
return self.replace(post=None)
140 changes: 137 additions & 3 deletions tests/semver/test_version_range.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,135 @@ def v300b1():
return Version.parse("3.0.0b1")


def test_allows_all(v003, v010, v080, v114, v123, v124, v140, v200, v234, v250, v300):
@pytest.mark.parametrize(
"base,other",
[
pytest.param(Version.parse("3.0.0"), Version.parse("3.0.0-1"), id="post"),
pytest.param(
Version.parse("3.0.0"), Version.parse("3.0.0+local.1"), id="local"
),
],
)
def test_allows_post_releases_with_max(base, other):
range = VersionRange(max=base, include_max=True)
assert range.allows(other)


@pytest.mark.parametrize(
"base,other",
[
pytest.param(Version.parse("3.0.0"), Version.parse("3.0.0-1"), id="post"),
pytest.param(
Version.parse("3.0.0"), Version.parse("3.0.0+local.1"), id="local"
),
],
)
def test_allows_post_releases_with_min(base, other):
range = VersionRange(min=base, include_min=True)
assert range.allows(other)


def test_allows_post_releases_with_post_and_local_min():
one = Version.parse("3.0.0+local.1")
two = Version.parse("3.0.0-1")
three = Version.parse("3.0.0-1+local.1")
four = Version.parse("3.0.0+local.2")

assert VersionRange(min=one, include_min=True).allows(two)
assert VersionRange(min=one, include_min=True).allows(three)
assert VersionRange(min=one, include_min=True).allows(four)

assert not VersionRange(min=two, include_min=True).allows(one)
assert VersionRange(min=two, include_min=True).allows(three)
assert not VersionRange(min=two, include_min=True).allows(four)

assert not VersionRange(min=three, include_min=True).allows(one)
assert not VersionRange(min=three, include_min=True).allows(two)
assert not VersionRange(min=three, include_min=True).allows(four)

assert not VersionRange(min=four, include_min=True).allows(one)
assert VersionRange(min=four, include_min=True).allows(two)
assert VersionRange(min=four, include_min=True).allows(three)


def test_allows_post_releases_with_post_and_local_max():
one = Version.parse("3.0.0+local.1")
two = Version.parse("3.0.0-1")
three = Version.parse("3.0.0-1+local.1")
four = Version.parse("3.0.0+local.2")

assert VersionRange(max=one, include_max=True).allows(two)
assert VersionRange(max=one, include_max=True).allows(three)
assert not VersionRange(max=one, include_max=True).allows(four)

assert VersionRange(max=two, include_max=True).allows(one)
assert VersionRange(max=two, include_max=True).allows(three)
assert VersionRange(max=two, include_max=True).allows(four)

assert VersionRange(max=three, include_max=True).allows(one)
assert VersionRange(max=three, include_max=True).allows(two)
assert VersionRange(max=three, include_max=True).allows(four)

assert VersionRange(max=four, include_max=True).allows(one)
assert VersionRange(max=four, include_max=True).allows(two)
assert VersionRange(max=four, include_max=True).allows(three)


@pytest.mark.parametrize(
"base,one,two",
[
pytest.param(
Version.parse("3.0.0"),
Version.parse("3.0.0-1"),
Version.parse("3.0.0-2"),
id="post",
),
pytest.param(
Version.parse("3.0.0"),
Version.parse("3.0.0+local.1"),
Version.parse("3.0.0+local.2"),
id="local",
),
],
)
def test_allows_post_releases_explicit_with_max(base, one, two):
range = VersionRange(max=one, include_max=True)
assert range.allows(base)
assert not range.allows(two)

range = VersionRange(max=two, include_max=True)
assert range.allows(base)
assert range.allows(one)


@pytest.mark.parametrize(
"base,one,two",
[
pytest.param(
Version.parse("3.0.0"),
Version.parse("3.0.0-1"),
Version.parse("3.0.0-2"),
id="post",
),
pytest.param(
Version.parse("3.0.0"),
Version.parse("3.0.0+local.1"),
Version.parse("3.0.0+local.2"),
id="local",
),
],
)
def test_allows_post_releases_explicit_with_min(base, one, two):
range = VersionRange(min=one, include_min=True)
assert not range.allows(base)
assert range.allows(two)

range = VersionRange(min=two, include_min=True)
assert not range.allows(base)
assert not range.allows(one)


def test_allows_all(v123, v124, v140, v250, v300):
assert VersionRange(v123, v250).allows_all(EmptyConstraint())

range = VersionRange(v123, v250, include_max=True)
Expand All @@ -84,7 +212,8 @@ def test_allows_all(v003, v010, v080, v114, v123, v124, v140, v200, v234, v250,
assert range.allows_all(v250)
assert not range.allows_all(v300)

# with no min

def test_allows_all_with_no_min(v080, v140, v250, v300):
range = VersionRange(max=v250)
assert range.allows_all(VersionRange(v080, v140))
assert not range.allows_all(VersionRange(v080, v300))
Expand All @@ -93,7 +222,8 @@ def test_allows_all(v003, v010, v080, v114, v123, v124, v140, v200, v234, v250,
assert range.allows_all(range)
assert not range.allows_all(VersionRange())

# with no max

def test_allows_all_with_no_max(v003, v010, v080, v140):
range = VersionRange(min=v010)
assert range.allows_all(VersionRange(v080, v140))
assert not range.allows_all(VersionRange(v003, v140))
Expand All @@ -102,6 +232,8 @@ def test_allows_all(v003, v010, v080, v114, v123, v124, v140, v200, v234, v250,
assert range.allows_all(range)
assert not range.allows_all(VersionRange())


def test_allows_all_bordering_range_not_more_inclusive(v010, v250):
# Allows bordering range that is not more inclusive
exclusive = VersionRange(v010, v250)
inclusive = VersionRange(v010, v250, True, True)
Expand All @@ -110,6 +242,8 @@ def test_allows_all(v003, v010, v080, v114, v123, v124, v140, v200, v234, v250,
assert not exclusive.allows_all(inclusive)
assert exclusive.allows_all(exclusive)


def test_allows_all_contained_unions(v010, v114, v123, v124, v140, v200, v234):
# Allows unions that are completely contained
range = VersionRange(v114, v200)
assert range.allows_all(VersionRange(v123, v124).union(v140))
Expand Down
Loading