Skip to content

Commit

Permalink
Allow coersion from releases to specifiers and requirements. (#36)
Browse files Browse the repository at this point in the history
  • Loading branch information
jesnie authored Aug 31, 2023
1 parent 6423140 commit cf969df
Show file tree
Hide file tree
Showing 7 changed files with 197 additions and 64 deletions.
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,10 @@ with cr.PoetryPyprojectFile.open() as pyproject:
)
```

Or see [requirements.py](https://github.com/jesnie/compreq/blob/main/requirements.py).
Or see [requirements.py](https://github.com/jesnie/compreq/blob/main/requirements.py).


# References:

https://peps.python.org/pep-0440/
https://packaging.pypa.io/en/stable
13 changes: 0 additions & 13 deletions TODO.txt

This file was deleted.

4 changes: 4 additions & 0 deletions compreq/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
EagerLazyRelease,
EagerLazyReleaseSet,
EagerLazyRequirementSet,
EagerLazySpecifier,
EagerLazySpecifierSet,
EagerLazyVersion,
LazyRelease,
Expand All @@ -37,6 +38,7 @@
LazyVersion,
PreLazyReleaseSet,
ProdLazyReleaseSet,
ReleaseLazySpecifier,
ReleaseLazyVersion,
SpecifierLazyReleaseSet,
SpecifierOperator,
Expand Down Expand Up @@ -153,6 +155,7 @@
"EagerLazyRelease",
"EagerLazyReleaseSet",
"EagerLazyRequirementSet",
"EagerLazySpecifier",
"EagerLazySpecifierSet",
"EagerLazyVersion",
"FloorLazyVersion",
Expand Down Expand Up @@ -187,6 +190,7 @@
"REL_MINOR",
"RelativeToFirstNonZeroLevel",
"Release",
"ReleaseLazySpecifier",
"ReleaseLazyVersion",
"ReleaseSet",
"RequirementSet",
Expand Down
82 changes: 66 additions & 16 deletions compreq/lazy.py
Original file line number Diff line number Diff line change
Expand Up @@ -313,8 +313,7 @@ def get_specifier_operator(op: AnySpecifierOperator) -> SpecifierOperator:
raise AssertionError(f"Unknown type of operator: {type(op)}")


@dataclass(order=True, frozen=True)
class LazySpecifier:
class LazySpecifier(ABC):
"""
Strategy for computing a `Specifier` in the context of a distribution.
Expand All @@ -324,14 +323,9 @@ class LazySpecifier:
lazy_specifier_set = lazy_specifier_1 & lazy_specifier_2
"""

op: SpecifierOperator
version: LazyVersion

@abstractmethod
async def resolve(self, context: DistributionContext) -> Specifier:
"""Compute the `Specifier`."""
op = self.op
version = await self.version.resolve(context)
return Specifier(f"{op.value}{version}")

@overload
def __and__(self, rhs: AnySpecifierSet) -> LazySpecifierSet:
Expand All @@ -356,18 +350,42 @@ def __rand__(self, lhs: AnyRequirement) -> LazySpecifierSet | LazyRequirement:
return compose(lhs, self)


AnySpecifier: TypeAlias = str | Specifier | LazySpecifier
@dataclass(order=True, frozen=True)
class EagerLazySpecifier(LazySpecifier):
op: SpecifierOperator
version: LazyVersion

async def resolve(self, context: DistributionContext) -> Specifier:
op = self.op
version = await self.version.resolve(context)
return Specifier(f"{op.value}{version}")


@dataclass(order=True, frozen=True)
class ReleaseLazySpecifier(LazySpecifier):
release: LazyRelease

async def resolve(self, context: DistributionContext) -> Specifier:
release = await self.release.resolve(context)
return Specifier(f"=={release.version}")


AnySpecifier: TypeAlias = str | Release | LazyRelease | Specifier | LazySpecifier
"""Type alias for anything that can be converted to a `LazySpecifier`."""


def get_lazy_specifier(specifier: AnySpecifier) -> LazySpecifier:
"""Get a `LazySpecifier` for the given specifier-like value."""
if isinstance(specifier, str):
specifier = Specifier(specifier)
if isinstance(specifier, Release):
specifier = EagerLazyRelease(specifier)
if isinstance(specifier, LazyRelease):
specifier = ReleaseLazySpecifier(specifier)
if isinstance(specifier, Specifier):
op = get_specifier_operator(specifier.operator)
version = get_lazy_version(specifier.version)
specifier = LazySpecifier(op, version)
specifier = EagerLazySpecifier(op, version)
if isinstance(specifier, LazySpecifier):
return specifier
raise AssertionError(f"Unknown type of specifier: {type(specifier)}")
Expand Down Expand Up @@ -429,15 +447,17 @@ async def resolve(self, context: DistributionContext) -> SpecifierSet:
return SpecifierSet(",".join(str(s) for s in specifiers))


AnySpecifierSet: TypeAlias = str | Specifier | LazySpecifier | SpecifierSet | LazySpecifierSet
AnySpecifierSet: TypeAlias = (
str | Release | LazyRelease | Specifier | LazySpecifier | SpecifierSet | LazySpecifierSet
)
"""Type alias for anything that can be converted to a `LazySpecifierSet`."""


def get_lazy_specifier_set(specifier_set: AnySpecifierSet) -> LazySpecifierSet:
"""Get a `LazySpecifierSet` for the given specifier-set-like value."""
if isinstance(specifier_set, str):
specifier_set = SpecifierSet(specifier_set)
if isinstance(specifier_set, Specifier):
if isinstance(specifier_set, (Release, LazyRelease, Specifier)):
specifier_set = get_lazy_specifier(specifier_set)
if isinstance(specifier_set, LazySpecifier):
specifier_set = EagerLazySpecifierSet(frozenset([specifier_set]))
Expand Down Expand Up @@ -575,6 +595,8 @@ async def resolve(self, context: Context) -> Requirement:

AnyRequirement: TypeAlias = (
str
| Release
| LazyRelease
| Specifier
| LazySpecifier
| SpecifierSet
Expand All @@ -589,6 +611,16 @@ def get_lazy_requirement(requirement: AnyRequirement) -> LazyRequirement:
"""Get a `LazyRequirement` for the given requirement-like value."""
if isinstance(requirement, str):
requirement = Requirement(requirement)
if isinstance(requirement, Release):
requirement = EagerLazyRelease(requirement)
if isinstance(requirement, LazyRelease):
distribution = requirement.get_distribution()
assert distribution is not None, requirement
requirement = replace(
EMPTY_REQUIREMENT,
distribution=distribution,
specifier=get_lazy_specifier_set(requirement),
)
if isinstance(requirement, (Specifier, LazySpecifier, SpecifierSet)):
requirement = get_lazy_specifier_set(requirement)
if isinstance(requirement, LazySpecifierSet):
Expand All @@ -609,17 +641,24 @@ def get_lazy_requirement(requirement: AnyRequirement) -> LazyRequirement:


@overload
def compose(lhs: AnySpecifierSet, rhs: AnySpecifierSet) -> LazySpecifierSet:
def compose(
lhs: str | Specifier | LazySpecifier | SpecifierSet | LazySpecifierSet,
rhs: str | Specifier | LazySpecifier | SpecifierSet | LazySpecifierSet,
) -> LazySpecifierSet:
...


@overload
def compose(lhs: AnyRequirement, rhs: Requirement | LazyRequirement) -> LazyRequirement:
def compose(
lhs: AnyRequirement, rhs: Release | LazyRelease | Requirement | LazyRequirement
) -> LazyRequirement:
...


@overload
def compose(lhs: Requirement | LazyRequirement, rhs: AnyRequirement) -> LazyRequirement:
def compose(
lhs: Release | LazyRelease | Requirement | LazyRequirement, rhs: AnyRequirement
) -> LazyRequirement:
...


Expand Down Expand Up @@ -714,6 +753,8 @@ async def resolve(self, context: Context) -> RequirementSet:

AnyRequirementSet: TypeAlias = (
str
| Release
| LazyRelease
| Specifier
| LazySpecifier
| SpecifierSet
Expand All @@ -732,7 +773,16 @@ def get_lazy_requirement_set(requirement_set: AnyRequirementSet) -> LazyRequirem
"""Get a `LazyRequirementSet` for the given requirement-set-like value."""
if isinstance(
requirement_set,
(str, Specifier, LazySpecifier, SpecifierSet, LazySpecifierSet, Requirement),
(
str,
Release,
LazyRelease,
Specifier,
LazySpecifier,
SpecifierSet,
LazySpecifierSet,
Requirement,
),
):
requirement_set = get_lazy_requirement(requirement_set)
if isinstance(requirement_set, LazyRequirement):
Expand Down
3 changes: 2 additions & 1 deletion compreq/versiontokens.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from compreq.lazy import (
AnySpecifierOperator,
AnyVersion,
EagerLazySpecifier,
LazySpecifier,
SpecifierOperator,
get_lazy_version,
Expand All @@ -18,7 +19,7 @@ class VersionToken:
def require(self, op: AnySpecifierOperator, version: AnyVersion) -> LazySpecifier:
op = get_specifier_operator(op)
version = get_lazy_version(version)
return LazySpecifier(op, version)
return EagerLazySpecifier(op, version)

def __call__(self, op: AnySpecifierOperator, version: AnyVersion) -> LazySpecifier:
return self.require(op, version)
Expand Down
98 changes: 92 additions & 6 deletions tests/test_lazy.py
Original file line number Diff line number Diff line change
Expand Up @@ -327,31 +327,49 @@ def test_get_specifier_operator(
assert cr.get_specifier_operator(op) == expected


async def test_lazy_specifier() -> None:
async def test_eager_lazy_specifier() -> None:
op = cr.SpecifierOperator.LT
version = MagicMock(cr.LazyVersion)
version.resolve.return_value = Version("1.5.0")
lazy = cr.LazySpecifier(op, version)
lazy = cr.EagerLazySpecifier(op, version)

context = MagicMock(cr.DistributionContext)
assert Specifier("<1.5.0") == await lazy.resolve(context)
version.resolve.assert_called_once_with(context)


async def test_release_lazy_specifier() -> None:
release = MagicMock(cr.LazyRelease)
release.resolve.return_value = fake_release(version="1.7.8")
lazy = cr.ReleaseLazySpecifier(release)

context = MagicMock(cr.DistributionContext)
assert Specifier("==1.7.8") == await lazy.resolve(context)
release.resolve.assert_called_once_with(context)


@pytest.mark.parametrize(
"specifier,expected",
[
(
">=1.1.0",
cr.LazySpecifier(cr.SpecifierOperator.GE, cr.EagerLazyVersion(Version("1.1.0"))),
cr.EagerLazySpecifier(cr.SpecifierOperator.GE, cr.EagerLazyVersion(Version("1.1.0"))),
),
(
fake_release(version="2.1.4"),
cr.ReleaseLazySpecifier(cr.EagerLazyRelease(fake_release(version="2.1.4"))),
),
(
cr.EagerLazyRelease(fake_release(version="2.1.4")),
cr.ReleaseLazySpecifier(cr.EagerLazyRelease(fake_release(version="2.1.4"))),
),
(
Specifier(">=1.2.0"),
cr.LazySpecifier(cr.SpecifierOperator.GE, cr.EagerLazyVersion(Version("1.2.0"))),
cr.EagerLazySpecifier(cr.SpecifierOperator.GE, cr.EagerLazyVersion(Version("1.2.0"))),
),
(
cr.LazySpecifier(cr.SpecifierOperator.GE, cr.EagerLazyVersion(Version("1.3.0"))),
cr.LazySpecifier(cr.SpecifierOperator.GE, cr.EagerLazyVersion(Version("1.3.0"))),
cr.EagerLazySpecifier(cr.SpecifierOperator.GE, cr.EagerLazyVersion(Version("1.3.0"))),
cr.EagerLazySpecifier(cr.SpecifierOperator.GE, cr.EagerLazyVersion(Version("1.3.0"))),
),
],
)
Expand Down Expand Up @@ -391,6 +409,22 @@ async def test_composite_lazy_specifier_set() -> None:
"specifier_set,expected",
[
(">=1.1.0", cr.EagerLazySpecifierSet(frozenset([cr.get_lazy_specifier(">=1.1.0")]))),
(
fake_release(version="2.1.4"),
cr.EagerLazySpecifierSet(
frozenset(
[cr.ReleaseLazySpecifier(cr.EagerLazyRelease(fake_release(version="2.1.4")))]
)
),
),
(
cr.EagerLazyRelease(fake_release(version="2.1.4")),
cr.EagerLazySpecifierSet(
frozenset(
[cr.ReleaseLazySpecifier(cr.EagerLazyRelease(fake_release(version="2.1.4")))]
)
),
),
(
Specifier(">=1.2.0"),
cr.EagerLazySpecifierSet(frozenset([cr.get_lazy_specifier(">=1.2.0")])),
Expand Down Expand Up @@ -494,6 +528,26 @@ async def test_lazy_requirement__url() -> None:
marker=None,
),
),
(
fake_release(version="1.2.3"),
cr.LazyRequirement(
distribution="foo.bar",
url=None,
extras=frozenset(),
specifier=cr.get_lazy_specifier_set(fake_release(version="1.2.3")),
marker=None,
),
),
(
cr.EagerLazyRelease(fake_release(version="1.2.3")),
cr.LazyRequirement(
distribution="foo.bar",
url=None,
extras=frozenset(),
specifier=cr.get_lazy_specifier_set(fake_release(version="1.2.3")),
marker=None,
),
),
(
Specifier("==1.2.0"),
cr.LazyRequirement(
Expand Down Expand Up @@ -1067,6 +1121,38 @@ def test_compose__specifier_sets() -> None:
)
),
),
(
fake_release(version="1.2.3"),
cr.EagerLazyRequirementSet(
frozenset(
[
cr.LazyRequirement(
distribution="foo.bar",
url=None,
extras=frozenset(),
specifier=cr.get_lazy_specifier_set(fake_release(version="1.2.3")),
marker=None,
),
]
)
),
),
(
cr.EagerLazyRelease(fake_release(version="1.2.3")),
cr.EagerLazyRequirementSet(
frozenset(
[
cr.LazyRequirement(
distribution="foo.bar",
url=None,
extras=frozenset(),
specifier=cr.get_lazy_specifier_set(fake_release(version="1.2.3")),
marker=None,
),
]
)
),
),
(
Specifier("==1.2.0"),
cr.EagerLazyRequirementSet(
Expand Down
Loading

0 comments on commit cf969df

Please sign in to comment.