def test_supported_osx_version(self) -> None: """ Wheels built for macOS 10.6 are supported on 10.9 """ tags = compatibility_tags.get_supported( "27", platforms=["macosx_10_9_intel"], impl="cp") w = Wheel("simple-0.1-cp27-none-macosx_10_6_intel.whl") assert w.supported(tags=tags) w = Wheel("simple-0.1-cp27-none-macosx_10_9_intel.whl") assert w.supported(tags=tags)
def test_supported_osx_version(self): """ Wheels built for macOS 10.6 are supported on 10.9 """ tags = compatibility_tags.get_supported( '27', platforms=['macosx_10_9_intel'], impl='cp') w = Wheel('simple-0.1-cp27-none-macosx_10_6_intel.whl') assert w.supported(tags=tags) w = Wheel('simple-0.1-cp27-none-macosx_10_9_intel.whl') assert w.supported(tags=tags)
def test_not_supported_multiarch_darwin(self): """ Single-arch wheels (x86_64) are not supported on multi-arch (intel) """ universal = compatibility_tags.get_supported( '27', platforms=['macosx_10_5_universal'], impl='cp') intel = compatibility_tags.get_supported( '27', platforms=['macosx_10_5_intel'], impl='cp') w = Wheel('simple-0.1-cp27-none-macosx_10_5_i386.whl') assert not w.supported(tags=intel) assert not w.supported(tags=universal) w = Wheel('simple-0.1-cp27-none-macosx_10_5_x86_64.whl') assert not w.supported(tags=intel) assert not w.supported(tags=universal)
def test_not_supported_multiarch_darwin(self) -> None: """ Single-arch wheels (x86_64) are not supported on multi-arch (intel) """ universal = compatibility_tags.get_supported( "27", platforms=["macosx_10_5_universal"], impl="cp") intel = compatibility_tags.get_supported( "27", platforms=["macosx_10_5_intel"], impl="cp") w = Wheel("simple-0.1-cp27-none-macosx_10_5_i386.whl") assert not w.supported(tags=intel) assert not w.supported(tags=universal) w = Wheel("simple-0.1-cp27-none-macosx_10_5_x86_64.whl") assert not w.supported(tags=intel) assert not w.supported(tags=universal)
def get( self, link, # type: Link package_name, # type: Optional[str] supported_tags, # type: List[Pep425Tag] ): # type: (...) -> Link candidates = [] if not package_name: return link canonical_package_name = canonicalize_name(package_name) for wheel_name in self._get_candidates(link, canonical_package_name): try: wheel = Wheel(wheel_name) except InvalidWheelFilename: continue if canonicalize_name(wheel.name) != canonical_package_name: logger.debug( "Ignoring cached wheel {} for {} as it " "does not match the expected distribution name {}.".format( wheel_name, link, package_name)) continue if not wheel.supported(supported_tags): # Built for a different python/arch/etc continue candidates.append( (wheel.support_index_min(supported_tags), wheel_name)) if not candidates: return link return self._link_for_candidate(link, min(candidates)[1])
def make_requirement_from_install_req(self, ireq, requested_extras): # type: (InstallRequirement, Iterable[str]) -> Optional[Requirement] if not ireq.match_markers(requested_extras): logger.info( "Ignoring %s: markers '%s' don't match your environment", ireq.name, ireq.markers, ) return None if not ireq.link: return SpecifierRequirement(ireq) if ireq.link.is_wheel: wheel = Wheel(ireq.link.filename) if not wheel.supported(self._finder.target_python.get_tags()): msg = "{} is not a supported wheel on this platform.".format( wheel.filename, ) raise UnsupportedWheel(msg) cand = self._make_candidate_from_link( ireq.link, extras=frozenset(ireq.extras), template=ireq, name=canonicalize_name(ireq.name) if ireq.name else None, version=None, ) if cand is None: # There's no way we can satisfy a URL requirement if the underlying # candidate fails to build. An unnamed URL must be user-supplied, so # we fail eagerly. If the URL is named, an unsatisfiable requirement # can make the resolver do the right thing, either backtrack (and # maybe find some other requirement that's buildable) or raise a # ResolutionImpossible eventually. if not ireq.name: raise self._build_failures[ireq.link] return UnsatisfiableRequirement(canonicalize_name(ireq.name)) return self.make_requirement_from_candidate(cand)
def make_requirement_from_install_req(self, ireq, requested_extras): # type: (InstallRequirement, Iterable[str]) -> Optional[Requirement] if not ireq.match_markers(requested_extras): logger.info( "Ignoring %s: markers '%s' don't match your environment", ireq.name, ireq.markers, ) return None if not ireq.link: return SpecifierRequirement(ireq) if ireq.link.is_wheel: wheel = Wheel(ireq.link.filename) if not wheel.supported(self._finder.target_python.get_tags()): msg = "{} is not a supported wheel on this platform.".format( wheel.filename, ) raise UnsupportedWheel(msg) cand = self._make_candidate_from_link( ireq.link, extras=frozenset(ireq.extras), template=ireq, name=canonicalize_name(ireq.name) if ireq.name else None, version=None, ) return self.make_requirement_from_candidate(cand)
def _sort_key(self, candidate): # type: (InstallationCandidate) -> CandidateSortingKey """ Function to pass as the `key` argument to a call to sorted() to sort InstallationCandidates by preference. Returns a tuple such that tuples sorting as greater using Python's default comparison operator are more preferred. The preference is as follows: First and foremost, candidates with allowed (matching) hashes are always preferred over candidates without matching hashes. This is because e.g. if the only candidate with an allowed hash is yanked, we still want to use that candidate. Second, excepting hash considerations, candidates that have been yanked (in the sense of PEP 592) are always less preferred than candidates that haven't been yanked. Then: If not finding wheels, they are sorted by version only. If finding wheels, then the sort order is by version, then: 1. existing installs 2. wheels ordered via Wheel.support_index_min(self._supported_tags) 3. source archives If prefer_binary was set, then all wheels are sorted above sources. Note: it was considered to embed this logic into the Link comparison operators, but then different sdist links with the same version, would have to be considered equal """ valid_tags = self._supported_tags support_num = len(valid_tags) build_tag = () # type: BuildTag binary_preference = 0 link = candidate.link if link.is_wheel: # can raise InvalidWheelFilename wheel = Wheel(link.filename) if not wheel.supported(valid_tags): raise UnsupportedWheel( "{} is not a supported wheel for this platform. It " "can't be sorted.".format(wheel.filename) ) if self._prefer_binary: binary_preference = 1 pri = -(wheel.support_index_min(valid_tags)) if wheel.build_tag is not None: match = re.match(r'^(\d+)(.*)$', wheel.build_tag) build_tag_groups = match.groups() build_tag = (int(build_tag_groups[0]), build_tag_groups[1]) else: # sdist pri = -(support_num) has_allowed_hash = int(link.is_hash_allowed(self._hashes)) yank_value = -1 * int(link.is_yanked) # -1 for yanked. return ( has_allowed_hash, yank_value, binary_preference, candidate.version, build_tag, pri, )
def get( self, link, # type: Link package_name, # type: Optional[str] supported_tags, # type: List[Tag] ): # type: (...) -> Link candidates = [] if not package_name: return link canonical_package_name = canonicalize_name(package_name) for wheel_name, wheel_dir in self._get_candidates( link, canonical_package_name): try: wheel = Wheel(wheel_name) except InvalidWheelFilename: continue if canonicalize_name(wheel.name) != canonical_package_name: logger.debug( "Ignoring cached wheel %s for %s as it " "does not match the expected distribution name %s.", wheel_name, link, package_name, ) continue if not wheel.supported(supported_tags): # Built for a different python/arch/etc continue candidates.append(( wheel.support_index_min(supported_tags), wheel_name, wheel_dir, )) if not candidates: return link _, wheel_name, wheel_dir = min(candidates) return Link(path_to_url(os.path.join(wheel_dir, wheel_name)))
def _fail_if_link_is_unsupported_wheel(self, link: Link) -> None: if not link.is_wheel: return wheel = Wheel(link.filename) if wheel.supported(self._finder.target_python.get_tags()): return msg = f"{link.filename} is not a supported wheel on this platform." raise UnsupportedWheel(msg)
def test_not_supported_osx_version(self): """ Wheels built for macOS 10.9 are not supported on 10.6 """ tags = pep425tags.get_supported('27', platform='macosx_10_6_intel', impl='cp') w = Wheel('simple-0.1-cp27-none-macosx_10_9_intel.whl') assert not w.supported(tags=tags)
def test_supported_multiarch_darwin(self): """ Multi-arch wheels (intel) are supported on components (i386, x86_64) """ universal = pep425tags.get_supported('27', platform='macosx_10_5_universal', impl='cp') intel = pep425tags.get_supported('27', platform='macosx_10_5_intel', impl='cp') x64 = pep425tags.get_supported('27', platform='macosx_10_5_x86_64', impl='cp') i386 = pep425tags.get_supported('27', platform='macosx_10_5_i386', impl='cp') ppc = pep425tags.get_supported('27', platform='macosx_10_5_ppc', impl='cp') ppc64 = pep425tags.get_supported('27', platform='macosx_10_5_ppc64', impl='cp') w = Wheel('simple-0.1-cp27-none-macosx_10_5_intel.whl') assert w.supported(tags=intel) assert w.supported(tags=x64) assert w.supported(tags=i386) assert not w.supported(tags=universal) assert not w.supported(tags=ppc) assert not w.supported(tags=ppc64) w = Wheel('simple-0.1-cp27-none-macosx_10_5_universal.whl') assert w.supported(tags=universal) assert w.supported(tags=intel) assert w.supported(tags=x64) assert w.supported(tags=i386) assert w.supported(tags=ppc) assert w.supported(tags=ppc64)
def test_not_supported_version(self): """ Test unsupported wheel is known to be unsupported """ w = Wheel('simple-0.1-py2-none-any.whl') assert not w.supported(tags=[Tag('py1', 'none', 'any')])
def test_supported_multi_version(self): """ Test multi-version wheel is known to be supported """ w = Wheel('simple-0.1-py2.py3-none-any.whl') assert w.supported(tags=[Tag('py3', 'none', 'any')])
def test_supported_single_version(self): """ Test single-version wheel is known to be supported """ w = Wheel('simple-0.1-py2-none-any.whl') assert w.supported(tags=[Tag('py2', 'none', 'any')])
def test_supported_multi_version(self) -> None: """ Test multi-version wheel is known to be supported """ w = Wheel("simple-0.1-py2.py3-none-any.whl") assert w.supported(tags=[Tag("py3", "none", "any")])
def evaluate_link(self, link): # type: (Link) -> Tuple[bool, Optional[Text]] """ Determine whether a link is a candidate for installation. :return: A tuple (is_candidate, result), where `result` is (1) a version string if `is_candidate` is True, and (2) if `is_candidate` is False, an optional string to log the reason the link fails to qualify. """ version = None if link.is_yanked and not self._allow_yanked: reason = link.yanked_reason or "<none given>" # Mark this as a unicode string to prevent "UnicodeEncodeError: # 'ascii' codec can't encode character" in Python 2 when # the reason contains non-ascii characters. return (False, u"yanked for reason: {}".format(reason)) if link.egg_fragment: egg_info = link.egg_fragment ext = link.ext else: egg_info, ext = link.splitext() if not ext: return (False, "not a file") if ext not in SUPPORTED_EXTENSIONS: return (False, "unsupported archive format: {}".format(ext)) if "binary" not in self._formats and ext == WHEEL_EXTENSION: reason = "No binaries permitted for {}".format( self.project_name) return (False, reason) if "macosx10" in link.path and ext == ".zip": return (False, "macosx10 one") if ext == WHEEL_EXTENSION: try: wheel = Wheel(link.filename) except InvalidWheelFilename: return (False, "invalid wheel filename") if canonicalize_name(wheel.name) != self._canonical_name: reason = "wrong project name (not {})".format( self.project_name) return (False, reason) supported_tags = self._target_python.get_tags() if not wheel.supported(supported_tags): # Include the wheel's tags in the reason string to # simplify troubleshooting compatibility issues. file_tags = wheel.get_formatted_file_tags() reason = "none of the wheel's tags match: {}".format( ", ".join(file_tags)) return (False, reason) version = wheel.version # This should be up by the self.ok_binary check, but see issue 2700. if "source" not in self._formats and ext != WHEEL_EXTENSION: reason = "No sources permitted for {}".format(self.project_name) return (False, reason) if not version: version = _extract_version_from_fragment( egg_info, self._canonical_name, ) if not version: reason = "Missing project version for {}".format(self.project_name) return (False, reason) match = self._py_version_re.search(version) if match: version = version[:match.start()] py_version = match.group(1) if py_version != self._target_python.py_version: return (False, "Python version is incorrect") supports_python = _check_link_requires_python( link, version_info=self._target_python.py_version_info, ignore_requires_python=self._ignore_requires_python, ) if not supports_python: # Return None for the reason text to suppress calling # _log_skipped_link(). return (False, None) logger.debug("Found link %s, version: %s", link, version) return (True, version)
def find_candidates( self, identifier: str, requirements: Mapping[str, Iterator[Requirement]], incompatibilities: Mapping[str, Iterator[Candidate]], constraint: Constraint, prefers_installed: bool, ) -> Iterable[Candidate]: # Since we cache all the candidates, incompatibility identification # can be made quicker by comparing only the id() values. incompat_ids = {id(c) for c in incompatibilities.get(identifier, ())} explicit_candidates = set() # type: Set[Candidate] ireqs = [] # type: List[InstallRequirement] for req in requirements[identifier]: cand, ireq = req.get_candidate_lookup() if cand is not None and id(cand) not in incompat_ids: explicit_candidates.add(cand) if ireq is not None: ireqs.append(ireq) for link in constraint.links: if not ireqs: # If we hit this condition, then we cannot construct a candidate. # However, if we hit this condition, then none of the requirements # provided an ireq, so they must have provided an explicit candidate. # In that case, either the candidate matches, in which case this loop # doesn't need to do anything, or it doesn't, in which case there's # nothing this loop can do to recover. break if link.is_wheel: wheel = Wheel(link.filename) # Check whether the provided wheel is compatible with the target # platform. if not wheel.supported(self._finder.target_python.get_tags()): # We are constrained to install a wheel that is incompatible with # the target architecture, so there are no valid candidates. # Return early, with no candidates. return () # Create a "fake" InstallRequirement that's basically a clone of # what "should" be the template, but with original_link set to link. # Using the given requirement is necessary for preserving hash # requirements, but without the original_link, direct_url.json # won't be created. ireq = install_req_from_link_and_ireq(link, ireqs[0]) candidate = self._make_candidate_from_link( link, extras=frozenset(), template=ireq, name=canonicalize_name(ireq.name) if ireq.name else None, version=None, ) if candidate is None: # _make_candidate_from_link returns None if the wheel fails to build. # We are constrained to install this wheel, so there are no valid # candidates. # Return early, with no candidates. return () explicit_candidates.add(candidate) # If none of the requirements want an explicit candidate, we can ask # the finder for candidates. if not explicit_candidates: return self._iter_found_candidates( ireqs, constraint.specifier, constraint.hashes, prefers_installed, incompat_ids, ) return (c for c in explicit_candidates if constraint.is_satisfied_by(c) and all( req.is_satisfied_by(c) for req in requirements[identifier]))
def test_supported_single_version(self) -> None: """ Test single-version wheel is known to be supported """ w = Wheel("simple-0.1-py2-none-any.whl") assert w.supported(tags=[Tag("py2", "none", "any")])
def evaluate_link(self, link: Link) -> Tuple[LinkType, str]: """ Determine whether a link is a candidate for installation. :return: A tuple (result, detail), where *result* is an enum representing whether the evaluation found a candidate, or the reason why one is not found. If a candidate is found, *detail* will be the candidate's version string; if one is not found, it contains the reason the link fails to qualify. """ version = None if link.is_yanked and not self._allow_yanked: reason = link.yanked_reason or "<none given>" return (LinkType.yanked, f"yanked for reason: {reason}") if link.egg_fragment: egg_info = link.egg_fragment ext = link.ext else: egg_info, ext = link.splitext() if not ext: return (LinkType.format_unsupported, "not a file") if ext not in SUPPORTED_EXTENSIONS: return ( LinkType.format_unsupported, f"unsupported archive format: {ext}", ) if "binary" not in self._formats and ext == WHEEL_EXTENSION: reason = f"No binaries permitted for {self.project_name}" return (LinkType.format_unsupported, reason) if "macosx10" in link.path and ext == ".zip": return (LinkType.format_unsupported, "macosx10 one") if ext == WHEEL_EXTENSION: try: wheel = Wheel(link.filename) except InvalidWheelFilename: return ( LinkType.format_invalid, "invalid wheel filename", ) if canonicalize_name(wheel.name) != self._canonical_name: reason = f"wrong project name (not {self.project_name})" return (LinkType.different_project, reason) supported_tags = self._target_python.get_tags() if not wheel.supported(supported_tags): # Include the wheel's tags in the reason string to # simplify troubleshooting compatibility issues. file_tags = ", ".join(wheel.get_formatted_file_tags()) reason = ( f"none of the wheel's tags ({file_tags}) are compatible " f"(run pip debug --verbose to show compatible tags)") return (LinkType.platform_mismatch, reason) version = wheel.version # This should be up by the self.ok_binary check, but see issue 2700. if "source" not in self._formats and ext != WHEEL_EXTENSION: reason = f"No sources permitted for {self.project_name}" return (LinkType.format_unsupported, reason) if not version: version = _extract_version_from_fragment( egg_info, self._canonical_name, ) if not version: reason = f"Missing project version for {self.project_name}" return (LinkType.format_invalid, reason) match = self._py_version_re.search(version) if match: version = version[:match.start()] py_version = match.group(1) if py_version != self._target_python.py_version: return ( LinkType.platform_mismatch, "Python version is incorrect", ) supports_python = _check_link_requires_python( link, version_info=self._target_python.py_version_info, ignore_requires_python=self._ignore_requires_python, ) if not supports_python: reason = f"{version} Requires-Python {link.requires_python}" return (LinkType.requires_python_mismatch, reason) logger.debug("Found link %s, version: %s", link, version) return (LinkType.candidate, version)
def test_supported_multiarch_darwin(self) -> None: """ Multi-arch wheels (intel) are supported on components (i386, x86_64) """ universal = compatibility_tags.get_supported( "27", platforms=["macosx_10_5_universal"], impl="cp") intel = compatibility_tags.get_supported( "27", platforms=["macosx_10_5_intel"], impl="cp") x64 = compatibility_tags.get_supported( "27", platforms=["macosx_10_5_x86_64"], impl="cp") i386 = compatibility_tags.get_supported("27", platforms=["macosx_10_5_i386"], impl="cp") ppc = compatibility_tags.get_supported("27", platforms=["macosx_10_5_ppc"], impl="cp") ppc64 = compatibility_tags.get_supported( "27", platforms=["macosx_10_5_ppc64"], impl="cp") w = Wheel("simple-0.1-cp27-none-macosx_10_5_intel.whl") assert w.supported(tags=intel) assert w.supported(tags=x64) assert w.supported(tags=i386) assert not w.supported(tags=universal) assert not w.supported(tags=ppc) assert not w.supported(tags=ppc64) w = Wheel("simple-0.1-cp27-none-macosx_10_5_universal.whl") assert w.supported(tags=universal) assert w.supported(tags=intel) assert w.supported(tags=x64) assert w.supported(tags=i386) assert w.supported(tags=ppc) assert w.supported(tags=ppc64)
def evaluate_link(self, link): # type: (Link) -> Tuple[bool, Optional[str]] """ Determine whether a link is a candidate for installation. :return: A tuple (is_candidate, result), where `result` is (1) a version string if `is_candidate` is True, and (2) if `is_candidate` is False, an optional string to log the reason the link fails to qualify. """ version = None if link.is_yanked and not self._allow_yanked: reason = link.yanked_reason or '<none given>' return (False, f'yanked for reason: {reason}') if link.egg_fragment: egg_info = link.egg_fragment ext = link.ext else: egg_info, ext = link.splitext() if not ext: return (False, 'not a file') if ext not in SUPPORTED_EXTENSIONS: return (False, f'unsupported archive format: {ext}') if "binary" not in self._formats and ext == WHEEL_EXTENSION: reason = 'No binaries permitted for {}'.format( self.project_name) return (False, reason) if "macosx10" in link.path and ext == '.zip': return (False, 'macosx10 one') if ext == WHEEL_EXTENSION: try: wheel = Wheel(link.filename) except InvalidWheelFilename: return (False, 'invalid wheel filename') if canonicalize_name(wheel.name) != self._canonical_name: reason = 'wrong project name (not {})'.format( self.project_name) return (False, reason) supported_tags = self._target_python.get_tags() if not wheel.supported(supported_tags): # Include the wheel's tags in the reason string to # simplify troubleshooting compatibility issues. file_tags = wheel.get_formatted_file_tags() reason = ("none of the wheel's tags match: {}".format( ', '.join(file_tags))) return (False, reason) version = wheel.version # This should be up by the self.ok_binary check, but see issue 2700. if "source" not in self._formats and ext != WHEEL_EXTENSION: reason = f'No sources permitted for {self.project_name}' return (False, reason) if not version: version = _extract_version_from_fragment( egg_info, self._canonical_name, ) if not version: reason = f'Missing project version for {self.project_name}' return (False, reason) match = self._py_version_re.search(version) if match: version = version[:match.start()] py_version = match.group(1) if py_version != self._target_python.py_version: return (False, 'Python version is incorrect') supports_python = _check_link_requires_python( link, version_info=self._target_python.py_version_info, ignore_requires_python=self._ignore_requires_python, ) if not supports_python: # Return None for the reason text to suppress calling # _log_skipped_link(). return (False, None) logger.debug('Found link %s, version: %s', link, version) return (True, version)
def add_requirement( self, install_req, # type: InstallRequirement parent_req_name=None, # type: Optional[str] extras_requested=None, # type: Optional[Iterable[str]] ): # type: (...) -> Tuple[List[InstallRequirement], Optional[InstallRequirement]] # noqa: E501 """Add install_req as a requirement to install. :param parent_req_name: The name of the requirement that needed this added. The name is used because when multiple unnamed requirements resolve to the same name, we could otherwise end up with dependency links that point outside the Requirements set. parent_req must already be added. Note that None implies that this is a user supplied requirement, vs an inferred one. :param extras_requested: an iterable of extras used to evaluate the environment markers. :return: Additional requirements to scan. That is either [] if the requirement is not applicable, or [install_req] if the requirement is applicable and has just been added. """ # If the markers do not match, ignore this requirement. if not install_req.match_markers(extras_requested): logger.info( "Ignoring %s: markers '%s' don't match your environment", install_req.name, install_req.markers, ) return [], None # If the wheel is not supported, raise an error. # Should check this after filtering out based on environment markers to # allow specifying different wheels based on the environment/OS, in a # single requirements file. if install_req.link and install_req.link.is_wheel: wheel = Wheel(install_req.link.filename) tags = pep425tags.get_supported() if self.check_supported_wheels and not wheel.supported(tags): raise InstallationError( "%s is not a supported wheel on this platform." % wheel.filename ) # This next bit is really a sanity check. assert install_req.is_direct == (parent_req_name is None), ( "a direct req shouldn't have a parent and also, " "a non direct req should have a parent" ) # Unnamed requirements are scanned again and the requirement won't be # added as a dependency until after scanning. if not install_req.name: self.add_unnamed_requirement(install_req) return [install_req], None try: existing_req = self.get_requirement(install_req.name) except KeyError: existing_req = None has_conflicting_requirement = ( parent_req_name is None and existing_req and not existing_req.constraint and existing_req.extras == install_req.extras and existing_req.req.specifier != install_req.req.specifier ) if has_conflicting_requirement: raise InstallationError( "Double requirement given: %s (already in %s, name=%r)" % (install_req, existing_req, install_req.name) ) # When no existing requirement exists, add the requirement as a # dependency and it will be scanned again after. if not existing_req: self.add_named_requirement(install_req) # We'd want to rescan this requirement later return [install_req], install_req # Assume there's no need to scan, and that we've already # encountered this for scanning. if install_req.constraint or not existing_req.constraint: return [], existing_req does_not_satisfy_constraint = install_req.link and not ( existing_req.link and install_req.link.path == existing_req.link.path ) if does_not_satisfy_constraint: self.reqs_to_cleanup.append(install_req) raise InstallationError( "Could not satisfy constraints for '%s': " "installation from path or url cannot be " "constrained to a version" % install_req.name, ) # If we're now installing a constraint, mark the existing # object for real installation. existing_req.constraint = False existing_req.extras = tuple( sorted(set(existing_req.extras) | set(install_req.extras)) ) logger.debug( "Setting %s extras to: %s", existing_req, existing_req.extras, ) # Return the existing requirement for addition to the parent and # scanning again. return [existing_req], existing_req
def test_not_supported_version(self) -> None: """ Test unsupported wheel is known to be unsupported """ w = Wheel("simple-0.1-py2-none-any.whl") assert not w.supported(tags=[Tag("py1", "none", "any")])