@@ -273,6 +273,10 @@ class PylockUnsupportedVersionError(PylockValidationError):
273273 """Raised when encountering an unsupported `lock_version`."""
274274
275275
276+ class PylockSelectError (Exception ):
277+ """Base exception for errors raised by :method:`Pylock.select()`."""
278+
279+
276280@dataclass (frozen = True , init = False )
277281class PackageVcs :
278282 type : str
@@ -638,186 +642,188 @@ def validate(self) -> None:
638642 Raises :class:`PylockValidationError` otherwise."""
639643 self .from_dict (self .to_dict ())
640644
645+ def select (
646+ self ,
647+ * ,
648+ environment : Environment | None = None ,
649+ tags : Sequence [Tag ] | None = None ,
650+ extras : Collection [str ] | None = None ,
651+ dependency_groups : Collection [str ] | None = None ,
652+ ) -> Iterator [ # XXX or Iterable?
653+ tuple [
654+ Package ,
655+ PackageVcs
656+ | PackageDirectory
657+ | PackageArchive
658+ | PackageWheel
659+ | PackageSdist ,
660+ ]
661+ ]:
662+ """Select what to install from the lock file.
663+
664+ The *environment* and *tags* parameters represent the environment being
665+ selected for. If unspecified, ``packaging.markers.default_environment()``
666+ and ``packaging.tags.sys_tags()`` are used.
667+
668+ The *extras* parameter represents the extras to install.
669+
670+ The *dependency_groups* parameter represents the groups to install. If
671+ unspecified, the default groups are used.
672+ """
673+ if environment is None :
674+ environment = default_environment ()
675+ if tags is None :
676+ tags = list (sys_tags ())
677+
678+ # Validating the lock object covers some parts of the spec, such as checking
679+ # the lock file version, and conflicting sources for packages.
680+ # XXX we could document that we expect a valid lock object here.
681+ self .validate ()
682+
683+ # #. Gather the extras and dependency groups to install and set ``extras`` and
684+ # ``dependency_groups`` for marker evaluation, respectively.
685+ #
686+ # #. ``extras`` SHOULD be set to the empty set by default.
687+ # #. ``dependency_groups`` SHOULD be the set created from
688+ # :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 []
694+ ),
695+ }
696+ env_python_version = environment .get ("python_version" )
697+
698+ # #. Check if the metadata version specified by :ref:`pylock-lock-version` is
699+ # supported; an error or warning MUST be raised as appropriate.
700+ # Covered by lock.validate() above.
641701
642- class PylockSelectError (Exception ):
643- """Base exception for errors raised by :func:`select()`."""
644-
645-
646- def select (
647- lock : Pylock ,
648- * ,
649- environment : Environment | None = None ,
650- tags : Sequence [Tag ] | None = None ,
651- extras : Collection [str ] | None = None ,
652- dependency_groups : Collection [str ] | None = None ,
653- ) -> Iterator [ # XXX or Iterable?
654- tuple [
655- Package ,
656- PackageVcs | PackageDirectory | PackageArchive | PackageWheel | PackageSdist ,
657- ]
658- ]:
659- """Select what to install from the lock file.
660-
661- The *environment* and *tags* parameters represent the environment being
662- selected for. If unspecified, ``packaging.markers.default_environment()``
663- and ``packaging.tags.sys_tags()`` are used.
664-
665- The *extras* parameter represents the extras to install.
666-
667- The *dependency_groups* parameter represents the groups to install. If
668- unspecified, the default groups are used.
669- """
670- if environment is None :
671- environment = default_environment ()
672- if tags is None :
673- tags = list (sys_tags ())
674-
675- # Validating the lock object covers some parts of the spec, such as checking
676- # the lock file version, and conflicting sources for packages.
677- # XXX we could document that we expect a valid lock object here.
678- lock .validate ()
679-
680- # #. Gather the extras and dependency groups to install and set ``extras`` and
681- # ``dependency_groups`` for marker evaluation, respectively.
682- #
683- # #. ``extras`` SHOULD be set to the empty set by default.
684- # #. ``dependency_groups`` SHOULD be the set created from
685- # :ref:`pylock-default-groups` by default.
686- env : dict [str , str | frozenset [str ]] = {
687- ** cast ("dict[str, str]" , environment ),
688- "extras" : frozenset (extras or []), # XXX is normalization needed?
689- "dependency_groups" : frozenset (
690- dependency_groups or lock .default_groups or []
691- ), # XXX is normalization needed?
692- }
693- env_python_version = environment .get ("python_version" )
694-
695- # #. Check if the metadata version specified by :ref:`pylock-lock-version` is
696- # supported; an error or warning MUST be raised as appropriate.
697- # Covered by lock.validate() above.
698-
699- # #. If :ref:`pylock-requires-python` is specified, check that the environment
700- # being installed for meets the requirement; an error MUST be raised if it is
701- # not met.
702- if lock .requires_python is not None :
703- if not env_python_version :
704- raise PylockSelectError (
705- f"Provided environment does not specify a Python version, "
706- f"but the lock file requires Python { lock .requires_python !r} "
707- )
708- if not lock .requires_python .contains (env_python_version , prereleases = True ):
709- # XXX confirm prereleases=True
710- raise PylockSelectError (
711- f"Provided environment does not satisfy the Python version "
712- f"requirement { lock .requires_python !r} "
713- )
714-
715- # #. If :ref:`pylock-environments` is specified, check that at least one of the
716- # environment marker expressions is satisfied; an error MUST be raised if no
717- # expression is satisfied.
718- if lock .environments :
719- for env_marker in lock .environments :
720- if env_marker .evaluate (env , context = "lock_file" ): # XXX check context
721- break
722- else :
723- raise PylockSelectError (
724- "Provided environment does not satisfy any of the "
725- "environments specified in the lock file"
726- )
727-
728- # #. For each package listed in :ref:`pylock-packages`:
729- selected_packages_by_name : dict [str , tuple [int , Package ]] = {}
730- for package_index , package in enumerate (lock .packages ):
731- # #. If :ref:`pylock-packages-marker` is specified, check if it is satisfied;
732- # if it isn't, skip to the next package.
733- if package .marker and not package .marker .evaluate (
734- env , context = "requirement"
735- ): # XXX check context
736- continue
737-
738- # #. If :ref:`pylock-packages-requires-python` is specified, check if it is
739- # satisfied; an error MUST be raised if it isn't.
740- if package .requires_python :
702+ # #. If :ref:`pylock-requires-python` is specified, check that the environment
703+ # being installed for meets the requirement; an error MUST be raised if it is
704+ # not met.
705+ if self .requires_python is not None :
741706 if not env_python_version :
742707 raise PylockSelectError (
743708 f"Provided environment does not specify a Python version, "
744- f"but package { package .name !r} at packages[{ package_index } ] "
745- f"requires Python { package .requires_python !r} "
709+ f"but the lock file requires Python { self .requires_python !r} "
746710 )
747- if not package .requires_python .contains (
748- env_python_version , prereleases = True
749- ):
711+ if not self .requires_python .contains (env_python_version , prereleases = True ):
750712 # XXX confirm prereleases=True
751713 raise PylockSelectError (
752714 f"Provided environment does not satisfy the Python version "
753- f"requirement { package .requires_python !r} for package "
754- f"{ package .name !r} at packages[{ package_index } ]"
715+ f"requirement { self .requires_python !r} "
755716 )
756717
757- # #. Check that no other conflicting instance of the package has been slated to
758- # be installed; an error about the ambiguity MUST be raised otherwise.
759- if package .name in selected_packages_by_name :
760- raise PylockSelectError (
761- f"Multiple packages with the name { package .name !r} are "
762- f"selected at packages[{ package_index } ] and "
763- f"packages[{ selected_packages_by_name [package .name ][0 ]} ]"
764- )
765-
766- # #. Check that the source of the package is specified appropriately (i.e.
767- # there are no conflicting sources in the package entry);
768- # an error MUST be raised if any issues are found.
769- # Covered by lock.validate() above.
770-
771- # #. Add the package to the set of packages to install.
772- selected_packages_by_name [package .name ] = (package_index , package )
773-
774- # #. For each package to be installed:
775- for package_index , package in selected_packages_by_name .values ():
776- # - If :ref:`pylock-packages-vcs` is set:
777- if package .vcs is not None :
778- yield package , package .vcs
779-
780- # - Else if :ref:`pylock-packages-directory` is set:
781- elif package .directory is not None :
782- yield package , package .directory
783-
784- # - Else if :ref:`pylock-packages-archive` is set:
785- elif package .archive is not None :
786- yield package , package .archive
787-
788- # - Else if there are entries for :ref:`pylock-packages-wheels`:
789- elif package .wheels :
790- # #. Look for the appropriate wheel file based on
791- # :ref:`pylock-packages-wheels-name`; if one is not found then move on
792- # to :ref:`pylock-packages-sdist` or an error MUST be raised about a
793- # lack of source for the project.
794- for package_wheel in package .wheels :
795- try :
796- assert package_wheel .name # XXX get name from path or url
797- package_wheel_tags = parse_wheel_filename (package_wheel .name )[- 1 ]
798- except Exception as e :
799- raise PylockSelectError (
800- f"Invalid wheel filename { package_wheel .name !r} for "
801- f"package { package .name !r} at packages[{ package_index } ]"
802- ) from e
803- if not package_wheel_tags .isdisjoint (tags ):
804- yield package , package_wheel
718+ # #. If :ref:`pylock-environments` is specified, check that at least one of the
719+ # environment marker expressions is satisfied; an error MUST be raised if no
720+ # expression is satisfied.
721+ if self .environments :
722+ for env_marker in self .environments :
723+ if env_marker .evaluate (env , context = "lock_file" ): # XXX check context
805724 break
806725 else :
807- if package .sdist is not None :
808- yield package , package .sdist
809- else :
726+ raise PylockSelectError (
727+ "Provided environment does not satisfy any of the "
728+ "environments specified in the lock file"
729+ )
730+
731+ # #. For each package listed in :ref:`pylock-packages`:
732+ selected_packages_by_name : dict [str , tuple [int , Package ]] = {}
733+ for package_index , package in enumerate (self .packages ):
734+ # #. If :ref:`pylock-packages-marker` is specified, check if it is
735+ # 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
739+ continue
740+
741+ # #. If :ref:`pylock-packages-requires-python` is specified, check if it is
742+ # satisfied; an error MUST be raised if it isn't.
743+ if package .requires_python :
744+ if not env_python_version :
810745 raise PylockSelectError (
811- f"No matching wheel found matching the provided tags "
812- f"for package { package .name !r} at packages[{ package_index } ], "
813- f"and no sdist available as a fallback"
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 } ]"
814758 )
815759
816- # - Else if no :ref:`pylock-packages-wheels` file is found or
817- # :ref:`pylock-packages-sdist` is solely set:
818- elif package .sdist is not None :
819- yield package , package .sdist
760+ # #. Check that no other conflicting instance of the package has been slated
761+ # to be installed; an error about the ambiguity MUST be raised otherwise.
762+ if package .name in selected_packages_by_name :
763+ raise PylockSelectError (
764+ f"Multiple packages with the name { package .name !r} are "
765+ f"selected at packages[{ package_index } ] and "
766+ f"packages[{ selected_packages_by_name [package .name ][0 ]} ]"
767+ )
820768
821- else :
769+ # #. Check that the source of the package is specified appropriately (i.e.
770+ # there are no conflicting sources in the package entry);
771+ # an error MUST be raised if any issues are found.
822772 # Covered by lock.validate() above.
823- raise NotImplementedError
773+
774+ # #. Add the package to the set of packages to install.
775+ selected_packages_by_name [package .name ] = (package_index , package )
776+
777+ # #. For each package to be installed:
778+ for package_index , package in selected_packages_by_name .values ():
779+ # - If :ref:`pylock-packages-vcs` is set:
780+ if package .vcs is not None :
781+ yield package , package .vcs
782+
783+ # - Else if :ref:`pylock-packages-directory` is set:
784+ elif package .directory is not None :
785+ yield package , package .directory
786+
787+ # - Else if :ref:`pylock-packages-archive` is set:
788+ elif package .archive is not None :
789+ yield package , package .archive
790+
791+ # - Else if there are entries for :ref:`pylock-packages-wheels`:
792+ elif package .wheels :
793+ # #. Look for the appropriate wheel file based on
794+ # :ref:`pylock-packages-wheels-name`; if one is not found then move
795+ # on to :ref:`pylock-packages-sdist` or an error MUST be raised about
796+ # a lack of source for the project.
797+ for package_wheel in package .wheels :
798+ try :
799+ assert package_wheel .name # XXX get name from path or url
800+ package_wheel_tags = parse_wheel_filename (package_wheel .name )[
801+ - 1
802+ ]
803+ except Exception as e :
804+ raise PylockSelectError (
805+ f"Invalid wheel filename { package_wheel .name !r} for "
806+ f"package { package .name !r} at packages[{ package_index } ]"
807+ ) from e
808+ if not package_wheel_tags .isdisjoint (tags ):
809+ yield package , package_wheel
810+ break
811+ else :
812+ if package .sdist is not None :
813+ yield package , package .sdist
814+ else :
815+ raise PylockSelectError (
816+ f"No wheel found matching the provided tags "
817+ f"for package { package .name !r} "
818+ f"at packages[{ package_index } ], "
819+ f"and no sdist available as a fallback"
820+ )
821+
822+ # - Else if no :ref:`pylock-packages-wheels` file is found or
823+ # :ref:`pylock-packages-sdist` is solely set:
824+ elif package .sdist is not None :
825+ yield package , package .sdist
826+
827+ else :
828+ # Covered by lock.validate() above.
829+ raise NotImplementedError
0 commit comments