Skip to content

Commit 110467f

Browse files
committed
pylock select: full coverage and fixes
1 parent 0256dca commit 110467f

File tree

3 files changed

+416
-108
lines changed

3 files changed

+416
-108
lines changed

src/packaging/pylock.py

Lines changed: 46 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from datetime import datetime
99
from typing import TYPE_CHECKING, Any, Callable, Protocol, TypeVar, cast
1010

11-
from .markers import Marker, default_environment
11+
from .markers import Environment, Marker, default_environment
1212
from .specifiers import SpecifierSet
1313
from .tags import sys_tags
1414
from .utils import NormalizedName, is_normalized_name, parse_wheel_filename
@@ -17,10 +17,10 @@
1717
if TYPE_CHECKING: # pragma: no cover
1818
from collections.abc import Collection, Iterator
1919
from pathlib import Path
20+
from typing import AbstractSet
2021

2122
from typing_extensions import Self
2223

23-
from .markers import Environment
2424
from .tags import Tag
2525

2626
_logger = logging.getLogger(__name__)
@@ -47,6 +47,11 @@ def __dir__() -> list[str]:
4747
_T2 = TypeVar("_T2")
4848

4949

50+
class _PylockEnvironment(Environment):
51+
extras: AbstractSet[str]
52+
dependency_groups: AbstractSet[str]
53+
54+
5055
class _FromMappingProtocol(Protocol): # pragma: no cover
5156
@classmethod
5257
def _from_dict(cls, d: Mapping[str, Any]) -> Self: ...
@@ -649,7 +654,7 @@ def select(
649654
tags: Sequence[Tag] | None = None,
650655
extras: Collection[str] | None = None,
651656
dependency_groups: Collection[str] | None = None,
652-
) -> Iterator[ # XXX or Iterable?
657+
) -> Iterator[
653658
tuple[
654659
Package,
655660
PackageVcs
@@ -675,8 +680,6 @@ def select(
675680
valid Pylock instances (i.e. one obtained from :meth:`Pylock.from_dict`
676681
or if constructed manually, after calling :meth:`Pylock.validate`).
677682
"""
678-
if environment is None:
679-
environment = default_environment()
680683
if tags is None:
681684
tags = list(sys_tags())
682685

@@ -686,14 +689,23 @@ def select(
686689
# #. ``extras`` SHOULD be set to the empty set by default.
687690
# #. ``dependency_groups`` SHOULD be the set created from
688691
# :ref:`pylock-default-groups` by default.
689-
env: dict[str, str | frozenset[str]] = {
690-
**cast("dict[str, str]", environment),
691-
"extras": frozenset(extras or []),
692-
"dependency_groups": frozenset(
693-
dependency_groups or self.default_groups or []
692+
env = cast(
693+
"dict[str, str | frozenset[str]]",
694+
dict(
695+
environment or {}, # Marker.evaluate will fill-up
696+
extras=frozenset(extras or []),
697+
dependency_groups=frozenset(
698+
(self.default_groups or [])
699+
if dependency_groups is None # to allow selecting no groups
700+
else dependency_groups
701+
),
694702
),
695-
}
696-
env_python_version = environment.get("python_version")
703+
)
704+
env_python_version = (
705+
environment["python_version"]
706+
if environment
707+
else default_environment()["python_version"]
708+
)
697709

698710
# #. Check if the metadata version specified by :ref:`pylock-lock-version` is
699711
# supported; an error or warning MUST be raised as appropriate.
@@ -702,25 +714,21 @@ def select(
702714
# #. If :ref:`pylock-requires-python` is specified, check that the environment
703715
# being installed for meets the requirement; an error MUST be raised if it is
704716
# not met.
705-
if self.requires_python is not None:
706-
if not env_python_version:
707-
raise PylockSelectError(
708-
f"Provided environment does not specify a Python version, "
709-
f"but the lock file requires Python {self.requires_python!r}"
710-
)
711-
if not self.requires_python.contains(env_python_version, prereleases=True):
712-
# XXX confirm prereleases=True
713-
raise PylockSelectError(
714-
f"Provided environment does not satisfy the Python version "
715-
f"requirement {self.requires_python!r}"
716-
)
717+
if self.requires_python and not self.requires_python.contains(
718+
env_python_version,
719+
prereleases=True, # XXX confirm prereleases=True
720+
):
721+
raise PylockSelectError(
722+
f"Provided environment does not satisfy the Python version "
723+
f"requirement {self.requires_python!r}"
724+
)
717725

718726
# #. If :ref:`pylock-environments` is specified, check that at least one of the
719727
# environment marker expressions is satisfied; an error MUST be raised if no
720728
# expression is satisfied.
721729
if self.environments:
722730
for env_marker in self.environments:
723-
if env_marker.evaluate(env, context="lock_file"): # XXX check context
731+
if env_marker.evaluate(env, context="lock_file"):
724732
break
725733
else:
726734
raise PylockSelectError(
@@ -733,29 +741,20 @@ def select(
733741
for package_index, package in enumerate(self.packages):
734742
# #. If :ref:`pylock-packages-marker` is specified, check if it is
735743
# satisfied;if it isn't, skip to the next package.
736-
if package.marker and not package.marker.evaluate(
737-
env, context="requirement"
738-
): # XXX check context
744+
if package.marker and not package.marker.evaluate(env, context="lock_file"):
739745
continue
740746

741747
# #. If :ref:`pylock-packages-requires-python` is specified, check if it is
742748
# satisfied; an error MUST be raised if it isn't.
743-
if package.requires_python:
744-
if not env_python_version:
745-
raise PylockSelectError(
746-
f"Provided environment does not specify a Python version, "
747-
f"but package {package.name!r} at packages[{package_index}] "
748-
f"requires Python {package.requires_python!r}"
749-
)
750-
if not package.requires_python.contains(
751-
env_python_version, prereleases=True
752-
):
753-
# XXX confirm prereleases=True
754-
raise PylockSelectError(
755-
f"Provided environment does not satisfy the Python version "
756-
f"requirement {package.requires_python!r} for package "
757-
f"{package.name!r} at packages[{package_index}]"
758-
)
749+
if package.requires_python and not package.requires_python.contains(
750+
env_python_version,
751+
prereleases=True, # XXX confirm prereleases=True
752+
):
753+
raise PylockSelectError(
754+
f"Provided environment does not satisfy the Python version "
755+
f"requirement {package.requires_python!r} for package "
756+
f"{package.name!r} at packages[{package_index}]"
757+
)
759758

760759
# #. Check that no other conflicting instance of the package has been slated
761760
# to be installed; an error about the ambiguity MUST be raised otherwise.
@@ -769,7 +768,7 @@ def select(
769768
# #. Check that the source of the package is specified appropriately (i.e.
770769
# there are no conflicting sources in the package entry);
771770
# an error MUST be raised if any issues are found.
772-
# Covered by lock.validate() above.
771+
# Covered by lock.validate() which is a precondition for this method.
773772

774773
# #. Add the package to the set of packages to install.
775774
selected_packages_by_name[package.name] = (package_index, package)
@@ -817,5 +816,5 @@ def select(
817816
yield package, package.sdist
818817

819818
else:
820-
# Covered by lock.validate() above.
821-
raise NotImplementedError
819+
# Covered by lock.validate() which is a precondition for this method.
820+
raise NotImplementedError # pragma: no cover

tests/test_pylock.py

Lines changed: 1 addition & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,24 @@
22

33
import datetime
44
import sys
5-
from dataclasses import dataclass
65
from pathlib import Path
76
from typing import Any
87

98
import pytest
109
import tomli_w
1110

12-
from packaging.markers import Environment, Marker
11+
from packaging.markers import Marker
1312
from packaging.pylock import (
1413
Package,
1514
PackageDirectory,
1615
PackageVcs,
1716
PackageWheel,
1817
Pylock,
19-
PylockSelectError,
2018
PylockUnsupportedVersionError,
2119
PylockValidationError,
2220
is_valid_pylock_path,
2321
)
2422
from packaging.specifiers import SpecifierSet
25-
from packaging.tags import Tag
2623
from packaging.utils import NormalizedName
2724
from packaging.version import Version
2825

@@ -581,60 +578,3 @@ def test_validate_attestation_identity_invalid_kind() -> None:
581578
"Unexpected type int (expected str) "
582579
"in 'packages[0].attestation-identities[0].kind'"
583580
)
584-
585-
586-
@dataclass
587-
class Platform:
588-
tags: list[Tag]
589-
environment: Environment
590-
591-
592-
_py312_linux = Platform(
593-
tags=[
594-
Tag("cp312", "cp312", "manylinux_2_17_x86_64"),
595-
Tag("py3", "none", "any"),
596-
],
597-
environment={
598-
"implementation_name": "cpython",
599-
"implementation_version": "3.12.12",
600-
"os_name": "posix",
601-
"platform_machine": "x86_64",
602-
"platform_release": "6.8.0-100-generic",
603-
"platform_system": "Linux",
604-
"platform_version": "#100-Ubuntu SMP PREEMPT_DYNAMIC",
605-
"python_full_version": "3.12.12",
606-
"platform_python_implementation": "CPython",
607-
"python_version": "3.12",
608-
"sys_platform": "linux",
609-
},
610-
)
611-
612-
613-
def test_select_smoke_test() -> None:
614-
pylock_path = Path(__file__).parent / "pylock" / "pylock.spec-example.toml"
615-
lock = Pylock.from_dict(tomllib.loads(pylock_path.read_text()))
616-
for package, dist in lock.select(
617-
tags=_py312_linux.tags,
618-
environment=_py312_linux.environment,
619-
):
620-
assert isinstance(package, Package)
621-
assert isinstance(dist, PackageWheel)
622-
623-
624-
def test_select_require_python_mismatch() -> None:
625-
pylock = Pylock(
626-
lock_version=Version("1.0"),
627-
created_by="some_tool",
628-
requires_python=SpecifierSet("==3.14.*"),
629-
packages=[],
630-
)
631-
with pytest.raises(
632-
PylockSelectError,
633-
match="Provided environment does not satisfy the Python version requirement",
634-
):
635-
list(
636-
pylock.select(
637-
tags=_py312_linux.tags,
638-
environment=_py312_linux.environment,
639-
)
640-
)

0 commit comments

Comments
 (0)