Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
pylock.py906 linesDownload Raw Back to packaging
1from __future__ import annotations2 3import dataclasses4import logging5import re6from collections.abc import Mapping, Sequence7from dataclasses import dataclass8from datetime import datetime9from typing import (10    TYPE_CHECKING,11    Any,12    Callable,13    Protocol,14    TypeVar,15    cast,16)17from urllib.parse import urlparse18 19from .markers import Environment, Marker, default_environment20from .specifiers import SpecifierSet21from .tags import create_compatible_tags_selector, sys_tags22from .utils import (23    NormalizedName,24    is_normalized_name,25    parse_sdist_filename,26    parse_wheel_filename,27)28from .version import Version29 30if TYPE_CHECKING:  # pragma: no cover31    from collections.abc import Collection, Iterator32    from pathlib import Path33 34    from typing_extensions import Self35 36    from .tags import Tag37 38_logger = logging.getLogger(__name__)39 40__all__ = [41    "Package",42    "PackageArchive",43    "PackageDirectory",44    "PackageSdist",45    "PackageVcs",46    "PackageWheel",47    "Pylock",48    "PylockUnsupportedVersionError",49    "PylockValidationError",50    "is_valid_pylock_path",51]52 53 54def __dir__() -> list[str]:55    return __all__56 57 58_T = TypeVar("_T")59_T2 = TypeVar("_T2")60 61 62class _FromMappingProtocol(Protocol):  # pragma: no cover63    @classmethod64    def _from_dict(cls, d: Mapping[str, Any]) -> Self: ...65 66 67_FromMappingProtocolT = TypeVar("_FromMappingProtocolT", bound=_FromMappingProtocol)68 69 70_PYLOCK_FILE_NAME_RE = re.compile(r"^pylock\.([^.]+)\.toml$")71 72 73def is_valid_pylock_path(path: Path) -> bool:74    """Check if the given path is a valid pylock file path."""75    return path.name == "pylock.toml" or bool(_PYLOCK_FILE_NAME_RE.match(path.name))76 77 78def _toml_key(key: str) -> str:79    return key.replace("_", "-")80 81 82def _toml_value(key: str, value: Any) -> Any:  # noqa: ANN40183    if isinstance(value, (Version, Marker, SpecifierSet)):84        return str(value)85    if isinstance(value, Sequence) and key == "environments":86        return [str(v) for v in value]87    return value88 89 90def _toml_dict_factory(data: list[tuple[str, Any]]) -> dict[str, Any]:91    return {92        _toml_key(key): _toml_value(key, value)93        for key, value in data94        if value is not None95    }96 97 98def _get(d: Mapping[str, Any], expected_type: type[_T], key: str) -> _T | None:99    """Get a value from the dictionary and verify it's the expected type."""100    if (value := d.get(key)) is None:101        return None102    if not isinstance(value, expected_type):103        raise PylockValidationError(104            f"Unexpected type {type(value).__name__} "105            f"(expected {expected_type.__name__})",106            context=key,107        )108    return value109 110 111def _get_required(d: Mapping[str, Any], expected_type: type[_T], key: str) -> _T:112    """Get a required value from the dictionary and verify it's the expected type."""113    if (value := _get(d, expected_type, key)) is None:114        raise _PylockRequiredKeyError(key)115    return value116 117 118def _get_sequence(119    d: Mapping[str, Any], expected_item_type: type[_T], key: str120) -> Sequence[_T] | None:121    """Get a list value from the dictionary and verify it's the expected items type."""122    if (value := _get(d, Sequence, key)) is None:  # type: ignore[type-abstract]123        return None124    if isinstance(value, (str, bytes)):125        # special case: str and bytes are Sequences, but we want to reject it126        raise PylockValidationError(127            f"Unexpected type {type(value).__name__} (expected Sequence)",128            context=key,129        )130    for i, item in enumerate(value):131        if not isinstance(item, expected_item_type):132            raise PylockValidationError(133                f"Unexpected type {type(item).__name__} "134                f"(expected {expected_item_type.__name__})",135                context=f"{key}[{i}]",136            )137    return value138 139 140def _get_as(141    d: Mapping[str, Any],142    expected_type: type[_T],143    target_type: Callable[[_T], _T2],144    key: str,145) -> _T2 | None:146    """Get a value from the dictionary, verify it's the expected type,147    and convert to the target type.148 149    This assumes the target_type constructor accepts the value.150    """151    if (value := _get(d, expected_type, key)) is None:152        return None153    try:154        return target_type(value)155    except Exception as e:156        raise PylockValidationError(e, context=key) from e157 158 159def _get_required_as(160    d: Mapping[str, Any],161    expected_type: type[_T],162    target_type: Callable[[_T], _T2],163    key: str,164) -> _T2:165    """Get a required value from the dict, verify it's the expected type,166    and convert to the target type."""167    if (value := _get_as(d, expected_type, target_type, key)) is None:168        raise _PylockRequiredKeyError(key)169    return value170 171 172def _get_sequence_as(173    d: Mapping[str, Any],174    expected_item_type: type[_T],175    target_item_type: Callable[[_T], _T2],176    key: str,177) -> list[_T2] | None:178    """Get list value from dictionary and verify expected items type."""179    if (value := _get_sequence(d, expected_item_type, key)) is None:180        return None181    result = []182    try:183        for item in value:184            typed_item = target_item_type(item)185            result.append(typed_item)186    except Exception as e:187        raise PylockValidationError(e, context=f"{key}[{len(result)}]") from e188    return result189 190 191def _get_object(192    d: Mapping[str, Any], target_type: type[_FromMappingProtocolT], key: str193) -> _FromMappingProtocolT | None:194    """Get a dictionary value from the dictionary and convert it to a dataclass."""195    if (value := _get(d, Mapping, key)) is None:  # type: ignore[type-abstract]196        return None197    try:198        return target_type._from_dict(value)199    except Exception as e:200        raise PylockValidationError(e, context=key) from e201 202 203def _get_sequence_of_objects(204    d: Mapping[str, Any], target_item_type: type[_FromMappingProtocolT], key: str205) -> list[_FromMappingProtocolT] | None:206    """Get a list value from the dictionary and convert its items to a dataclass."""207    if (value := _get_sequence(d, Mapping, key)) is None:  # type: ignore[type-abstract]208        return None209    result: list[_FromMappingProtocolT] = []210    try:211        for item in value:212            typed_item = target_item_type._from_dict(item)213            result.append(typed_item)214    except Exception as e:215        raise PylockValidationError(e, context=f"{key}[{len(result)}]") from e216    return result217 218 219def _get_required_sequence_of_objects(220    d: Mapping[str, Any], target_item_type: type[_FromMappingProtocolT], key: str221) -> Sequence[_FromMappingProtocolT]:222    """Get a required list value from the dictionary and convert its items to a223    dataclass."""224    if (result := _get_sequence_of_objects(d, target_item_type, key)) is None:225        raise _PylockRequiredKeyError(key)226    return result227 228 229def _validate_normalized_name(name: str) -> NormalizedName:230    """Validate that a string is a NormalizedName."""231    if not is_normalized_name(name):232        raise PylockValidationError(f"Name {name!r} is not normalized")233    return NormalizedName(name)234 235 236def _validate_path_url(path: str | None, url: str | None) -> None:237    if not path and not url:238        raise PylockValidationError("path or url must be provided")239 240 241def _path_name(path: str | None) -> str | None:242    if not path:243        return None244    # If the path is relative it MAY use POSIX-style path separators explicitly245    # for portability246    if "/" in path:247        return path.rsplit("/", 1)[-1]248    elif "\\" in path:249        return path.rsplit("\\", 1)[-1]250    else:251        return path252 253 254def _url_name(url: str | None) -> str | None:255    if not url:256        return None257    url_path = urlparse(url).path258    return url_path.rsplit("/", 1)[-1]259 260 261def _validate_hashes(hashes: Mapping[str, Any]) -> Mapping[str, Any]:262    if not hashes:263        raise PylockValidationError("At least one hash must be provided")264    if not all(isinstance(hash_val, str) for hash_val in hashes.values()):265        raise PylockValidationError("Hash values must be strings")266    return hashes267 268 269class PylockValidationError(Exception):270    """Raised when when input data is not spec-compliant."""271 272    context: str | None = None273    message: str274 275    def __init__(276        self,277        cause: str | Exception,278        *,279        context: str | None = None,280    ) -> None:281        if isinstance(cause, PylockValidationError):282            if cause.context:283                self.context = (284                    f"{context}.{cause.context}" if context else cause.context285                )286            else:287                self.context = context288            self.message = cause.message289        else:290            self.context = context291            self.message = str(cause)292 293    def __str__(self) -> str:294        if self.context:295            return f"{self.message} in {self.context!r}"296        return self.message297 298 299class _PylockRequiredKeyError(PylockValidationError):300    def __init__(self, key: str) -> None:301        super().__init__("Missing required value", context=key)302 303 304class PylockUnsupportedVersionError(PylockValidationError):305    """Raised when encountering an unsupported `lock_version`."""306 307 308class PylockSelectError(Exception):309    """Base exception for errors raised by :meth:`Pylock.select`."""310 311 312@dataclass(frozen=True, init=False)313class PackageVcs:314    type: str315    url: str | None = None316    path: str | None = None317    requested_revision: str | None = None318    commit_id: str  # type: ignore[misc]319    subdirectory: str | None = None320 321    def __init__(322        self,323        *,324        type: str,325        url: str | None = None,326        path: str | None = None,327        requested_revision: str | None = None,328        commit_id: str,329        subdirectory: str | None = None,330    ) -> None:331        # In Python 3.10+ make dataclass kw_only=True and remove __init__332        object.__setattr__(self, "type", type)333        object.__setattr__(self, "url", url)334        object.__setattr__(self, "path", path)335        object.__setattr__(self, "requested_revision", requested_revision)336        object.__setattr__(self, "commit_id", commit_id)337        object.__setattr__(self, "subdirectory", subdirectory)338 339    @classmethod340    def _from_dict(cls, d: Mapping[str, Any]) -> Self:341        package_vcs = cls(342            type=_get_required(d, str, "type"),343            url=_get(d, str, "url"),344            path=_get(d, str, "path"),345            requested_revision=_get(d, str, "requested-revision"),346            commit_id=_get_required(d, str, "commit-id"),347            subdirectory=_get(d, str, "subdirectory"),348        )349        _validate_path_url(package_vcs.path, package_vcs.url)350        return package_vcs351 352 353@dataclass(frozen=True, init=False)354class PackageDirectory:355    path: str356    editable: bool | None = None357    subdirectory: str | None = None358 359    def __init__(360        self,361        *,362        path: str,363        editable: bool | None = None,364        subdirectory: str | None = None,365    ) -> None:366        # In Python 3.10+ make dataclass kw_only=True and remove __init__367        object.__setattr__(self, "path", path)368        object.__setattr__(self, "editable", editable)369        object.__setattr__(self, "subdirectory", subdirectory)370 371    @classmethod372    def _from_dict(cls, d: Mapping[str, Any]) -> Self:373        return cls(374            path=_get_required(d, str, "path"),375            editable=_get(d, bool, "editable"),376            subdirectory=_get(d, str, "subdirectory"),377        )378 379 380@dataclass(frozen=True, init=False)381class PackageArchive:382    url: str | None = None383    path: str | None = None384    size: int | None = None385    upload_time: datetime | None = None386    hashes: Mapping[str, str]  # type: ignore[misc]387    subdirectory: str | None = None388 389    def __init__(390        self,391        *,392        url: str | None = None,393        path: str | None = None,394        size: int | None = None,395        upload_time: datetime | None = None,396        hashes: Mapping[str, str],397        subdirectory: str | None = None,398    ) -> None:399        # In Python 3.10+ make dataclass kw_only=True and remove __init__400        object.__setattr__(self, "url", url)401        object.__setattr__(self, "path", path)402        object.__setattr__(self, "size", size)403        object.__setattr__(self, "upload_time", upload_time)404        object.__setattr__(self, "hashes", hashes)405        object.__setattr__(self, "subdirectory", subdirectory)406 407    @classmethod408    def _from_dict(cls, d: Mapping[str, Any]) -> Self:409        package_archive = cls(410            url=_get(d, str, "url"),411            path=_get(d, str, "path"),412            size=_get(d, int, "size"),413            upload_time=_get(d, datetime, "upload-time"),414            hashes=_get_required_as(d, Mapping, _validate_hashes, "hashes"),  # type: ignore[type-abstract]415            subdirectory=_get(d, str, "subdirectory"),416        )417        _validate_path_url(package_archive.path, package_archive.url)418        return package_archive419 420 421@dataclass(frozen=True, init=False)422class PackageSdist:423    name: str | None = None424    upload_time: datetime | None = None425    url: str | None = None426    path: str | None = None427    size: int | None = None428    hashes: Mapping[str, str]  # type: ignore[misc]429 430    def __init__(431        self,432        *,433        name: str | None = None,434        upload_time: datetime | None = None,435        url: str | None = None,436        path: str | None = None,437        size: int | None = None,438        hashes: Mapping[str, str],439    ) -> None:440        # In Python 3.10+ make dataclass kw_only=True and remove __init__441        object.__setattr__(self, "name", name)442        object.__setattr__(self, "upload_time", upload_time)443        object.__setattr__(self, "url", url)444        object.__setattr__(self, "path", path)445        object.__setattr__(self, "size", size)446        object.__setattr__(self, "hashes", hashes)447 448    @classmethod449    def _from_dict(cls, d: Mapping[str, Any]) -> Self:450        package_sdist = cls(451            name=_get(d, str, "name"),452            upload_time=_get(d, datetime, "upload-time"),453            url=_get(d, str, "url"),454            path=_get(d, str, "path"),455            size=_get(d, int, "size"),456            hashes=_get_required_as(d, Mapping, _validate_hashes, "hashes"),  # type: ignore[type-abstract]457        )458        _validate_path_url(package_sdist.path, package_sdist.url)459        return package_sdist460 461    @property462    def filename(self) -> str:463        """Get the filename of the sdist."""464        filename = self.name or _path_name(self.path) or _url_name(self.url)465        if not filename:466            raise PylockValidationError("Cannot determine sdist filename")467        return filename468 469 470@dataclass(frozen=True, init=False)471class PackageWheel:472    name: str | None = None473    upload_time: datetime | None = None474    url: str | None = None475    path: str | None = None476    size: int | None = None477    hashes: Mapping[str, str]  # type: ignore[misc]478 479    def __init__(480        self,481        *,482        name: str | None = None,483        upload_time: datetime | None = None,484        url: str | None = None,485        path: str | None = None,486        size: int | None = None,487        hashes: Mapping[str, str],488    ) -> None:489        # In Python 3.10+ make dataclass kw_only=True and remove __init__490        object.__setattr__(self, "name", name)491        object.__setattr__(self, "upload_time", upload_time)492        object.__setattr__(self, "url", url)493        object.__setattr__(self, "path", path)494        object.__setattr__(self, "size", size)495        object.__setattr__(self, "hashes", hashes)496 497    @classmethod498    def _from_dict(cls, d: Mapping[str, Any]) -> Self:499        package_wheel = cls(500            name=_get(d, str, "name"),501            upload_time=_get(d, datetime, "upload-time"),502            url=_get(d, str, "url"),503            path=_get(d, str, "path"),504            size=_get(d, int, "size"),505            hashes=_get_required_as(d, Mapping, _validate_hashes, "hashes"),  # type: ignore[type-abstract]506        )507        _validate_path_url(package_wheel.path, package_wheel.url)508        return package_wheel509 510    @property511    def filename(self) -> str:512        """Get the filename of the wheel."""513        filename = self.name or _path_name(self.path) or _url_name(self.url)514        if not filename:515            raise PylockValidationError("Cannot determine wheel filename")516        return filename517 518 519@dataclass(frozen=True, init=False)520class Package:521    name: NormalizedName522    version: Version | None = None523    marker: Marker | None = None524    requires_python: SpecifierSet | None = None525    dependencies: Sequence[Mapping[str, Any]] | None = None526    vcs: PackageVcs | None = None527    directory: PackageDirectory | None = None528    archive: PackageArchive | None = None529    index: str | None = None530    sdist: PackageSdist | None = None531    wheels: Sequence[PackageWheel] | None = None532    attestation_identities: Sequence[Mapping[str, Any]] | None = None533    tool: Mapping[str, Any] | None = None534 535    def __init__(536        self,537        *,538        name: NormalizedName,539        version: Version | None = None,540        marker: Marker | None = None,541        requires_python: SpecifierSet | None = None,542        dependencies: Sequence[Mapping[str, Any]] | None = None,543        vcs: PackageVcs | None = None,544        directory: PackageDirectory | None = None,545        archive: PackageArchive | None = None,546        index: str | None = None,547        sdist: PackageSdist | None = None,548        wheels: Sequence[PackageWheel] | None = None,549        attestation_identities: Sequence[Mapping[str, Any]] | None = None,550        tool: Mapping[str, Any] | None = None,551    ) -> None:552        # In Python 3.10+ make dataclass kw_only=True and remove __init__553        object.__setattr__(self, "name", name)554        object.__setattr__(self, "version", version)555        object.__setattr__(self, "marker", marker)556        object.__setattr__(self, "requires_python", requires_python)557        object.__setattr__(self, "dependencies", dependencies)558        object.__setattr__(self, "vcs", vcs)559        object.__setattr__(self, "directory", directory)560        object.__setattr__(self, "archive", archive)561        object.__setattr__(self, "index", index)562        object.__setattr__(self, "sdist", sdist)563        object.__setattr__(self, "wheels", wheels)564        object.__setattr__(self, "attestation_identities", attestation_identities)565        object.__setattr__(self, "tool", tool)566 567    @classmethod568    def _from_dict(cls, d: Mapping[str, Any]) -> Self:569        package = cls(570            name=_get_required_as(d, str, _validate_normalized_name, "name"),571            version=_get_as(d, str, Version, "version"),572            requires_python=_get_as(d, str, SpecifierSet, "requires-python"),573            dependencies=_get_sequence(d, Mapping, "dependencies"),  # type: ignore[type-abstract]574            marker=_get_as(d, str, Marker, "marker"),575            vcs=_get_object(d, PackageVcs, "vcs"),576            directory=_get_object(d, PackageDirectory, "directory"),577            archive=_get_object(d, PackageArchive, "archive"),578            index=_get(d, str, "index"),579            sdist=_get_object(d, PackageSdist, "sdist"),580            wheels=_get_sequence_of_objects(d, PackageWheel, "wheels"),581            attestation_identities=_get_sequence(d, Mapping, "attestation-identities"),  # type: ignore[type-abstract]582            tool=_get(d, Mapping, "tool"),  # type: ignore[type-abstract]583        )584        distributions = bool(package.sdist) + len(package.wheels or [])585        direct_urls = (586            bool(package.vcs) + bool(package.directory) + bool(package.archive)587        )588        if distributions > 0 and direct_urls > 0:589            raise PylockValidationError(590                "None of vcs, directory, archive must be set if sdist or wheels are set"591            )592        if distributions == 0 and direct_urls != 1:593            raise PylockValidationError(594                "Exactly one of vcs, directory, archive must be set "595                "if sdist and wheels are not set"596            )597        for i, wheel in enumerate(package.wheels or []):598            try:599                (name, version, _, _) = parse_wheel_filename(wheel.filename)600            except Exception as e:601                raise PylockValidationError(602                    f"Invalid wheel filename {wheel.filename!r}",603                    context=f"wheels[{i}]",604                ) from e605            if name != package.name:606                raise PylockValidationError(607                    f"Name in {wheel.filename!r} is not consistent with "608                    f"package name {package.name!r}",609                    context=f"wheels[{i}]",610                )611            if package.version and version != package.version:612                raise PylockValidationError(613                    f"Version in {wheel.filename!r} is not consistent with "614                    f"package version {str(package.version)!r}",615                    context=f"wheels[{i}]",616                )617        if package.sdist:618            try:619                name, version = parse_sdist_filename(package.sdist.filename)620            except Exception as e:621                raise PylockValidationError(622                    f"Invalid sdist filename {package.sdist.filename!r}",623                    context="sdist",624                ) from e625            if name != package.name:626                raise PylockValidationError(627                    f"Name in {package.sdist.filename!r} is not consistent with "628                    f"package name {package.name!r}",629                    context="sdist",630                )631            if package.version and version != package.version:632                raise PylockValidationError(633                    f"Version in {package.sdist.filename!r} is not consistent with "634                    f"package version {str(package.version)!r}",635                    context="sdist",636                )637        try:638            for i, attestation_identity in enumerate(  # noqa: B007639                package.attestation_identities or []640            ):641                _get_required(attestation_identity, str, "kind")642        except Exception as e:643            raise PylockValidationError(644                e, context=f"attestation-identities[{i}]"645            ) from e646        return package647 648    @property649    def is_direct(self) -> bool:650        return not (self.sdist or self.wheels)651 652 653@dataclass(frozen=True, init=False)654class Pylock:655    """A class representing a pylock file."""656 657    lock_version: Version658    environments: Sequence[Marker] | None = None659    requires_python: SpecifierSet | None = None660    extras: Sequence[NormalizedName] | None = None661    dependency_groups: Sequence[str] | None = None662    default_groups: Sequence[str] | None = None663    created_by: str  # type: ignore[misc]664    packages: Sequence[Package]  # type: ignore[misc]665    tool: Mapping[str, Any] | None = None666 667    def __init__(668        self,669        *,670        lock_version: Version,671        environments: Sequence[Marker] | None = None,672        requires_python: SpecifierSet | None = None,673        extras: Sequence[NormalizedName] | None = None,674        dependency_groups: Sequence[str] | None = None,675        default_groups: Sequence[str] | None = None,676        created_by: str,677        packages: Sequence[Package],678        tool: Mapping[str, Any] | None = None,679    ) -> None:680        # In Python 3.10+ make dataclass kw_only=True and remove __init__681        object.__setattr__(self, "lock_version", lock_version)682        object.__setattr__(self, "environments", environments)683        object.__setattr__(self, "requires_python", requires_python)684        object.__setattr__(self, "extras", extras)685        object.__setattr__(self, "dependency_groups", dependency_groups)686        object.__setattr__(self, "default_groups", default_groups)687        object.__setattr__(self, "created_by", created_by)688        object.__setattr__(self, "packages", packages)689        object.__setattr__(self, "tool", tool)690 691    @classmethod692    def _from_dict(cls, d: Mapping[str, Any]) -> Self:693        pylock = cls(694            lock_version=_get_required_as(d, str, Version, "lock-version"),695            environments=_get_sequence_as(d, str, Marker, "environments"),696            extras=_get_sequence_as(d, str, _validate_normalized_name, "extras"),697            dependency_groups=_get_sequence(d, str, "dependency-groups"),698            default_groups=_get_sequence(d, str, "default-groups"),699            created_by=_get_required(d, str, "created-by"),700            requires_python=_get_as(d, str, SpecifierSet, "requires-python"),701            packages=_get_required_sequence_of_objects(d, Package, "packages"),702            tool=_get(d, Mapping, "tool"),  # type: ignore[type-abstract]703        )704        if not Version("1") <= pylock.lock_version < Version("2"):705            raise PylockUnsupportedVersionError(706                f"pylock version {pylock.lock_version} is not supported"707            )708        if pylock.lock_version > Version("1.0"):709            _logger.warning(710                "pylock minor version %s is not supported", pylock.lock_version711            )712        return pylock713 714    @classmethod715    def from_dict(cls, d: Mapping[str, Any], /) -> Self:716        """Create and validate a Pylock instance from a TOML dictionary.717 718        Raises :class:`PylockValidationError` if the input data is not719        spec-compliant.720        """721        return cls._from_dict(d)722 723    def to_dict(self) -> Mapping[str, Any]:724        """Convert the Pylock instance to a TOML dictionary."""725        return dataclasses.asdict(self, dict_factory=_toml_dict_factory)726 727    def validate(self) -> None:728        """Validate the Pylock instance against the specification.729 730        Raises :class:`PylockValidationError` otherwise."""731        self.from_dict(self.to_dict())732 733    def select(734        self,735        *,736        environment: Environment | None = None,737        tags: Sequence[Tag] | None = None,738        extras: Collection[str] | None = None,739        dependency_groups: Collection[str] | None = None,740    ) -> Iterator[741        tuple[742            Package,743            PackageVcs744            | PackageDirectory745            | PackageArchive746            | PackageWheel747            | PackageSdist,748        ]749    ]:750        """Select what to install from the lock file.751 752        The *environment* and *tags* parameters represent the environment being753        selected for. If unspecified, ``packaging.markers.default_environment()`` and754        ``packaging.tags.sys_tags()`` are used.755 756        The *extras* parameter represents the extras to install.757 758        The *dependency_groups* parameter represents the groups to install. If759        unspecified, the default groups are used.760 761        This method must be used on valid Pylock instances (i.e. one obtained762        from :meth:`Pylock.from_dict` or if constructed manually, after calling763        :meth:`Pylock.validate`).764        """765        compatible_tags_selector = create_compatible_tags_selector(tags or sys_tags())766 767        # #. Gather the extras and dependency groups to install and set ``extras`` and768        #    ``dependency_groups`` for marker evaluation, respectively.769        #770        #    #. ``extras`` SHOULD be set to the empty set by default.771        #    #. ``dependency_groups`` SHOULD be the set created from772        #       :ref:`pylock-default-groups` by default.773        env = cast(774            "dict[str, str | frozenset[str]]",775            dict(776                environment or {},  # Marker.evaluate will fill-up777                extras=frozenset(extras or []),778                dependency_groups=frozenset(779                    (self.default_groups or [])780                    if dependency_groups is None  # to allow selecting no group781                    else dependency_groups782                ),783            ),784        )785        env_python_full_version = (786            environment["python_full_version"]787            if environment788            else default_environment()["python_full_version"]789        )790 791        # #. Check if the metadata version specified by :ref:`pylock-lock-version` is792        #    supported; an error or warning MUST be raised as appropriate.793        # Covered by lock.validate() which is a precondition for this method.794 795        # #. If :ref:`pylock-requires-python` is specified, check that the environment796        #    being installed for meets the requirement; an error MUST be raised if it is797        #    not met.798        if self.requires_python and not self.requires_python.contains(799            env_python_full_version,800        ):801            raise PylockSelectError(802                f"python_full_version {env_python_full_version!r} "803                f"in provided environment does not satisfy the Python version "804                f"requirement {str(self.requires_python)!r}"805            )806 807        # #. If :ref:`pylock-environments` is specified, check that at least one of the808        #    environment marker expressions is satisfied; an error MUST be raised if no809        #    expression is satisfied.810        if self.environments:811            for env_marker in self.environments:812                if env_marker.evaluate(813                    cast("dict[str, str]", environment or {}), context="requirement"814                ):815                    break816            else:817                raise PylockSelectError(818                    "Provided environment does not satisfy any of the "819                    "environments specified in the lock file"820                )821 822        # #. For each package listed in :ref:`pylock-packages`:823        selected_packages_by_name: dict[str, tuple[int, Package]] = {}824        for package_index, package in enumerate(self.packages):825            # #. If :ref:`pylock-packages-marker` is specified, check if it is826            #    satisfied;if it isn't, skip to the next package.827            if package.marker and not package.marker.evaluate(env, context="lock_file"):828                continue829 830            # #. If :ref:`pylock-packages-requires-python` is specified, check if it is831            #    satisfied; an error MUST be raised if it isn't.832            if package.requires_python and not package.requires_python.contains(833                env_python_full_version,834            ):835                raise PylockSelectError(836                    f"python_full_version {env_python_full_version!r} "837                    f"in provided environment does not satisfy the Python version "838                    f"requirement {str(package.requires_python)!r} for package "839                    f"{package.name!r} at packages[{package_index}]"840                )841 842            # #. Check that no other conflicting instance of the package has been slated843            #    to be installed; an error about the ambiguity MUST be raised otherwise.844            if package.name in selected_packages_by_name:845                raise PylockSelectError(846                    f"Multiple packages with the name {package.name!r} are "847                    f"selected at packages[{package_index}] and "848                    f"packages[{selected_packages_by_name[package.name][0]}]"849                )850 851            # #. Check that the source of the package is specified appropriately (i.e.852            #    there are no conflicting sources in the package entry);853            #    an error MUST be raised if any issues are found.854            # Covered by lock.validate() which is a precondition for this method.855 856            # #. Add the package to the set of packages to install.857            selected_packages_by_name[package.name] = (package_index, package)858 859        # #. For each package to be installed:860        for package_index, package in selected_packages_by_name.values():861            # - If :ref:`pylock-packages-vcs` is set:862            if package.vcs is not None:863                yield package, package.vcs864 865            # - Else if :ref:`pylock-packages-directory` is set:866            elif package.directory is not None:867                yield package, package.directory868 869            # - Else if :ref:`pylock-packages-archive` is set:870            elif package.archive is not None:871                yield package, package.archive872 873            # - Else if there are entries for :ref:`pylock-packages-wheels`:874            elif package.wheels:875                # #. Look for the appropriate wheel file based on876                #    :ref:`pylock-packages-wheels-name`; if one is not found then move877                #    on to :ref:`pylock-packages-sdist` or an error MUST be raised about878                #    a lack of source for the project.879                best_wheel = next(880                    compatible_tags_selector(881                        (wheel, parse_wheel_filename(wheel.filename)[-1])882                        for wheel in package.wheels883                    ),884                    None,885                )886                if best_wheel:887                    yield package, best_wheel888                elif package.sdist is not None:889                    yield package, package.sdist890                else:891                    raise PylockSelectError(892                        f"No wheel found matching the provided tags "893                        f"for package {package.name!r} "894                        f"at packages[{package_index}], "895                        f"and no sdist available as a fallback"896                    )897 898            # - Else if no :ref:`pylock-packages-wheels` file is found or899            #   :ref:`pylock-packages-sdist` is solely set:900            elif package.sdist is not None:901                yield package, package.sdist902 903            else:904                # Covered by lock.validate() which is a precondition for this method.905                raise NotImplementedError  # pragma: no cover906 
codekingpro/portable-devtools · Team Ai